From 68b03eea71c258a4335f34cebfbb0affdff7b2a6 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 11:58:20 +0200 Subject: [PATCH 01/75] wip --- .../feature-payin-account/build.gradle.kts | 34 ++ .../src/main/AndroidManifest.xml | 2 + .../main/graphql/SetupInvoicePayin.graphql} | 0 .../account/data/SetupInvoicePayoutUseCase.kt | 35 ++ .../navigation/PayinAccountDestination.kt | 25 ++ .../payin/account/navigation/PayinNavGraph.kt | 97 +++++ .../PayoutAccountOverviewDestination.kt | 340 ++++++++++++++++++ .../PayoutAccountOverviewViewModel.kt | 76 ++++ .../SelectPayinMethodDestination.kt | 94 +++++ .../SetupInvoicePayinDestination.kt} | 22 +- .../SetupInvoicePayoutViewModel.kt | 2 +- .../ui/setupswish/SetupSwishPayinViewModel.kt | 90 +++++ .../setupswish/SetupSwishPayoutDestination.kt | 116 ++++++ .../navigation/PayoutAccountGraph.kt | 15 - .../SelectPayoutMethodDestination.kt | 9 - .../lint-baseline-feature-payin-account.xml | 51 +++ 16 files changed, 981 insertions(+), 27 deletions(-) create mode 100644 app/feature/feature-payin-account/build.gradle.kts create mode 100644 app/feature/feature-payin-account/src/main/AndroidManifest.xml rename app/feature/{feature-payout-account/src/main/graphql/SetupInvoicePayout.graphql => feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql} (100%) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt rename app/feature/{feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutDestination.kt => feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt} (83%) rename app/feature/{feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount => feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account}/ui/setupinvoice/SetupInvoicePayoutViewModel.kt (97%) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt create mode 100644 hedvig-lint/lint-baseline/lint-baseline-feature-payin-account.xml diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts new file mode 100644 index 0000000000..105feaab33 --- /dev/null +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id("hedvig.android.library") + id("hedvig.gradle.plugin") +} + +hedvig { + apollo("octopus") + serialization() + compose() +} + +dependencies { + implementation(libs.apollo.normalizedCache) + implementation(libs.apollo.runtime) + implementation(libs.arrow.core) + implementation(projects.apolloCore) + implementation(projects.apolloNetworkCacheManager) + implementation(projects.apolloOctopusPublic) + implementation(projects.coreBuildConstants) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.lifecycle.runtime.compose) + implementation(libs.jetbrains.navigation.compose) + implementation(libs.koin.composeViewModel) + implementation(libs.koin.core) + implementation(projects.composeUi) + implementation(projects.coreCommonPublic) + implementation(projects.coreResources) + implementation(projects.designSystemHedvig) + implementation(projects.moleculePublic) + implementation(projects.navigationCommon) + implementation(projects.navigationCompose) + implementation(projects.navigationComposeTyped) + implementation(projects.navigationCore) +} diff --git a/app/feature/feature-payin-account/src/main/AndroidManifest.xml b/app/feature/feature-payin-account/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..568741e54f --- /dev/null +++ b/app/feature/feature-payin-account/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/feature/feature-payout-account/src/main/graphql/SetupInvoicePayout.graphql b/app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql similarity index 100% rename from app/feature/feature-payout-account/src/main/graphql/SetupInvoicePayout.graphql rename to app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt new file mode 100644 index 0000000000..bbceab4868 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt @@ -0,0 +1,35 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +import arrow.core.raise.either +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.ErrorMessage +import com.hedvig.android.apollo.NetworkCacheManager +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.core.common.ErrorMessage +import octopus.SetupInvoicePayoutMutation +import octopus.type.PaymentMethodInvoiceDelivery +import octopus.type.PaymentMethodSetupStatus + +internal class SetupInvoicePayoutUseCase( + private val apolloClient: ApolloClient, + private val networkCacheManager: NetworkCacheManager, +) { + suspend fun invoke(): Either = either { + val result = apolloClient + .mutation(SetupInvoicePayoutMutation()) + .safeExecute(::ErrorMessage) + .bind() + + val output = result.paymentMethodSetupInvoicePayin + when (output.status) { + PaymentMethodSetupStatus.FAILED -> { + raise(ErrorMessage(output.error?.message ?: "Failed to set up invoice payout")) + } + + else -> { + networkCacheManager.clearCache() + } + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt new file mode 100644 index 0000000000..6539776797 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -0,0 +1,25 @@ +package com.hedvig.android.feature.payin.account.navigation + +import com.hedvig.android.navigation.common.Destination +import kotlinx.serialization.Serializable + +sealed interface PayinAccountDestination { + @Serializable + data object Graph : PayinAccountDestination, Destination +} + +internal sealed interface PayinAccountDestinations { + @Serializable + data object Overview : PayinAccountDestinations, Destination + + @Serializable + data class SelectPayinMethod( + val availableProviders: List, + ) : PayinAccountDestinations, Destination + + @Serializable + data object SetupSwishPayin : PayinAccountDestinations, Destination + + @Serializable + data object SetupInvoicePayin : PayinAccountDestinations, Destination +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt new file mode 100644 index 0000000000..5826555180 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -0,0 +1,97 @@ +package com.hedvig.android.feature.payin.account.navigation + +import androidx.lifecycle.compose.dropUnlessResumed +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptionsBuilder +import com.hedvig.android.design.system.hedvig.GlobalSnackBarState +import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewDestination +import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewUiState +import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewViewModel +import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination +import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayoutViewModel +import com.hedvig.android.navigation.compose.navDeepLinks +import com.hedvig.android.navigation.compose.navdestination +import com.hedvig.android.navigation.compose.navgraph +import com.hedvig.android.navigation.compose.typedPopBackStack +import com.hedvig.android.navigation.compose.typedPopUpTo +import com.hedvig.android.navigation.core.HedvigDeepLinkContainer +import octopus.type.MemberPaymentProvider +import org.koin.compose.viewmodel.koinViewModel + +fun NavGraphBuilder.payinAccountGraph( + navController: NavController, + globalSnackBarState: GlobalSnackBarState, + hedvigDeepLinkContainer: HedvigDeepLinkContainer, + navigateToConnectPayment: (builder: NavOptionsBuilder.() -> Unit) -> Unit, + navigateUp: () -> Unit, +) { + navgraph( + startDestination = PayinAccountDestinations.Overview::class, + deepLinks = navDeepLinks(hedvigDeepLinkContainer.payout), + ) { + navdestination { + val viewModel: PayoutAccountOverviewViewModel = koinViewModel() + PayoutAccountOverviewDestination( + viewModel = viewModel, + onConnectPayoutMethodClicked = dropUnlessResumed { + val content = viewModel.uiState.value as? PayoutAccountOverviewUiState.Content + navController.navigate( + PayinAccountDestinations.SelectPayinMethod( + availableProviders = content?.availablePayoutMethods?.map { it.rawValue } ?: emptyList(), + ), + ) + }, + navigateToConnectPayment = dropUnlessResumed { + navigateToConnectPayment { + typedPopUpTo { + inclusive = true + } + } + }, + navigateUp = navigateUp, + ) + } + + navdestination { + SelectPayoutMethodDestination( + availableProviders = this.availableProviders.map { MemberPaymentProvider.safeValueOf(it) }, + onTrustlySelected = dropUnlessResumed { + navigateToConnectPayment { + typedPopUpTo { + inclusive = true + } + } + }, + onNordeaSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.EditBankAccount) }, + onSwishSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupSwishPayin) }, + onInvoiceSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupInvoicePayin) }, + navigateUp = navController::navigateUp, + ) + } + + navdestination { + val viewModel: SetupSwishPayoutViewModel = koinViewModel() + SetupSwishPayoutDestination( + viewModel = viewModel, + globalSnackBarState = globalSnackBarState, + onSuccessfullyConnected = { + navController.typedPopBackStack(inclusive = true) + }, + navigateUp = navController::navigateUp, + ) + } + + navdestination { + val viewModel: SetupInvoicePayoutViewModel = koinViewModel() + SetupInvoicePayinDestination( + viewModel = viewModel, + globalSnackBarState = globalSnackBarState, + onSuccessfullyConnected = { + navController.typedPopBackStack(inclusive = true) + }, + navigateUp = navController::navigateUp, + ) + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt new file mode 100644 index 0000000000..ee8c3407c8 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt @@ -0,0 +1,340 @@ +package com.hedvig.android.feature.payin.account.ui.overview + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.design.system.hedvig.EmptyState +import com.hedvig.android.design.system.hedvig.EmptyStateDefaults +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigErrorSection +import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced +import com.hedvig.android.design.system.hedvig.HedvigInformationSection +import com.hedvig.android.design.system.hedvig.HedvigNotificationCard +import com.hedvig.android.design.system.hedvig.HedvigPreview +import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigTextField +import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.feature.payoutaccount.data.PayoutAccount +import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Content +import hedvig.resources.CHANGE_PAYOUT_METHOD_BUTTON_LABEL +import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE +import hedvig.resources.PAYMENTS_ACCOUNT +import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYOUT_MISSING_INFO +import hedvig.resources.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE +import hedvig.resources.PAYOUT_NO_PAYOUT_OPTIONS_TITLE +import hedvig.resources.PAYOUT_PAGE_HEADING +import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD +import hedvig.resources.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON +import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL +import hedvig.resources.Res +import hedvig.resources.swish +import hedvig.resources.trustly +import octopus.type.MemberPaymentProvider +import octopus.type.PaymentMethodInvoiceDelivery +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun PayoutAccountOverviewDestination( + viewModel: PayoutAccountOverviewViewModel, + onConnectPayoutMethodClicked: () -> Unit, + navigateToConnectPayment: () -> Unit, + navigateUp: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + PayoutAccountOverviewScreen( + uiState = uiState, + onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, + navigateToConnectPayment = navigateToConnectPayment, + onRetry = { viewModel.emit(PayoutAccountOverviewEvent.Retry) }, + navigateUp = navigateUp, + ) +} + +@Composable +private fun PayoutAccountOverviewScreen( + uiState: PayoutAccountOverviewUiState, + onConnectPayoutMethodClicked: () -> Unit, + navigateToConnectPayment: () -> Unit, + onRetry: () -> Unit, + navigateUp: () -> Unit, +) { + HedvigScaffold( + topAppBarText = stringResource(Res.string.PAYOUT_PAGE_HEADING), + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + when (uiState) { + PayoutAccountOverviewUiState.Loading -> { + HedvigFullScreenCenterAlignedProgressDebounced( + Modifier + .weight(1f) + .wrapContentHeight(), + ) + } + + PayoutAccountOverviewUiState.Error -> { + HedvigErrorSection( + onButtonClick = onRetry, + modifier = Modifier + .weight(1f) + .wrapContentHeight(), + ) + } + + PayoutAccountOverviewUiState.NoPayoutOptions -> { + HedvigInformationSection( + title = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_TITLE), + subTitle = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE), + buttonText = stringResource(Res.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON), + onButtonClick = navigateToConnectPayment, + modifier = Modifier + .weight(1f) + .wrapContentHeight(), + ) + } + + is Content -> { + PayoutAccountContent( + currentMethod = uiState.currentMethod, + availablePayoutMethods = uiState.availablePayoutMethods, + onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, + modifier = Modifier.weight(1f), + ) + } + } + } +} + +@Composable +private fun PayoutAccountContent( + currentMethod: PayoutAccount?, + availablePayoutMethods: List, + onConnectPayoutMethodClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier) { + Spacer(Modifier.height(8.dp)) + when (currentMethod) { + null -> { + if (availablePayoutMethods.isNotEmpty()) { + Spacer(Modifier.weight(1f)) + EmptyState( + text = stringResource(Res.string.PAYOUT_MISSING_INFO), + description = null, + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, + ) + } + } + + is PayoutAccount.SwishPayout -> { + val phoneNumber = currentMethod.phoneNumber.orEmpty() + PayoutAccountReadOnlyTextField( + label = stringResource(Res.string.swish), + text = if (currentMethod.isPending && phoneNumber.isBlank()) { + stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + } else { + phoneNumber + }, + ) + } + + is PayoutAccount.Trustly -> { + val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) + PayoutAccountReadOnlyTextField( + label = formatBankAccountLabel(stringResource(Res.string.trustly), currentMethod.bankName), + text = if (currentMethod.isPending && accountNumber.isBlank()) { + stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + } else { + accountNumber + }, + ) + } + + is PayoutAccount.Invoice -> { + PayoutAccountReadOnlyTextField( + stringResource(Res.string.PAYMENTS_ACCOUNT), + stringResource(Res.string.PAYMENTS_INVOICE), + ) + } + + is PayoutAccount.BankAccount -> { + val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) + PayoutAccountReadOnlyTextField( + label = formatBankAccountLabel(stringResource(Res.string.PAYMENTS_ACCOUNT), currentMethod.bankName), + text = if (currentMethod.isPending && accountNumber.isBlank()) { + stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + } else { + accountNumber + }, + ) + } + } + Spacer(Modifier.weight(1f)) + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + if (currentMethod?.isPending == true) { + HedvigNotificationCard( + message = stringResource(Res.string.MY_PAYMENT_UPDATING_MESSAGE), + priority = NotificationPriority.Info, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + if (availablePayoutMethods.isNotEmpty()) { + HedvigButton( + text = if (currentMethod == null) { + stringResource(Res.string.PAYOUT_SELECT_PAYOUT_METHOD) + } else { + stringResource(Res.string.CHANGE_PAYOUT_METHOD_BUTTON_LABEL) + }, + onClick = onConnectPayoutMethodClicked, + enabled = true, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + } + Spacer(Modifier.height(16.dp)) + } +} + +@Composable +private fun PayoutAccountReadOnlyTextField(label: String, text: String, modifier: Modifier = Modifier) { + HedvigTextField( + text = text, + onValueChange = {}, + labelText = label, + textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, + readOnly = true, + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) +} + +private fun formatBankAccountLabel(baseLabel: String, bankName: String?): String { + return if (bankName != null) "$baseLabel - $bankName" else baseLabel +} + +private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?): String { + return when { + clearingNumber != null && accountNumber != null -> "$clearingNumber-$accountNumber" + else -> clearingNumber.orEmpty() + } +} + +@Composable +@HedvigPreview +private fun PreviewPayoutAccountOverviewScreen( + @PreviewParameter(PayoutAccountOverviewUiStateProvider::class) uiState: PayoutAccountOverviewUiState, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + PayoutAccountOverviewScreen( + uiState = uiState, + onConnectPayoutMethodClicked = {}, + navigateToConnectPayment = {}, + onRetry = {}, + navigateUp = {}, + ) + } + } +} + +private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( + listOf( + PayoutAccountOverviewUiState.Loading, + PayoutAccountOverviewUiState.Error, + PayoutAccountOverviewUiState.NoPayoutOptions, + Content( + currentMethod = null, + availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), + ), + Content( + currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = false), + availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), + ), + Content( + currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = false), + availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), + ), + Content( + currentMethod = PayoutAccount.SwishPayout(phoneNumber = null, isPending = true), + availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), + ), + Content( + currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = true), + availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), + ), + Content( + currentMethod = PayoutAccount.Trustly( + clearingNumber = "8327", + accountNumber = "12345678", + bankName = "Mock Swedbank", + isPending = false, + ), + availablePayoutMethods = listOf(MemberPaymentProvider.TRUSTLY), + ), + Content( + currentMethod = PayoutAccount.BankAccount( + clearingNumber = "3300", + accountNumber = "1234567", + bankName = "Nordea", + isPending = false, + ), + availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), + ), + Content( + currentMethod = PayoutAccount.BankAccount( + clearingNumber = null, + accountNumber = null, + bankName = null, + isPending = true, + ), + availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), + ), + Content( + currentMethod = PayoutAccount.BankAccount( + clearingNumber = "3300", + accountNumber = "1234567", + bankName = "Nordea", + isPending = true, + ), + availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), + ), + Content( + currentMethod = PayoutAccount.Invoice( + delivery = PaymentMethodInvoiceDelivery.KIVRA, + email = null, + isPending = false, + ), + availablePayoutMethods = listOf(MemberPaymentProvider.INVOICE), + ), + Content( + currentMethod = PayoutAccount.Invoice( + delivery = PaymentMethodInvoiceDelivery.MAIL, + email = "user@example.com", + isPending = false, + ), + availablePayoutMethods = listOf(MemberPaymentProvider.INVOICE, MemberPaymentProvider.TRUSTLY), + ), + ), +) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt new file mode 100644 index 0000000000..b0b07e1299 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt @@ -0,0 +1,76 @@ +package com.hedvig.android.feature.payin.account.ui.overview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.feature.payoutaccount.data.GetPayoutAccountUseCase +import com.hedvig.android.feature.payoutaccount.data.PayoutAccount +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import octopus.type.MemberPaymentProvider + +internal class PayoutAccountOverviewViewModel( + getPayoutAccountUseCase: GetPayoutAccountUseCase, +) : MoleculeViewModel( + PayoutAccountOverviewUiState.Loading, + PayoutAccountOverviewPresenter(getPayoutAccountUseCase), + ) + +internal sealed interface PayoutAccountOverviewEvent { + data object Retry : PayoutAccountOverviewEvent +} + +internal sealed interface PayoutAccountOverviewUiState { + data object Loading : PayoutAccountOverviewUiState + + data object Error : PayoutAccountOverviewUiState + + data object NoPayoutOptions : PayoutAccountOverviewUiState + + data class Content( + val currentMethod: PayoutAccount?, + val availablePayoutMethods: List, + ) : PayoutAccountOverviewUiState +} + +internal class PayoutAccountOverviewPresenter( + private val getPayoutAccountUseCase: GetPayoutAccountUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: PayoutAccountOverviewUiState, + ): PayoutAccountOverviewUiState { + var loadIteration by remember { mutableIntStateOf(0) } + var uiState by remember { mutableStateOf(lastState) } + + LaunchedEffect(loadIteration) { + uiState = PayoutAccountOverviewUiState.Loading + getPayoutAccountUseCase.invoke().fold( + ifLeft = { uiState = PayoutAccountOverviewUiState.Error }, + ifRight = { data -> + uiState = if (data.currentMethod == null && data.availablePayoutMethods.isEmpty()) { + PayoutAccountOverviewUiState.NoPayoutOptions + } else { + PayoutAccountOverviewUiState.Content( + currentMethod = data.currentMethod, + availablePayoutMethods = data.availablePayoutMethods, + ) + } + }, + ) + } + + CollectEvents { event -> + when (event) { + PayoutAccountOverviewEvent.Retry -> loadIteration++ + } + } + + return uiState + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt new file mode 100644 index 0000000000..8d8fb06104 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -0,0 +1,94 @@ +package com.hedvig.android.feature.payin.account.ui.selectmethod + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTheme +import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION +import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE +import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYOUT_METHOD_INVOICE_DESCRIPTION +import hedvig.resources.PAYOUT_METHOD_SWISH_DESCRIPTION +import hedvig.resources.PAYOUT_METHOD_TRUSTLY_DESCRIPTION +import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD +import hedvig.resources.Res +import hedvig.resources.swish +import hedvig.resources.trustly +import octopus.type.MemberPaymentProvider +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun SelectPayinMethodDestination( + availableProviders: List, + onTrustlySelected: () -> Unit, + onSwishSelected: () -> Unit, + onInvoiceSelected: () -> Unit, + navigateUp: () -> Unit, +) { + HedvigScaffold( + topAppBarText = stringResource(Res.string.PAYOUT_SELECT_PAYOUT_METHOD), //todo + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + Spacer(Modifier.height(8.dp)) + Column(Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + for (provider in availableProviders) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> { + PayinMethodRow( + title = stringResource(Res.string.trustly), + subtitle = stringResource(Res.string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), //todo + onClick = onTrustlySelected, + ) + } + + MemberPaymentProvider.SWISH -> { + PayinMethodRow( + title = stringResource(Res.string.swish), + subtitle = stringResource(Res.string.PAYOUT_METHOD_SWISH_DESCRIPTION), //todo + onClick = onSwishSelected, + ) + } + + MemberPaymentProvider.INVOICE -> { + PayinMethodRow( + title = stringResource(Res.string.PAYMENTS_INVOICE), + subtitle = stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION), //todo + onClick = onInvoiceSelected, + ) + } + + else -> {} + } + } + } + Spacer(Modifier.height(16.dp)) + } +} + +@Composable +private fun PayinMethodRow(title: String, subtitle: String, onClick: () -> Unit, modifier: Modifier = Modifier) { + HedvigCard( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + ) { + Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) { + HedvigText(text = title) + HedvigText( + text = subtitle, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + } +} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt similarity index 83% rename from app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutDestination.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index b73f09e543..831dfa751e 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -1,4 +1,4 @@ -package com.hedvig.android.feature.payoutaccount.ui.setupinvoice +package com.hedvig.android.feature.payin.account.ui.setupinvoice import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically @@ -17,15 +17,18 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard +import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.Surface import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.Res import org.jetbrains.compose.resources.stringResource @Composable -internal fun SetupInvoicePayoutDestination( +internal fun SetupInvoicePayinDestination( viewModel: SetupInvoicePayoutViewModel, globalSnackBarState: GlobalSnackBarState, onSuccessfullyConnected: () -> Unit, @@ -92,3 +95,18 @@ private fun SetupInvoicePayoutScreen( Spacer(Modifier.height(16.dp)) } } + + +@Composable +@HedvigPreview +private fun PreviewPayoutAccountOverviewScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SetupInvoicePayoutScreen( + uiState = SetupInvoicePayoutUiState(false, null), + globalSnackBarState = GlobalSnackBarState(), + {} + ) + } + } +} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayoutViewModel.kt similarity index 97% rename from app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutViewModel.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayoutViewModel.kt index 8b9872ba4f..edcc19e326 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupinvoice/SetupInvoicePayoutViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayoutViewModel.kt @@ -1,4 +1,4 @@ -package com.hedvig.android.feature.payoutaccount.ui.setupinvoice +package com.hedvig.android.feature.payin.account.ui.setupinvoice import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt new file mode 100644 index 0000000000..32ddaf73d8 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -0,0 +1,90 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.feature.payoutaccount.data.SetupSwishPayoutUseCase +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel + +internal class SetupSwishPayinViewModel( + setupSwishPayoutUseCase: SetupSwishPayoutUseCase, +) : MoleculeViewModel( + SetupSwishPayoutUiState(TextFieldState(), false, null, false), + SetupSwishPayoutPresenter(setupSwishPayoutUseCase), + ) + +internal sealed interface SetupSwishPayoutEvent { + data object Save : SetupSwishPayoutEvent + + data object ShowedSnackBar : SetupSwishPayoutEvent +} + +internal data class SetupSwishPayoutUiState( + val phoneNumberState: TextFieldState, + val isLoading: Boolean, + val errorMessage: ErrorMessage?, + val showSuccessSnackBar: Boolean, +) + +internal class SetupSwishPayoutPresenter( + private val setupSwishPayoutUseCase: SetupSwishPayinUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: SetupSwishPayoutUiState, + ): SetupSwishPayoutUiState { + val phoneNumberState = remember { lastState.phoneNumberState } + var isLoading by remember { mutableStateOf(false) } + var errorMessage by remember { mutableStateOf(null) } + var showSuccessSnackBar by remember { mutableStateOf(false) } + var saveIteration by remember { mutableStateOf(null) } + + val currentSave = saveIteration + if (currentSave != null) { + LaunchedEffect(currentSave) { + isLoading = true + errorMessage = null + setupSwishPayoutUseCase.invoke(currentSave).fold( + ifLeft = { + isLoading = false + errorMessage = it + saveIteration = null + }, + ifRight = { + isLoading = false + showSuccessSnackBar = true + saveIteration = null + }, + ) + } + } + + CollectEvents { event -> + when (event) { + SetupSwishPayoutEvent.Save -> { + if (!isLoading) { + saveIteration = phoneNumberState.text.toString() + } + } + + SetupSwishPayoutEvent.ShowedSnackBar -> { + showSuccessSnackBar = false + } + } + } + + return SetupSwishPayoutUiState( + phoneNumberState = phoneNumberState, + isLoading = isLoading, + errorMessage = errorMessage, + showSuccessSnackBar = showSuccessSnackBar, + ) + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt new file mode 100644 index 0000000000..fdf86594f6 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt @@ -0,0 +1,116 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.design.system.hedvig.GlobalSnackBarState +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigNotificationCard +import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigTextField +import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import hedvig.resources.CONTACT_INFO_CHANGES_SAVED +import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL +import hedvig.resources.Res +import hedvig.resources.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION +import hedvig.resources.general_save_button +import hedvig.resources.swish +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun SetupSwishPayinDestination( + viewModel: SetupSwishPayinViewModel, + globalSnackBarState: GlobalSnackBarState, + onSuccessfullyConnected: () -> Unit, + navigateUp: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + SetupSwishPayoutScreen( + uiState = uiState, + globalSnackBarState = globalSnackBarState, + onSave = { viewModel.emit(SetupSwishPayoutEvent.Save) }, + showedSnackBar = { + viewModel.emit(SetupSwishPayoutEvent.ShowedSnackBar) + onSuccessfullyConnected() + }, + navigateUp = navigateUp, + ) +} + +//todo fetch payment methods continuously to see if it already not in pending state + +@Composable +private fun SetupSwishPayoutScreen( + uiState: SetupSwishPayoutUiState, + globalSnackBarState: GlobalSnackBarState, + onSave: () -> Unit, + showedSnackBar: () -> Unit, + navigateUp: () -> Unit, +) { + val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) + LaunchedEffect(uiState.showSuccessSnackBar) { + if (!uiState.showSuccessSnackBar) return@LaunchedEffect + globalSnackBarState.show(changesSaved, NotificationPriority.Campaign) + showedSnackBar() + } + + HedvigScaffold( + topAppBarText = stringResource(Res.string.swish), + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + Spacer(Modifier.weight(1f)) + Column(Modifier.padding(horizontal = 16.dp)) { + HedvigTextField( + state = uiState.phoneNumberState, + labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), + textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone, + ), + modifier = Modifier.fillMaxWidth(), + ) + } + AnimatedVisibility( + visible = uiState.errorMessage != null, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + HedvigNotificationCard( + message = uiState.errorMessage?.message + ?: stringResource(Res.string.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION), + priority = NotificationPriority.Attention, + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 4.dp) + .fillMaxWidth(), + ) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_save_button), + onClick = onSave, + enabled = !uiState.isLoading && uiState.phoneNumberState.text.length >= 10, + isLoading = uiState.isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + } +} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountGraph.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountGraph.kt index 458ff71f26..01c2e1b6d9 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountGraph.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountGraph.kt @@ -11,8 +11,6 @@ import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOvervie import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewViewModel import com.hedvig.android.feature.payoutaccount.ui.selectmethod.SelectPayoutMethodDestination -import com.hedvig.android.feature.payoutaccount.ui.setupinvoice.SetupInvoicePayoutDestination -import com.hedvig.android.feature.payoutaccount.ui.setupinvoice.SetupInvoicePayoutViewModel import com.hedvig.android.feature.payoutaccount.ui.setupswish.SetupSwishPayoutDestination import com.hedvig.android.feature.payoutaccount.ui.setupswish.SetupSwishPayoutViewModel import com.hedvig.android.navigation.compose.navDeepLinks @@ -70,7 +68,6 @@ fun NavGraphBuilder.payoutAccountGraph( }, onNordeaSelected = dropUnlessResumed { navController.navigate(PayoutAccountDestinations.EditBankAccount) }, onSwishSelected = dropUnlessResumed { navController.navigate(PayoutAccountDestinations.SetupSwishPayout) }, - onInvoiceSelected = dropUnlessResumed { navController.navigate(PayoutAccountDestinations.SetupInvoicePayout) }, navigateUp = navController::navigateUp, ) } @@ -98,17 +95,5 @@ fun NavGraphBuilder.payoutAccountGraph( navigateUp = navController::navigateUp, ) } - - navdestination { - val viewModel: SetupInvoicePayoutViewModel = koinViewModel() - SetupInvoicePayoutDestination( - viewModel = viewModel, - globalSnackBarState = globalSnackBarState, - onSuccessfullyConnected = { - navController.typedPopBackStack(inclusive = true) - }, - navigateUp = navController::navigateUp, - ) - } } } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 691e3a936c..aa94ff4d35 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -34,7 +34,6 @@ internal fun SelectPayoutMethodDestination( onTrustlySelected: () -> Unit, onNordeaSelected: () -> Unit, onSwishSelected: () -> Unit, - onInvoiceSelected: () -> Unit, navigateUp: () -> Unit, ) { HedvigScaffold( @@ -70,14 +69,6 @@ internal fun SelectPayoutMethodDestination( ) } - MemberPaymentProvider.INVOICE -> { - PayoutMethodRow( - title = stringResource(Res.string.PAYMENTS_INVOICE), - subtitle = stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION), - onClick = onInvoiceSelected, - ) - } - else -> {} } } diff --git a/hedvig-lint/lint-baseline/lint-baseline-feature-payin-account.xml b/hedvig-lint/lint-baseline/lint-baseline-feature-payin-account.xml new file mode 100644 index 0000000000..a5d7f6b144 --- /dev/null +++ b/hedvig-lint/lint-baseline/lint-baseline-feature-payin-account.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + From c97328790ac06259f443430f1e1948e7f23586a9 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 13:20:29 +0200 Subject: [PATCH 02/75] setup invoice payin --- .../src/main/graphql/GetPayinMethods.graphql | 42 +++++++++++++++++++ .../main/graphql/SetupInvoicePayin.graphql | 2 +- ...UseCase.kt => SetupInvoicePayinUseCase.kt} | 9 ++-- .../payin/account/navigation/PayinNavGraph.kt | 4 +- .../SetupInvoicePayinDestination.kt | 2 +- ...Model.kt => SetupInvoicePayinViewModel.kt} | 16 +++---- .../data/SetupInvoicePayoutUseCase.kt | 5 +-- 7 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql rename app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/{SetupInvoicePayoutUseCase.kt => SetupInvoicePayinUseCase.kt} (81%) rename app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/{SetupInvoicePayoutViewModel.kt => SetupInvoicePayinViewModel.kt} (85%) diff --git a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql new file mode 100644 index 0000000000..eaac18a154 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql @@ -0,0 +1,42 @@ +query GetPayinMethods { + currentMember { + paymentMethods { + payinMethods { + provider + status + isDefault + details { + ... on PaymentMethodBankAccountDetails { + account + bank + } + ... on PaymentMethodSwishDetails { + phoneNumber + } + ... on PaymentMethodInvoiceDetails { + delivery + email + } + } + } + availableMethods { + provider + supportsPayin + isActive + details { + ... on PaymentMethodBankAccountDetails { + account + bank + } + ... on PaymentMethodSwishDetails { + phoneNumber + } + ... on PaymentMethodInvoiceDetails { + delivery + email + } + } + } + } + } +} diff --git a/app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql b/app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql index dd61ac8dcf..cb935fbfd1 100644 --- a/app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql +++ b/app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql @@ -1,4 +1,4 @@ -mutation SetupInvoicePayout { +mutation SetupInvoicePayin { paymentMethodSetupInvoicePayin { status error { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt similarity index 81% rename from app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt index bbceab4868..bfe420a006 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayoutUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt @@ -7,24 +7,23 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.NetworkCacheManager import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage -import octopus.SetupInvoicePayoutMutation -import octopus.type.PaymentMethodInvoiceDelivery +import octopus.SetupInvoicePayinMutation import octopus.type.PaymentMethodSetupStatus -internal class SetupInvoicePayoutUseCase( +internal class SetupInvoicePayinUseCase( private val apolloClient: ApolloClient, private val networkCacheManager: NetworkCacheManager, ) { suspend fun invoke(): Either = either { val result = apolloClient - .mutation(SetupInvoicePayoutMutation()) + .mutation(SetupInvoicePayinMutation()) .safeExecute(::ErrorMessage) .bind() val output = result.paymentMethodSetupInvoicePayin when (output.status) { PaymentMethodSetupStatus.FAILED -> { - raise(ErrorMessage(output.error?.message ?: "Failed to set up invoice payout")) + raise(ErrorMessage(output.error?.message ?: "Failed to set up invoice payin")) } else -> { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index 5826555180..6993cc7c52 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -9,7 +9,7 @@ import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOvervie import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewUiState import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewViewModel import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination -import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayoutViewModel +import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel import com.hedvig.android.navigation.compose.navDeepLinks import com.hedvig.android.navigation.compose.navdestination import com.hedvig.android.navigation.compose.navgraph @@ -83,7 +83,7 @@ fun NavGraphBuilder.payinAccountGraph( } navdestination { - val viewModel: SetupInvoicePayoutViewModel = koinViewModel() + val viewModel: SetupInvoicePayinViewModel = koinViewModel() SetupInvoicePayinDestination( viewModel = viewModel, globalSnackBarState = globalSnackBarState, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index 831dfa751e..29fe46468f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -29,7 +29,7 @@ import org.jetbrains.compose.resources.stringResource @Composable internal fun SetupInvoicePayinDestination( - viewModel: SetupInvoicePayoutViewModel, + viewModel: SetupInvoicePayinViewModel, globalSnackBarState: GlobalSnackBarState, onSuccessfullyConnected: () -> Unit, navigateUp: () -> Unit, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayoutViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt similarity index 85% rename from app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayoutViewModel.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt index edcc19e326..b5ebbbd121 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayoutViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt @@ -7,17 +7,17 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.hedvig.android.feature.payoutaccount.data.SetupInvoicePayoutUseCase +import com.hedvig.android.feature.payin.account.data.SetupInvoicePayinUseCase import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel -internal class SetupInvoicePayoutViewModel( - setupInvoicePayoutUseCase: SetupInvoicePayoutUseCase, +internal class SetupInvoicePayinViewModel( + setupInvoicePayinUseCase: SetupInvoicePayinUseCase, ) : MoleculeViewModel( - SetupInvoicePayoutUiState(false, null, false), - SetupInvoicePayoutPresenter(setupInvoicePayoutUseCase), - ) + SetupInvoicePayoutUiState(false, null, false), + SetupInvoicePayoutPresenter(setupInvoicePayinUseCase), +) internal sealed interface SetupInvoicePayoutEvent { data object Connect : SetupInvoicePayoutEvent @@ -32,7 +32,7 @@ internal data class SetupInvoicePayoutUiState( ) internal class SetupInvoicePayoutPresenter( - private val setupInvoicePayoutUseCase: SetupInvoicePayoutUseCase, + private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( @@ -48,7 +48,7 @@ internal class SetupInvoicePayoutPresenter( LaunchedEffect(connectIteration) { isLoading = true errorMessage = null - setupInvoicePayoutUseCase.invoke().fold( + setupInvoicePayinUseCase.invoke().fold( ifLeft = { isLoading = false errorMessage = it.message ?: "Something went wrong, please try again" diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt index 5502cb9051..a81161d98e 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt @@ -7,8 +7,7 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.NetworkCacheManager import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage -import octopus.SetupInvoicePayoutMutation -import octopus.type.PaymentMethodInvoiceDelivery +import octopus.SetupInvoicePayinMutation import octopus.type.PaymentMethodSetupStatus internal class SetupInvoicePayoutUseCase( @@ -17,7 +16,7 @@ internal class SetupInvoicePayoutUseCase( ) { suspend fun invoke(): Either = either { val result = apolloClient - .mutation(SetupInvoicePayoutMutation()) + .mutation(SetupInvoicePayinMutation()) .safeExecute(::ErrorMessage) .bind() From 4ce872cf423c956f428d1be832684ec1d672a240 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 13:41:27 +0200 Subject: [PATCH 03/75] di and renaming --- app/app/build.gradle.kts | 1 + .../android/app/di/ApplicationModule.kt | 2 + .../account/data/GetPayinAccountUseCase.kt | 105 ++++++++++++++++++ .../account/data/SetupSwishPayinUseCase.kt | 19 ++++ .../payin/account/di/PayinAccountModule.kt | 24 ++++ .../payin/account/navigation/PayinNavGraph.kt | 22 ++-- ....kt => PayinAccountOverviewDestination.kt} | 27 ++--- .../overview/PayinAccountOverviewViewModel.kt | 76 +++++++++++++ .../PayoutAccountOverviewViewModel.kt | 76 ------------- .../SetupInvoicePayinDestination.kt | 8 +- .../ui/setupswish/SetupSwishPayinViewModel.kt | 1 + .../data/SetupInvoicePayoutUseCase.kt | 34 ------ .../payoutaccount/di/PayoutAccountModule.kt | 4 - 13 files changed, 258 insertions(+), 141 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt rename app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/{PayoutAccountOverviewDestination.kt => PayinAccountOverviewDestination.kt} (93%) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt delete mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt delete mode 100644 app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index d6ea697d9e..9476e93990 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -206,6 +206,7 @@ dependencies { implementation(projects.featureMovingflow) implementation(projects.featureRemoveAddons) + implementation(projects.featurePayinAccount) implementation(projects.featurePayoutAccount) implementation(projects.featurePayments) implementation(projects.featureProfile) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt b/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt index ff9b0703c5..1972f0d44c 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt @@ -78,6 +78,7 @@ import com.hedvig.android.feature.insurance.certificate.di.insuranceEvidenceModu import com.hedvig.android.feature.insurances.di.insurancesModule import com.hedvig.android.feature.login.di.loginModule import com.hedvig.android.feature.movingflow.di.movingFlowModule +import com.hedvig.android.feature.payin.account.di.payinAccountModule import com.hedvig.android.feature.payments.di.paymentsModule import com.hedvig.android.feature.payoutaccount.di.payoutAccountModule import com.hedvig.android.feature.profile.di.profileModule @@ -346,6 +347,7 @@ val applicationModule = module { notificationBadgeModule, notificationModule, payoutAccountModule, + payinAccountModule, paymentsModule, profileModule, settingsDatastoreModule, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt new file mode 100644 index 0000000000..593d91f13d --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -0,0 +1,105 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +import arrow.core.raise.either +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.cache.normalized.FetchPolicy +import com.apollographql.apollo.cache.normalized.fetchPolicy +import com.hedvig.android.apollo.ErrorMessage +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.core.common.ErrorMessage +import octopus.GetPayinMethodsQuery +import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodBankAccountDetails +import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodSwishDetails +import octopus.type.MemberPaymentMethodStatus +import octopus.type.MemberPaymentProvider + +internal data class PayinAccountData( + val currentMethod: PayinAccount?, + val availablePayoutMethods: List, +) + +internal class GetPayinAccountUseCase( + private val apolloClient: ApolloClient, +) { + suspend fun invoke(): Either = either { + val result = apolloClient + .query(GetPayinMethodsQuery()) + .fetchPolicy(FetchPolicy.NetworkOnly) + .safeExecute(::ErrorMessage) + .bind() + + val paymentMethods = result.currentMember.paymentMethods + val defaultPayoutMethod = paymentMethods.payinMethods.firstOrNull { it.isDefault } + // todo. Return the other, non-default payin methods when we can switch to them + val currentMethod = defaultPayoutMethod?.let { method -> + val isPending = method.status == MemberPaymentMethodStatus.PENDING + when (method.provider) { + MemberPaymentProvider.SWISH -> { + val phoneNumber = method.details?.asPaymentMethodSwishDetails()?.phoneNumber + PayinAccount.SwishPayin(phoneNumber = phoneNumber, isPending = isPending) + } + + MemberPaymentProvider.TRUSTLY -> { + val (clearingNumber, accountNumber, bankName) = parseBankAccountDetails(method) + PayinAccount.Trustly( + clearingNumber = clearingNumber, + accountNumber = accountNumber, + bankName = bankName, + isPending = isPending, + ) + } + + else -> { + null + } + } + } + + val availablePayoutMethods = paymentMethods.availableMethods + .filter { it.supportsPayin } + .map { it.provider } + + PayinAccountData( + currentMethod = currentMethod, + availablePayoutMethods = availablePayoutMethods, + ) + } +} + +private data class ParsedBankAccountDetails( + val clearingNumber: String?, + val accountNumber: String?, + val bankName: String?, +) + +private fun parseBankAccountDetails( + method: GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod, +): ParsedBankAccountDetails { + val bankAccountDetails = method.details?.asPaymentMethodBankAccountDetails() + val account = bankAccountDetails?.account + val dashIndex = account?.indexOf('-') ?: -1 + val clearingNumber = if (dashIndex >= 0) account?.substring(0, dashIndex) else account + val accountNumber = if (dashIndex >= 0) account?.substring(dashIndex + 1) else null + return ParsedBankAccountDetails( + clearingNumber = clearingNumber, + accountNumber = accountNumber, + bankName = bankAccountDetails?.bank, + ) +} + +internal sealed interface PayinAccount { + val isPending: Boolean + + data class Trustly( + val clearingNumber: String?, + val accountNumber: String?, + val bankName: String?, + override val isPending: Boolean, + ) : PayinAccount + + data class SwishPayin( + val phoneNumber: String?, + override val isPending: Boolean, + ) : PayinAccount +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt new file mode 100644 index 0000000000..f7e0ca5e7a --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -0,0 +1,19 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.NetworkCacheManager +import com.hedvig.android.core.common.ErrorMessage + +internal interface SetupSwishPayinUseCase { + suspend fun invoke(): Either +} + +internal class SetupSwishPayinUseCaseImpl( + private val apolloClient: ApolloClient, + private val networkCacheManager: NetworkCacheManager, +): SetupSwishPayinUseCase { + override suspend fun invoke(): Either { + TODO("Not yet implemented") + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt new file mode 100644 index 0000000000..efab20f540 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt @@ -0,0 +1,24 @@ +package com.hedvig.android.feature.payin.account.di + +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.NetworkCacheManager +import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase +import com.hedvig.android.feature.payin.account.data.SetupInvoicePayinUseCase +import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase +import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCaseImpl +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel +import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel +import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinViewModel +import org.koin.core.module.dsl.viewModel +import org.koin.dsl.module + +val payinAccountModule = module { + single { + SetupSwishPayinUseCaseImpl(get(), get()) + } + single {GetPayinAccountUseCase(get())} + single { SetupInvoicePayinUseCase(get(), get()) } + viewModel { SetupInvoicePayinViewModel(get()) } + viewModel { SetupSwishPayinViewModel(get()) } + viewModel { PayinAccountOverviewViewModel(get()) } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index 6993cc7c52..9ea82a0621 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -5,11 +5,14 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptionsBuilder import com.hedvig.android.design.system.hedvig.GlobalSnackBarState -import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewDestination -import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewUiState -import com.hedvig.android.feature.payin.account.ui.overview.PayoutAccountOverviewViewModel +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewDestination +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel +import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodDestination import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel +import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinDestination +import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinViewModel import com.hedvig.android.navigation.compose.navDeepLinks import com.hedvig.android.navigation.compose.navdestination import com.hedvig.android.navigation.compose.navgraph @@ -31,11 +34,11 @@ fun NavGraphBuilder.payinAccountGraph( deepLinks = navDeepLinks(hedvigDeepLinkContainer.payout), ) { navdestination { - val viewModel: PayoutAccountOverviewViewModel = koinViewModel() - PayoutAccountOverviewDestination( + val viewModel: PayinAccountOverviewViewModel = koinViewModel() + PayinAccountOverviewDestination( viewModel = viewModel, onConnectPayoutMethodClicked = dropUnlessResumed { - val content = viewModel.uiState.value as? PayoutAccountOverviewUiState.Content + val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content navController.navigate( PayinAccountDestinations.SelectPayinMethod( availableProviders = content?.availablePayoutMethods?.map { it.rawValue } ?: emptyList(), @@ -54,7 +57,7 @@ fun NavGraphBuilder.payinAccountGraph( } navdestination { - SelectPayoutMethodDestination( + SelectPayinMethodDestination( availableProviders = this.availableProviders.map { MemberPaymentProvider.safeValueOf(it) }, onTrustlySelected = dropUnlessResumed { navigateToConnectPayment { @@ -63,7 +66,6 @@ fun NavGraphBuilder.payinAccountGraph( } } }, - onNordeaSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.EditBankAccount) }, onSwishSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupSwishPayin) }, onInvoiceSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupInvoicePayin) }, navigateUp = navController::navigateUp, @@ -71,8 +73,8 @@ fun NavGraphBuilder.payinAccountGraph( } navdestination { - val viewModel: SetupSwishPayoutViewModel = koinViewModel() - SetupSwishPayoutDestination( + val viewModel: SetupSwishPayinViewModel = koinViewModel() + SetupSwishPayinDestination( viewModel = viewModel, globalSnackBarState = globalSnackBarState, onSuccessfullyConnected = { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt similarity index 93% rename from app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index ee8c3407c8..70d0d87bf2 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -30,8 +29,6 @@ import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.feature.payoutaccount.data.PayoutAccount -import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Content import hedvig.resources.CHANGE_PAYOUT_METHOD_BUTTON_LABEL import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE import hedvig.resources.PAYMENTS_ACCOUNT @@ -51,8 +48,8 @@ import octopus.type.PaymentMethodInvoiceDelivery import org.jetbrains.compose.resources.stringResource @Composable -internal fun PayoutAccountOverviewDestination( - viewModel: PayoutAccountOverviewViewModel, +internal fun PayinAccountOverviewDestination( + viewModel: PayinAccountOverviewViewModel, onConnectPayoutMethodClicked: () -> Unit, navigateToConnectPayment: () -> Unit, navigateUp: () -> Unit, @@ -62,14 +59,14 @@ internal fun PayoutAccountOverviewDestination( uiState = uiState, onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, navigateToConnectPayment = navigateToConnectPayment, - onRetry = { viewModel.emit(PayoutAccountOverviewEvent.Retry) }, + onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, navigateUp = navigateUp, ) } @Composable private fun PayoutAccountOverviewScreen( - uiState: PayoutAccountOverviewUiState, + uiState: PayinAccountOverviewUiState, onConnectPayoutMethodClicked: () -> Unit, navigateToConnectPayment: () -> Unit, onRetry: () -> Unit, @@ -81,7 +78,7 @@ private fun PayoutAccountOverviewScreen( modifier = Modifier.fillMaxSize(), ) { when (uiState) { - PayoutAccountOverviewUiState.Loading -> { + PayinAccountOverviewUiState.Loading -> { HedvigFullScreenCenterAlignedProgressDebounced( Modifier .weight(1f) @@ -89,7 +86,7 @@ private fun PayoutAccountOverviewScreen( ) } - PayoutAccountOverviewUiState.Error -> { + PayinAccountOverviewUiState.Error -> { HedvigErrorSection( onButtonClick = onRetry, modifier = Modifier @@ -98,7 +95,7 @@ private fun PayoutAccountOverviewScreen( ) } - PayoutAccountOverviewUiState.NoPayoutOptions -> { + PayinAccountOverviewUiState.NoPayinOptions -> { HedvigInformationSection( title = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_TITLE), subTitle = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE), @@ -244,7 +241,7 @@ private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: Stri @Composable @HedvigPreview private fun PreviewPayoutAccountOverviewScreen( - @PreviewParameter(PayoutAccountOverviewUiStateProvider::class) uiState: PayoutAccountOverviewUiState, + @PreviewParameter(PayoutAccountOverviewUiStateProvider::class) uiState: PayinAccountOverviewUiState, ) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { @@ -259,11 +256,11 @@ private fun PreviewPayoutAccountOverviewScreen( } } -private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( +private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( listOf( - PayoutAccountOverviewUiState.Loading, - PayoutAccountOverviewUiState.Error, - PayoutAccountOverviewUiState.NoPayoutOptions, + PayinAccountOverviewUiState.Loading, + PayinAccountOverviewUiState.Error, + PayinAccountOverviewUiState.NoPayinOptions, Content( currentMethod = null, availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt new file mode 100644 index 0000000000..cfd2781fbf --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -0,0 +1,76 @@ +package com.hedvig.android.feature.payin.account.ui.overview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import octopus.type.MemberPaymentProvider + +internal class PayinAccountOverviewViewModel( + getPayinAccountUseCase: GetPayinAccountUseCase, +) : MoleculeViewModel( + PayinAccountOverviewUiState.Loading, + PayinAccountOverviewPresenter(getPayinAccountUseCase), +) + +internal sealed interface PayinAccountOverviewEvent { + data object Retry : PayinAccountOverviewEvent +} + +internal sealed interface PayinAccountOverviewUiState { + data object Loading : PayinAccountOverviewUiState + + data object Error : PayinAccountOverviewUiState + + data object NoPayinOptions : PayinAccountOverviewUiState + + data class Content( + val currentMethod: PayinAccount?, + val availablePayoutMethods: List, + ) : PayinAccountOverviewUiState +} + +internal class PayinAccountOverviewPresenter( + private val getPayinAccountUseCase: GetPayinAccountUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: PayinAccountOverviewUiState, + ): PayinAccountOverviewUiState { + var loadIteration by remember { mutableIntStateOf(0) } + var uiState by remember { mutableStateOf(lastState) } + + LaunchedEffect(loadIteration) { + uiState = PayinAccountOverviewUiState.Loading + getPayinAccountUseCase.invoke().fold( + ifLeft = { uiState = PayinAccountOverviewUiState.Error }, + ifRight = { data -> + uiState = if (data.currentMethod == null && data.availablePayoutMethods.isEmpty()) { + PayinAccountOverviewUiState.NoPayinOptions + } else { + PayinAccountOverviewUiState.Content( + currentMethod = data.currentMethod, + availablePayoutMethods = data.availablePayoutMethods, + ) + } + }, + ) + } + + CollectEvents { event -> + when (event) { + PayinAccountOverviewEvent.Retry -> loadIteration++ + } + } + + return uiState + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt deleted file mode 100644 index b0b07e1299..0000000000 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayoutAccountOverviewViewModel.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.hedvig.android.feature.payin.account.ui.overview - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import com.hedvig.android.feature.payoutaccount.data.GetPayoutAccountUseCase -import com.hedvig.android.feature.payoutaccount.data.PayoutAccount -import com.hedvig.android.molecule.public.MoleculePresenter -import com.hedvig.android.molecule.public.MoleculePresenterScope -import com.hedvig.android.molecule.public.MoleculeViewModel -import octopus.type.MemberPaymentProvider - -internal class PayoutAccountOverviewViewModel( - getPayoutAccountUseCase: GetPayoutAccountUseCase, -) : MoleculeViewModel( - PayoutAccountOverviewUiState.Loading, - PayoutAccountOverviewPresenter(getPayoutAccountUseCase), - ) - -internal sealed interface PayoutAccountOverviewEvent { - data object Retry : PayoutAccountOverviewEvent -} - -internal sealed interface PayoutAccountOverviewUiState { - data object Loading : PayoutAccountOverviewUiState - - data object Error : PayoutAccountOverviewUiState - - data object NoPayoutOptions : PayoutAccountOverviewUiState - - data class Content( - val currentMethod: PayoutAccount?, - val availablePayoutMethods: List, - ) : PayoutAccountOverviewUiState -} - -internal class PayoutAccountOverviewPresenter( - private val getPayoutAccountUseCase: GetPayoutAccountUseCase, -) : MoleculePresenter { - @Composable - override fun MoleculePresenterScope.present( - lastState: PayoutAccountOverviewUiState, - ): PayoutAccountOverviewUiState { - var loadIteration by remember { mutableIntStateOf(0) } - var uiState by remember { mutableStateOf(lastState) } - - LaunchedEffect(loadIteration) { - uiState = PayoutAccountOverviewUiState.Loading - getPayoutAccountUseCase.invoke().fold( - ifLeft = { uiState = PayoutAccountOverviewUiState.Error }, - ifRight = { data -> - uiState = if (data.currentMethod == null && data.availablePayoutMethods.isEmpty()) { - PayoutAccountOverviewUiState.NoPayoutOptions - } else { - PayoutAccountOverviewUiState.Content( - currentMethod = data.currentMethod, - availablePayoutMethods = data.availablePayoutMethods, - ) - } - }, - ) - } - - CollectEvents { event -> - when (event) { - PayoutAccountOverviewEvent.Retry -> loadIteration++ - } - } - - return uiState - } -} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index 29fe46468f..82e2ec00ce 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -103,9 +103,13 @@ private fun PreviewPayoutAccountOverviewScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { SetupInvoicePayoutScreen( - uiState = SetupInvoicePayoutUiState(false, null), + uiState = SetupInvoicePayoutUiState( + false, + null, + showSuccessSnackBar = false + ), globalSnackBarState = GlobalSnackBarState(), - {} + {}, {} ,{} ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt index 32ddaf73d8..dc942c0abc 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase import com.hedvig.android.feature.payoutaccount.data.SetupSwishPayoutUseCase import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt deleted file mode 100644 index a81161d98e..0000000000 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.hedvig.android.feature.payoutaccount.data - -import arrow.core.Either -import arrow.core.raise.either -import com.apollographql.apollo.ApolloClient -import com.hedvig.android.apollo.ErrorMessage -import com.hedvig.android.apollo.NetworkCacheManager -import com.hedvig.android.apollo.safeExecute -import com.hedvig.android.core.common.ErrorMessage -import octopus.SetupInvoicePayinMutation -import octopus.type.PaymentMethodSetupStatus - -internal class SetupInvoicePayoutUseCase( - private val apolloClient: ApolloClient, - private val networkCacheManager: NetworkCacheManager, -) { - suspend fun invoke(): Either = either { - val result = apolloClient - .mutation(SetupInvoicePayinMutation()) - .safeExecute(::ErrorMessage) - .bind() - - val output = result.paymentMethodSetupInvoicePayin - when (output.status) { - PaymentMethodSetupStatus.FAILED -> { - raise(ErrorMessage(output.error?.message ?: "Failed to set up invoice payout")) - } - - else -> { - networkCacheManager.clearCache() - } - } - } -} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/di/PayoutAccountModule.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/di/PayoutAccountModule.kt index b7d1f52094..580b17fe9b 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/di/PayoutAccountModule.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/di/PayoutAccountModule.kt @@ -3,12 +3,10 @@ package com.hedvig.android.feature.payoutaccount.di import com.apollographql.apollo.ApolloClient import com.hedvig.android.apollo.NetworkCacheManager import com.hedvig.android.feature.payoutaccount.data.GetPayoutAccountUseCase -import com.hedvig.android.feature.payoutaccount.data.SetupInvoicePayoutUseCase import com.hedvig.android.feature.payoutaccount.data.SetupNordeaPayoutUseCase import com.hedvig.android.feature.payoutaccount.data.SetupSwishPayoutUseCase import com.hedvig.android.feature.payoutaccount.ui.editbankaccount.EditBankAccountViewModel import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewViewModel -import com.hedvig.android.feature.payoutaccount.ui.setupinvoice.SetupInvoicePayoutViewModel import com.hedvig.android.feature.payoutaccount.ui.setupswish.SetupSwishPayoutViewModel import org.koin.core.module.dsl.viewModel import org.koin.dsl.module @@ -17,9 +15,7 @@ val payoutAccountModule = module { single { GetPayoutAccountUseCase(get()) } single { SetupNordeaPayoutUseCase(get(), get()) } single { SetupSwishPayoutUseCase(get(), get()) } - single { SetupInvoicePayoutUseCase(get(), get()) } viewModel { PayoutAccountOverviewViewModel(get()) } viewModel { EditBankAccountViewModel(get()) } viewModel { SetupSwishPayoutViewModel(get()) } - viewModel { SetupInvoicePayoutViewModel(get()) } } From 3e9a3d1dbdaca6c3c53021046afa215c3c6dab4c Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 14:18:37 +0200 Subject: [PATCH 04/75] PayinAccountOverviewDestination --- .../account/data/GetPayinAccountUseCase.kt | 21 ++- .../PayinAccountOverviewDestination.kt | 159 +++++------------- .../overview/PayinAccountOverviewViewModel.kt | 11 +- .../SetupInvoicePayinDestination.kt | 19 ++- .../SetupInvoicePayinViewModel.kt | 18 +- ...ation.kt => SetupSwishPayinDestination.kt} | 25 +++ .../ui/setupswish/SetupSwishPayinViewModel.kt | 11 +- .../data/GetPayoutAccountUseCase.kt | 10 -- .../payoutaccount/data/PayoutAccount.kt | 7 - .../PayoutAccountOverviewDestination.kt | 23 --- 10 files changed, 112 insertions(+), 192 deletions(-) rename app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/{SetupSwishPayoutDestination.kt => SetupSwishPayinDestination.kt} (85%) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 593d91f13d..333f3fa145 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -10,13 +10,15 @@ import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import octopus.GetPayinMethodsQuery import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodBankAccountDetails +import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodInvoiceDetails import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodSwishDetails import octopus.type.MemberPaymentMethodStatus import octopus.type.MemberPaymentProvider +import octopus.type.PaymentMethodInvoiceDelivery internal data class PayinAccountData( val currentMethod: PayinAccount?, - val availablePayoutMethods: List, + val availablePayinMethods: List, ) internal class GetPayinAccountUseCase( @@ -50,6 +52,15 @@ internal class GetPayinAccountUseCase( ) } + MemberPaymentProvider.INVOICE -> { + val invoiceDetails = method.details?.asPaymentMethodInvoiceDetails() + PayinAccount.Invoice( + delivery = invoiceDetails?.delivery, + email = invoiceDetails?.email, + isPending = isPending, + ) + } + else -> { null } @@ -62,7 +73,7 @@ internal class GetPayinAccountUseCase( PayinAccountData( currentMethod = currentMethod, - availablePayoutMethods = availablePayoutMethods, + availablePayinMethods = availablePayoutMethods, ) } } @@ -102,4 +113,10 @@ internal sealed interface PayinAccount { val phoneNumber: String?, override val isPending: Boolean, ) : PayinAccount + + data class Invoice( + val delivery: PaymentMethodInvoiceDelivery?, + val email: String?, + override val isPending: Boolean, + ) : PayinAccount } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 70d0d87bf2..d27d4e902f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -20,7 +20,6 @@ import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced -import com.hedvig.android.design.system.hedvig.HedvigInformationSection import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold @@ -29,51 +28,40 @@ import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface -import hedvig.resources.CHANGE_PAYOUT_METHOD_BUTTON_LABEL -import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE +import com.hedvig.android.feature.payin.account.data.PayinAccount import hedvig.resources.PAYMENTS_ACCOUNT import hedvig.resources.PAYMENTS_INVOICE -import hedvig.resources.PAYOUT_MISSING_INFO -import hedvig.resources.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE -import hedvig.resources.PAYOUT_NO_PAYOUT_OPTIONS_TITLE -import hedvig.resources.PAYOUT_PAGE_HEADING -import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD -import hedvig.resources.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res import hedvig.resources.swish import hedvig.resources.trustly import octopus.type.MemberPaymentProvider -import octopus.type.PaymentMethodInvoiceDelivery import org.jetbrains.compose.resources.stringResource @Composable internal fun PayinAccountOverviewDestination( viewModel: PayinAccountOverviewViewModel, onConnectPayoutMethodClicked: () -> Unit, - navigateToConnectPayment: () -> Unit, navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - PayoutAccountOverviewScreen( + PayinAccountOverviewScreen( uiState = uiState, onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, - navigateToConnectPayment = navigateToConnectPayment, onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, navigateUp = navigateUp, ) } @Composable -private fun PayoutAccountOverviewScreen( +private fun PayinAccountOverviewScreen( uiState: PayinAccountOverviewUiState, onConnectPayoutMethodClicked: () -> Unit, - navigateToConnectPayment: () -> Unit, onRetry: () -> Unit, navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = stringResource(Res.string.PAYOUT_PAGE_HEADING), + topAppBarText = "Billing account", //todo! navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -95,23 +83,11 @@ private fun PayoutAccountOverviewScreen( ) } - PayinAccountOverviewUiState.NoPayinOptions -> { - HedvigInformationSection( - title = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_TITLE), - subTitle = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE), - buttonText = stringResource(Res.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON), - onButtonClick = navigateToConnectPayment, - modifier = Modifier - .weight(1f) - .wrapContentHeight(), - ) - } - - is Content -> { + is PayinAccountOverviewUiState.Content -> { PayoutAccountContent( currentMethod = uiState.currentMethod, - availablePayoutMethods = uiState.availablePayoutMethods, - onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, + availablePayinMethods = uiState.availablePayoutMethods, + onConnectPayinMethodClicked = onConnectPayoutMethodClicked, modifier = Modifier.weight(1f), ) } @@ -121,28 +97,28 @@ private fun PayoutAccountOverviewScreen( @Composable private fun PayoutAccountContent( - currentMethod: PayoutAccount?, - availablePayoutMethods: List, - onConnectPayoutMethodClicked: () -> Unit, + currentMethod: PayinAccount?, + availablePayinMethods: List, + onConnectPayinMethodClicked: () -> Unit, modifier: Modifier = Modifier, ) { Column(modifier) { Spacer(Modifier.height(8.dp)) when (currentMethod) { null -> { - if (availablePayoutMethods.isNotEmpty()) { + if (availablePayinMethods.isNotEmpty()) { Spacer(Modifier.weight(1f)) EmptyState( - text = stringResource(Res.string.PAYOUT_MISSING_INFO), + text = "You haven’t added a billing method yet. Add one to pay for your insurance.", //todo description = null, iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, ) } } - is PayoutAccount.SwishPayout -> { + is PayinAccount.SwishPayin -> { val phoneNumber = currentMethod.phoneNumber.orEmpty() - PayoutAccountReadOnlyTextField( + PayinAccountReadOnlyTextField( label = stringResource(Res.string.swish), text = if (currentMethod.isPending && phoneNumber.isBlank()) { stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) @@ -152,9 +128,9 @@ private fun PayoutAccountContent( ) } - is PayoutAccount.Trustly -> { + is PayinAccount.Trustly -> { val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayoutAccountReadOnlyTextField( + PayinAccountReadOnlyTextField( label = formatBankAccountLabel(stringResource(Res.string.trustly), currentMethod.bankName), text = if (currentMethod.isPending && accountNumber.isBlank()) { stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) @@ -164,44 +140,32 @@ private fun PayoutAccountContent( ) } - is PayoutAccount.Invoice -> { - PayoutAccountReadOnlyTextField( + is PayinAccount.Invoice -> { + PayinAccountReadOnlyTextField( stringResource(Res.string.PAYMENTS_ACCOUNT), stringResource(Res.string.PAYMENTS_INVOICE), ) } - - is PayoutAccount.BankAccount -> { - val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayoutAccountReadOnlyTextField( - label = formatBankAccountLabel(stringResource(Res.string.PAYMENTS_ACCOUNT), currentMethod.bankName), - text = if (currentMethod.isPending && accountNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) - } else { - accountNumber - }, - ) - } } Spacer(Modifier.weight(1f)) Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { if (currentMethod?.isPending == true) { HedvigNotificationCard( - message = stringResource(Res.string.MY_PAYMENT_UPDATING_MESSAGE), + message = "You have just added or changed your billing method, it will appear here soon.", //todo priority = NotificationPriority.Info, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), ) } - if (availablePayoutMethods.isNotEmpty()) { + if (availablePayinMethods.isNotEmpty()) { HedvigButton( text = if (currentMethod == null) { - stringResource(Res.string.PAYOUT_SELECT_PAYOUT_METHOD) + "Select billing method" //todo! } else { - stringResource(Res.string.CHANGE_PAYOUT_METHOD_BUTTON_LABEL) + "Change billing method" //todo! }, - onClick = onConnectPayoutMethodClicked, + onClick = onConnectPayinMethodClicked, enabled = true, modifier = Modifier .fillMaxWidth() @@ -214,7 +178,7 @@ private fun PayoutAccountContent( } @Composable -private fun PayoutAccountReadOnlyTextField(label: String, text: String, modifier: Modifier = Modifier) { +private fun PayinAccountReadOnlyTextField(label: String, text: String, modifier: Modifier = Modifier) { HedvigTextField( text = text, onValueChange = {}, @@ -240,15 +204,14 @@ private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: Stri @Composable @HedvigPreview -private fun PreviewPayoutAccountOverviewScreen( - @PreviewParameter(PayoutAccountOverviewUiStateProvider::class) uiState: PayinAccountOverviewUiState, +private fun PreviewPayinAccountOverviewScreen( + @PreviewParameter(PayinAccountOverviewUiStateProvider::class) uiState: PayinAccountOverviewUiState, ) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - PayoutAccountOverviewScreen( + PayinAccountOverviewScreen( uiState = uiState, onConnectPayoutMethodClicked = {}, - navigateToConnectPayment = {}, onRetry = {}, navigateUp = {}, ) @@ -256,33 +219,32 @@ private fun PreviewPayoutAccountOverviewScreen( } } -private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( +private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( listOf( PayinAccountOverviewUiState.Loading, PayinAccountOverviewUiState.Error, - PayinAccountOverviewUiState.NoPayinOptions, - Content( + PayinAccountOverviewUiState.Content( currentMethod = null, availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), ), - Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = false), + PayinAccountOverviewUiState.Content( + currentMethod = PayinAccount.SwishPayin(phoneNumber = "070-123 45 67", isPending = false), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), ), - Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = false), + PayinAccountOverviewUiState.Content( + currentMethod = PayinAccount.SwishPayin(phoneNumber = "070-123 45 67", isPending = false), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), ), - Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = null, isPending = true), + PayinAccountOverviewUiState.Content( + currentMethod = PayinAccount.SwishPayin(phoneNumber = null, isPending = true), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), ), - Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = true), + PayinAccountOverviewUiState.Content( + currentMethod = PayinAccount.SwishPayin(phoneNumber = "070-123 45 67", isPending = true), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), ), - Content( - currentMethod = PayoutAccount.Trustly( + PayinAccountOverviewUiState.Content( + currentMethod = PayinAccount.Trustly( clearingNumber = "8327", accountNumber = "12345678", bankName = "Mock Swedbank", @@ -290,48 +252,5 @@ private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterP ), availablePayoutMethods = listOf(MemberPaymentProvider.TRUSTLY), ), - Content( - currentMethod = PayoutAccount.BankAccount( - clearingNumber = "3300", - accountNumber = "1234567", - bankName = "Nordea", - isPending = false, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), - ), - Content( - currentMethod = PayoutAccount.BankAccount( - clearingNumber = null, - accountNumber = null, - bankName = null, - isPending = true, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), - ), - Content( - currentMethod = PayoutAccount.BankAccount( - clearingNumber = "3300", - accountNumber = "1234567", - bankName = "Nordea", - isPending = true, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), - ), - Content( - currentMethod = PayoutAccount.Invoice( - delivery = PaymentMethodInvoiceDelivery.KIVRA, - email = null, - isPending = false, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.INVOICE), - ), - Content( - currentMethod = PayoutAccount.Invoice( - delivery = PaymentMethodInvoiceDelivery.MAIL, - email = "user@example.com", - isPending = false, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.INVOICE, MemberPaymentProvider.TRUSTLY), - ), ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index cfd2781fbf..94427835f3 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -30,8 +30,6 @@ internal sealed interface PayinAccountOverviewUiState { data object Error : PayinAccountOverviewUiState - data object NoPayinOptions : PayinAccountOverviewUiState - data class Content( val currentMethod: PayinAccount?, val availablePayoutMethods: List, @@ -53,14 +51,11 @@ internal class PayinAccountOverviewPresenter( getPayinAccountUseCase.invoke().fold( ifLeft = { uiState = PayinAccountOverviewUiState.Error }, ifRight = { data -> - uiState = if (data.currentMethod == null && data.availablePayoutMethods.isEmpty()) { - PayinAccountOverviewUiState.NoPayinOptions - } else { - PayinAccountOverviewUiState.Content( + uiState = PayinAccountOverviewUiState.Content( currentMethod = data.currentMethod, - availablePayoutMethods = data.availablePayoutMethods, + availablePayoutMethods = data.availablePayinMethods, ) - } + }, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index 82e2ec00ce..680955d469 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -3,6 +3,7 @@ package com.hedvig.android.feature.payin.account.ui.setupinvoice import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -19,6 +20,7 @@ import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface @@ -35,7 +37,7 @@ internal fun SetupInvoicePayinDestination( navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - SetupInvoicePayoutScreen( + SetupInvoicePayinScreen( uiState = uiState, globalSnackBarState = globalSnackBarState, onConnect = { viewModel.emit(SetupInvoicePayoutEvent.Connect) }, @@ -48,8 +50,8 @@ internal fun SetupInvoicePayinDestination( } @Composable -private fun SetupInvoicePayoutScreen( - uiState: SetupInvoicePayoutUiState, +private fun SetupInvoicePayinScreen( + uiState: SetupInvoicePayinUiState, globalSnackBarState: GlobalSnackBarState, onConnect: () -> Unit, showedSnackBar: () -> Unit, @@ -67,7 +69,10 @@ private fun SetupInvoicePayoutScreen( navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { - Spacer(Modifier.weight(1f)) + //todo: some text here?? + Column (Modifier.weight(1f)) { + HedvigText("You can choose invoice as you pay-in") + } AnimatedVisibility( visible = uiState.errorMessage != null, enter = expandVertically(), @@ -84,7 +89,7 @@ private fun SetupInvoicePayoutScreen( } Spacer(Modifier.height(16.dp)) HedvigButton( - text = "Connect", + text = "Connect", //todo onClick = onConnect, enabled = !uiState.isLoading, isLoading = uiState.isLoading, @@ -102,8 +107,8 @@ private fun SetupInvoicePayoutScreen( private fun PreviewPayoutAccountOverviewScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - SetupInvoicePayoutScreen( - uiState = SetupInvoicePayoutUiState( + SetupInvoicePayinScreen( + uiState = SetupInvoicePayinUiState( false, null, showSuccessSnackBar = false diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt index b5ebbbd121..c5364c5f65 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt @@ -14,9 +14,9 @@ import com.hedvig.android.molecule.public.MoleculeViewModel internal class SetupInvoicePayinViewModel( setupInvoicePayinUseCase: SetupInvoicePayinUseCase, -) : MoleculeViewModel( - SetupInvoicePayoutUiState(false, null, false), - SetupInvoicePayoutPresenter(setupInvoicePayinUseCase), +) : MoleculeViewModel( + SetupInvoicePayinUiState(false, null, false), + SetupInvoicePayinPresenter(setupInvoicePayinUseCase), ) internal sealed interface SetupInvoicePayoutEvent { @@ -25,19 +25,19 @@ internal sealed interface SetupInvoicePayoutEvent { data object ShowedSnackBar : SetupInvoicePayoutEvent } -internal data class SetupInvoicePayoutUiState( +internal data class SetupInvoicePayinUiState( val isLoading: Boolean, val errorMessage: String?, val showSuccessSnackBar: Boolean, ) -internal class SetupInvoicePayoutPresenter( +internal class SetupInvoicePayinPresenter( private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, -) : MoleculePresenter { +) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( - lastState: SetupInvoicePayoutUiState, - ): SetupInvoicePayoutUiState { + lastState: SetupInvoicePayinUiState, + ): SetupInvoicePayinUiState { var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } var showSuccessSnackBar by remember { mutableStateOf(false) } @@ -78,7 +78,7 @@ internal class SetupInvoicePayoutPresenter( } } - return SetupInvoicePayoutUiState( + return SetupInvoicePayinUiState( isLoading = isLoading, errorMessage = errorMessage, showSuccessSnackBar = showSuccessSnackBar, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt similarity index 85% rename from app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index fdf86594f6..4bee2c08d6 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayoutDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -20,10 +21,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard +import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults +import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.Surface import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL import hedvig.resources.Res @@ -114,3 +118,24 @@ private fun SetupSwishPayoutScreen( Spacer(Modifier.height(16.dp)) } } + +@Composable +@HedvigPreview +private fun PreviewSetupSwishPayinScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SetupSwishPayoutScreen( + uiState = SetupSwishPayoutUiState( + phoneNumberState = TextFieldState(), + isLoading = false, + errorMessage = null, + showSuccessSnackBar = false, + ), + globalSnackBarState = GlobalSnackBarState(), + onSave = {}, + showedSnackBar = {}, + navigateUp = {}, + ) + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt index dc942c0abc..a365317fa4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -9,17 +9,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase -import com.hedvig.android.feature.payoutaccount.data.SetupSwishPayoutUseCase import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel internal class SetupSwishPayinViewModel( - setupSwishPayoutUseCase: SetupSwishPayoutUseCase, + setupSwishPayoutUseCase: SetupSwishPayinUseCase, ) : MoleculeViewModel( - SetupSwishPayoutUiState(TextFieldState(), false, null, false), - SetupSwishPayoutPresenter(setupSwishPayoutUseCase), - ) + SetupSwishPayoutUiState(TextFieldState(), false, null, false), + SetupSwishPayoutPresenter(setupSwishPayoutUseCase), +) internal sealed interface SetupSwishPayoutEvent { data object Save : SetupSwishPayoutEvent @@ -52,7 +51,7 @@ internal class SetupSwishPayoutPresenter( LaunchedEffect(currentSave) { isLoading = true errorMessage = null - setupSwishPayoutUseCase.invoke(currentSave).fold( + setupSwishPayoutUseCase.invoke().fold( ifLeft = { isLoading = false errorMessage = it diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/GetPayoutAccountUseCase.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/GetPayoutAccountUseCase.kt index 3ad7b40f77..79373b55ce 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/GetPayoutAccountUseCase.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/GetPayoutAccountUseCase.kt @@ -10,7 +10,6 @@ import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import octopus.GetPayoutMethodsQuery import octopus.GetPayoutMethodsQuery.Data.CurrentMember.PaymentMethods.PayoutMethod.Details.Companion.asPaymentMethodBankAccountDetails -import octopus.GetPayoutMethodsQuery.Data.CurrentMember.PaymentMethods.PayoutMethod.Details.Companion.asPaymentMethodInvoiceDetails import octopus.GetPayoutMethodsQuery.Data.CurrentMember.PaymentMethods.PayoutMethod.Details.Companion.asPaymentMethodSwishDetails import octopus.type.MemberPaymentMethodStatus import octopus.type.MemberPaymentProvider @@ -41,15 +40,6 @@ internal class GetPayoutAccountUseCase( PayoutAccount.SwishPayout(phoneNumber = phoneNumber, isPending = isPending) } - MemberPaymentProvider.INVOICE -> { - val invoiceDetails = method.details?.asPaymentMethodInvoiceDetails() - PayoutAccount.Invoice( - delivery = invoiceDetails?.delivery, - email = invoiceDetails?.email, - isPending = isPending, - ) - } - MemberPaymentProvider.TRUSTLY -> { val (clearingNumber, accountNumber, bankName) = parseBankAccountDetails(method) PayoutAccount.Trustly( diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt index 8a7b7b3bdc..a947a55ab0 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt @@ -1,6 +1,5 @@ package com.hedvig.android.feature.payoutaccount.data -import octopus.type.PaymentMethodInvoiceDelivery internal sealed interface PayoutAccount { val isPending: Boolean @@ -23,10 +22,4 @@ internal sealed interface PayoutAccount { val bankName: String?, override val isPending: Boolean, ) : PayoutAccount - - data class Invoice( - val delivery: PaymentMethodInvoiceDelivery?, - val email: String?, - override val isPending: Boolean, - ) : PayoutAccount } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt index 859fd97808..d3f2870caf 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt @@ -167,13 +167,6 @@ private fun PayoutAccountContent( ) } - is PayoutAccount.Invoice -> { - PayoutAccountReadOnlyTextField( - stringResource(Res.string.PAYMENTS_ACCOUNT), - stringResource(Res.string.PAYMENTS_INVOICE), - ) - } - is PayoutAccount.BankAccount -> { val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) PayoutAccountReadOnlyTextField( @@ -320,21 +313,5 @@ private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterP ), availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), ), - Content( - currentMethod = PayoutAccount.Invoice( - delivery = PaymentMethodInvoiceDelivery.KIVRA, - email = null, - isPending = false, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.INVOICE), - ), - Content( - currentMethod = PayoutAccount.Invoice( - delivery = PaymentMethodInvoiceDelivery.MAIL, - email = "user@example.com", - isPending = false, - ), - availablePayoutMethods = listOf(MemberPaymentProvider.INVOICE, MemberPaymentProvider.TRUSTLY), - ), ), ) From fb10cc910aa8c005172bf2d10ba3eb6bbdc35fe5 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 14:39:31 +0200 Subject: [PATCH 05/75] navgraph change --- .../feature/payin/account/navigation/PayinNavGraph.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index 9ea82a0621..ccf5914d3f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -45,13 +45,6 @@ fun NavGraphBuilder.payinAccountGraph( ), ) }, - navigateToConnectPayment = dropUnlessResumed { - navigateToConnectPayment { - typedPopUpTo { - inclusive = true - } - } - }, navigateUp = navigateUp, ) } From d0d5a423cf5eb434220d75f56ba30b70ef442c21 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 15:41:55 +0200 Subject: [PATCH 06/75] active methods first iteration --- .../android/app/navigation/HedvigNavHost.kt | 12 + .../account/data/GetPayinAccountUseCase.kt | 25 +- .../payin/account/navigation/PayinNavGraph.kt | 17 +- .../PayinAccountOverviewDestination.kt | 236 +++++++++++++----- .../overview/PayinAccountOverviewViewModel.kt | 8 +- .../SelectPayinMethodDestination.kt | 27 +- .../SetupInvoicePayinDestination.kt | 22 +- 7 files changed, 259 insertions(+), 88 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index aff77558a5..d9d6762d11 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -68,6 +68,8 @@ import com.hedvig.android.feature.insurances.navigation.insuranceGraph import com.hedvig.android.feature.login.navigation.loginGraph import com.hedvig.android.feature.movingflow.SelectContractForMoving import com.hedvig.android.feature.movingflow.movingFlowGraph +import com.hedvig.android.feature.payin.account.navigation.PayinAccountDestination +import com.hedvig.android.feature.payin.account.navigation.payinAccountGraph import com.hedvig.android.feature.payments.navigation.paymentsGraph import com.hedvig.android.feature.payoutaccount.navigation.PayoutAccountDestination import com.hedvig.android.feature.payoutaccount.navigation.payoutAccountGraph @@ -110,6 +112,9 @@ internal fun HedvigNavHost( val navController = hedvigAppState.navController fun navigateToConnectPayment(builder: NavOptionsBuilder.() -> Unit = {}) { + navController.navigate(PayinAccountDestination.Graph, builder) + } + fun navigateToConnectTrustly(builder: NavOptionsBuilder.() -> Unit = {}) { navController.navigate(TrustlyDestination, builder) } val navigateToInbox = { @@ -357,6 +362,13 @@ internal fun HedvigNavHost( navigateToConnectPayment = ::navigateToConnectPayment, navigateUp = navController::navigateUp, ) + payinAccountGraph( + navController = navController, + globalSnackBarState = globalSnackBarState, + hedvigDeepLinkContainer = hedvigDeepLinkContainer, + navigateToConnectTrustly = ::navigateToConnectTrustly, + navigateUp = navController::navigateUp, + ) profileGraph( settingsDestinationNestedGraphs = { deleteAccountGraph(hedvigDeepLinkContainer, navController) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 333f3fa145..a52a637ff2 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -17,7 +17,7 @@ import octopus.type.MemberPaymentProvider import octopus.type.PaymentMethodInvoiceDelivery internal data class PayinAccountData( - val currentMethod: PayinAccount?, + val currentMethods: List, val availablePayinMethods: List, ) @@ -32,14 +32,17 @@ internal class GetPayinAccountUseCase( .bind() val paymentMethods = result.currentMember.paymentMethods - val defaultPayoutMethod = paymentMethods.payinMethods.firstOrNull { it.isDefault } - // todo. Return the other, non-default payin methods when we can switch to them - val currentMethod = defaultPayoutMethod?.let { method -> + + val currentMethods: List = paymentMethods.payinMethods.mapNotNull { method -> val isPending = method.status == MemberPaymentMethodStatus.PENDING when (method.provider) { MemberPaymentProvider.SWISH -> { val phoneNumber = method.details?.asPaymentMethodSwishDetails()?.phoneNumber - PayinAccount.SwishPayin(phoneNumber = phoneNumber, isPending = isPending) + PayinAccount.SwishPayin( + phoneNumber = phoneNumber, + isPending = isPending, + isDefault = method.isDefault, + ) } MemberPaymentProvider.TRUSTLY -> { @@ -49,6 +52,7 @@ internal class GetPayinAccountUseCase( accountNumber = accountNumber, bankName = bankName, isPending = isPending, + isDefault = method.isDefault ) } @@ -58,6 +62,7 @@ internal class GetPayinAccountUseCase( delivery = invoiceDetails?.delivery, email = invoiceDetails?.email, isPending = isPending, + isDefault = method.isDefault ) } @@ -67,13 +72,13 @@ internal class GetPayinAccountUseCase( } } - val availablePayoutMethods = paymentMethods.availableMethods + val availablePayinMethods = paymentMethods.availableMethods .filter { it.supportsPayin } .map { it.provider } PayinAccountData( - currentMethod = currentMethod, - availablePayinMethods = availablePayoutMethods, + currentMethods = currentMethods, + availablePayinMethods = availablePayinMethods, ) } } @@ -101,22 +106,26 @@ private fun parseBankAccountDetails( internal sealed interface PayinAccount { val isPending: Boolean + val isDefault: Boolean data class Trustly( val clearingNumber: String?, val accountNumber: String?, val bankName: String?, override val isPending: Boolean, + override val isDefault: Boolean, ) : PayinAccount data class SwishPayin( val phoneNumber: String?, override val isPending: Boolean, + override val isDefault: Boolean, ) : PayinAccount data class Invoice( val delivery: PaymentMethodInvoiceDelivery?, val email: String?, override val isPending: Boolean, + override val isDefault: Boolean, ) : PayinAccount } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index ccf5914d3f..e7303c6e77 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -26,12 +26,12 @@ fun NavGraphBuilder.payinAccountGraph( navController: NavController, globalSnackBarState: GlobalSnackBarState, hedvigDeepLinkContainer: HedvigDeepLinkContainer, - navigateToConnectPayment: (builder: NavOptionsBuilder.() -> Unit) -> Unit, + navigateToConnectTrustly: (builder: NavOptionsBuilder.() -> Unit) -> Unit, navigateUp: () -> Unit, ) { navgraph( startDestination = PayinAccountDestinations.Overview::class, - deepLinks = navDeepLinks(hedvigDeepLinkContainer.payout), + deepLinks = navDeepLinks(hedvigDeepLinkContainer.connectPayment), ) { navdestination { val viewModel: PayinAccountOverviewViewModel = koinViewModel() @@ -41,11 +41,20 @@ fun NavGraphBuilder.payinAccountGraph( val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content navController.navigate( PayinAccountDestinations.SelectPayinMethod( - availableProviders = content?.availablePayoutMethods?.map { it.rawValue } ?: emptyList(), + availableProviders = content?.availablePayinMethods?.map { it.rawValue } ?: emptyList(), ), ) }, navigateUp = navigateUp, + onTrustlySelected = dropUnlessResumed { + navigateToConnectTrustly { + typedPopUpTo { + inclusive = true + } + } + }, + onSwishSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupSwishPayin) }, + onInvoiceSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupInvoicePayin) }, ) } @@ -53,7 +62,7 @@ fun NavGraphBuilder.payinAccountGraph( SelectPayinMethodDestination( availableProviders = this.availableProviders.map { MemberPaymentProvider.safeValueOf(it) }, onTrustlySelected = dropUnlessResumed { - navigateToConnectPayment { + navigateToConnectTrustly { typedPopUpTo { inclusive = true } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index d27d4e902f..1b9e599e0f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -2,6 +2,7 @@ package com.hedvig.android.feature.payin.account.ui.overview import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider @@ -18,14 +20,19 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.EmptyState import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HighlightLabel +import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults +import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.feature.payin.account.data.PayinAccount @@ -43,6 +50,9 @@ internal fun PayinAccountOverviewDestination( viewModel: PayinAccountOverviewViewModel, onConnectPayoutMethodClicked: () -> Unit, navigateUp: () -> Unit, + onTrustlySelected: () -> Unit, + onSwishSelected: () -> Unit, + onInvoiceSelected: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() PayinAccountOverviewScreen( @@ -50,6 +60,9 @@ internal fun PayinAccountOverviewDestination( onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, navigateUp = navigateUp, + onTrustlySelected = onTrustlySelected, + onSwishSelected = onSwishSelected, + onInvoiceSelected = onInvoiceSelected ) } @@ -59,6 +72,9 @@ private fun PayinAccountOverviewScreen( onConnectPayoutMethodClicked: () -> Unit, onRetry: () -> Unit, navigateUp: () -> Unit, + onTrustlySelected: () -> Unit, + onSwishSelected: () -> Unit, + onInvoiceSelected: () -> Unit, ) { HedvigScaffold( topAppBarText = "Billing account", //todo! @@ -85,10 +101,13 @@ private fun PayinAccountOverviewScreen( is PayinAccountOverviewUiState.Content -> { PayoutAccountContent( - currentMethod = uiState.currentMethod, - availablePayinMethods = uiState.availablePayoutMethods, + currentMethods = uiState.currentMethods, + availablePayinMethods = uiState.availablePayinMethods, onConnectPayinMethodClicked = onConnectPayoutMethodClicked, modifier = Modifier.weight(1f), + onTrustlySelected = onTrustlySelected, + onSwishSelected = onSwishSelected, + onInvoiceSelected = onInvoiceSelected ) } } @@ -97,61 +116,81 @@ private fun PayinAccountOverviewScreen( @Composable private fun PayoutAccountContent( - currentMethod: PayinAccount?, + currentMethods: List, availablePayinMethods: List, onConnectPayinMethodClicked: () -> Unit, + onTrustlySelected: () -> Unit, + onSwishSelected: () -> Unit, + onInvoiceSelected: () -> Unit, modifier: Modifier = Modifier, ) { Column(modifier) { Spacer(Modifier.height(8.dp)) - when (currentMethod) { - null -> { - if (availablePayinMethods.isNotEmpty()) { - Spacer(Modifier.weight(1f)) - EmptyState( - text = "You haven’t added a billing method yet. Add one to pay for your insurance.", //todo - description = null, - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, - ) - } - } - - is PayinAccount.SwishPayin -> { - val phoneNumber = currentMethod.phoneNumber.orEmpty() - PayinAccountReadOnlyTextField( - label = stringResource(Res.string.swish), - text = if (currentMethod.isPending && phoneNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) - } else { - phoneNumber - }, + if (currentMethods.isEmpty()) { + if (availablePayinMethods.isNotEmpty()) { + Spacer(Modifier.weight(1f)) + EmptyState( + text = "You haven’t added a billing method yet. Add one to pay for your insurance.", //todo + description = null, + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, ) } + } else { + HedvigText("Active billing methods", modifier = Modifier.padding(horizontal = 16.dp)) //todo + Spacer(Modifier.height(16.dp)) + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + currentMethods.forEach { method -> + when (method) { + is PayinAccount.SwishPayin -> { + val phoneNumber = method.phoneNumber.orEmpty() + CurrentPayinMethodRow( + label = stringResource(Res.string.swish), + text = if (method.isPending && phoneNumber.isBlank()) { + stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + } else { + phoneNumber + }, + isDefault = method.isDefault, + onClick = onSwishSelected, + modifier = Modifier.padding(horizontal = 16.dp) + ) + } - is PayinAccount.Trustly -> { - val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayinAccountReadOnlyTextField( - label = formatBankAccountLabel(stringResource(Res.string.trustly), currentMethod.bankName), - text = if (currentMethod.isPending && accountNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) - } else { - accountNumber - }, - ) - } + is PayinAccount.Trustly -> { + val accountNumber = formatBankAccountNumber(method.clearingNumber, method.accountNumber) + CurrentPayinMethodRow( + label = formatBankAccountLabel(stringResource(Res.string.trustly), method.bankName), + text = if (method.isPending && accountNumber.isBlank()) { + stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + } else { + accountNumber + }, + isDefault = method.isDefault, + onClick = onTrustlySelected, + modifier = Modifier.padding(horizontal = 16.dp) + ) + } - is PayinAccount.Invoice -> { - PayinAccountReadOnlyTextField( - stringResource(Res.string.PAYMENTS_ACCOUNT), - stringResource(Res.string.PAYMENTS_INVOICE), - ) + is PayinAccount.Invoice -> { + CurrentPayinMethodRow( + stringResource(Res.string.PAYMENTS_ACCOUNT), + stringResource(Res.string.PAYMENTS_INVOICE), + isDefault = method.isDefault, + onClick = onInvoiceSelected, + modifier = Modifier.padding(horizontal = 16.dp) + ) + } + } + } } } Spacer(Modifier.weight(1f)) Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { - if (currentMethod?.isPending == true) { + if (currentMethods.any { it.isPending }) { HedvigNotificationCard( - message = "You have just added or changed your billing method, it will appear here soon.", //todo + message = "You have just added or changed a billing method, it will appear here soon.", //todo priority = NotificationPriority.Info, modifier = Modifier .fillMaxWidth() @@ -160,11 +199,7 @@ private fun PayoutAccountContent( } if (availablePayinMethods.isNotEmpty()) { HedvigButton( - text = if (currentMethod == null) { - "Select billing method" //todo! - } else { - "Change billing method" //todo! - }, + text = "Add a billing method", //todo! onClick = onConnectPayinMethodClicked, enabled = true, modifier = Modifier @@ -191,6 +226,52 @@ private fun PayinAccountReadOnlyTextField(label: String, text: String, modifier: ) } +@Composable +private fun CurrentPayinMethodRow( + label: String, + text: String, + isDefault: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + HedvigCard( + onClick = onClick, + modifier = modifier.fillMaxWidth(), + ) { + HorizontalItemsWithMaximumSpaceTaken( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + startSlot = { + Column { + HedvigText(text = label) + HedvigText( + text = text, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + }, + endSlot = { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ){ + if (isDefault) { + HighlightLabel( + labelText = "Default", //todo + size = HighlightLabelDefaults.HighLightSize.Small, + color = HighlightLabelDefaults.HighlightColor.Green( + HighlightLabelDefaults.HighlightShade.LIGHT + ) + ) + } + } + }, + spaceBetween = 6.dp + ) + } +} + private fun formatBankAccountLabel(baseLabel: String, bankName: String?): String { return if (bankName != null) "$baseLabel - $bankName" else baseLabel } @@ -214,7 +295,8 @@ private fun PreviewPayinAccountOverviewScreen( onConnectPayoutMethodClicked = {}, onRetry = {}, navigateUp = {}, - ) + {},{}, +{} ) } } } @@ -224,33 +306,59 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr PayinAccountOverviewUiState.Loading, PayinAccountOverviewUiState.Error, PayinAccountOverviewUiState.Content( - currentMethod = null, - availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), + currentMethods = emptyList(), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), ), PayinAccountOverviewUiState.Content( - currentMethod = PayinAccount.SwishPayin(phoneNumber = "070-123 45 67", isPending = false), - availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), + currentMethods = listOf( + PayinAccount.SwishPayin( + phoneNumber = "070-123 45 67", + isPending = false, + isDefault = true, + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH), ), PayinAccountOverviewUiState.Content( - currentMethod = PayinAccount.SwishPayin(phoneNumber = "070-123 45 67", isPending = false), - availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), + currentMethods = listOf( + PayinAccount.SwishPayin( + phoneNumber = "070-123 45 67", + isPending = false, + isDefault = true, + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), ), PayinAccountOverviewUiState.Content( - currentMethod = PayinAccount.SwishPayin(phoneNumber = null, isPending = true), - availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), + currentMethods = listOf(PayinAccount.SwishPayin(phoneNumber = null, isPending = true, isDefault = true)), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH), ), PayinAccountOverviewUiState.Content( - currentMethod = PayinAccount.SwishPayin(phoneNumber = "070-123 45 67", isPending = true), - availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), + currentMethods = listOf( + PayinAccount.SwishPayin( + phoneNumber = "070-123 45 67", + isPending = true, + isDefault = true, + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH), ), PayinAccountOverviewUiState.Content( - currentMethod = PayinAccount.Trustly( - clearingNumber = "8327", - accountNumber = "12345678", - bankName = "Mock Swedbank", - isPending = false, + currentMethods = listOf( + PayinAccount.Trustly( + clearingNumber = "8327", + accountNumber = "12345678", + bankName = "Mock Swedbank", + isPending = false, + isDefault = true, + ), + PayinAccount.SwishPayin( + phoneNumber = "070-123 45 67", + isPending = true, + isDefault = false, + ), ), - availablePayoutMethods = listOf(MemberPaymentProvider.TRUSTLY), + availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), ), ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index 94427835f3..da91d6f161 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -31,8 +31,8 @@ internal sealed interface PayinAccountOverviewUiState { data object Error : PayinAccountOverviewUiState data class Content( - val currentMethod: PayinAccount?, - val availablePayoutMethods: List, + val currentMethods: List, + val availablePayinMethods: List, ) : PayinAccountOverviewUiState } @@ -52,8 +52,8 @@ internal class PayinAccountOverviewPresenter( ifLeft = { uiState = PayinAccountOverviewUiState.Error }, ifRight = { data -> uiState = PayinAccountOverviewUiState.Content( - currentMethod = data.currentMethod, - availablePayoutMethods = data.availablePayinMethods, + currentMethods = data.currentMethods, + availablePayinMethods = data.availablePayinMethods, ) }, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 8d8fb06104..17da4a8c69 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -12,9 +12,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Surface import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE import hedvig.resources.PAYMENTS_INVOICE @@ -37,7 +39,7 @@ internal fun SelectPayinMethodDestination( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = stringResource(Res.string.PAYOUT_SELECT_PAYOUT_METHOD), //todo + topAppBarText = "Select billing method to add", //todo navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -48,7 +50,7 @@ internal fun SelectPayinMethodDestination( MemberPaymentProvider.TRUSTLY -> { PayinMethodRow( title = stringResource(Res.string.trustly), - subtitle = stringResource(Res.string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), //todo + subtitle = "Connect your bank via Trustly", //todo onClick = onTrustlySelected, ) } @@ -56,7 +58,7 @@ internal fun SelectPayinMethodDestination( MemberPaymentProvider.SWISH -> { PayinMethodRow( title = stringResource(Res.string.swish), - subtitle = stringResource(Res.string.PAYOUT_METHOD_SWISH_DESCRIPTION), //todo + subtitle = "Monthly auto-payments through Swish", //todo onClick = onSwishSelected, ) } @@ -92,3 +94,22 @@ private fun PayinMethodRow(title: String, subtitle: String, onClick: () -> Unit, } } } + +@Composable +@HedvigPreview +private fun PreviewSelectPayinMethodScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SelectPayinMethodDestination( + availableProviders = listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.INVOICE, + ), + onTrustlySelected = {}, + onSwishSelected = {}, + onInvoiceSelected = {}, + navigateUp = {}, + ) + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index 680955d469..007cc46e50 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -3,6 +3,7 @@ package com.hedvig.android.feature.payin.account.ui.setupinvoice import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -12,7 +13,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.GlobalSnackBarState @@ -70,8 +73,17 @@ private fun SetupInvoicePayinScreen( modifier = Modifier.fillMaxSize(), ) { //todo: some text here?? - Column (Modifier.weight(1f)) { - HedvigText("You can choose invoice as you pay-in") + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + HedvigText("You can choose invoice as your billing method. " + + "You will then get a monthly invoice via Kivra or email if you don't have Kivra account.", + textAlign = TextAlign.Center) } AnimatedVisibility( visible = uiState.errorMessage != null, @@ -89,7 +101,7 @@ private fun SetupInvoicePayinScreen( } Spacer(Modifier.height(16.dp)) HedvigButton( - text = "Connect", //todo + text = "Set invoice as billing method", //todo onClick = onConnect, enabled = !uiState.isLoading, isLoading = uiState.isLoading, @@ -111,10 +123,10 @@ private fun PreviewPayoutAccountOverviewScreen() { uiState = SetupInvoicePayinUiState( false, null, - showSuccessSnackBar = false + showSuccessSnackBar = false, ), globalSnackBarState = GlobalSnackBarState(), - {}, {} ,{} + {}, {}, {}, ) } } From 425c908d60c8aa7e14fc3bc667597c754076b4ab Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 20 May 2026 17:04:55 +0200 Subject: [PATCH 07/75] SetAsDefaultUseCase --- .../android/design/system/hedvig/Button.kt | 2 + .../main/graphql/SetAsDefaultMutation.graphql | 7 + .../account/data/GetPayinAccountUseCase.kt | 19 +- .../payin/account/data/SetAsDefaultUseCase.kt | 47 +++++ .../payin/account/di/PayinAccountModule.kt | 17 +- .../payin/account/navigation/PayinNavGraph.kt | 9 - .../PayinAccountOverviewDestination.kt | 163 +++++++++++------- .../overview/PayinAccountOverviewViewModel.kt | 39 ++++- 8 files changed, 224 insertions(+), 79 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/graphql/SetAsDefaultMutation.graphql create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt index d963ed99e7..f41fbd0237 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt @@ -203,6 +203,7 @@ fun HedvigButtonGhostWithBorder( enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, size: ButtonSize = ButtonSize.Medium, + isLoading: Boolean = false ) { HedvigTextButton( text = text, @@ -215,6 +216,7 @@ fun HedvigButtonGhostWithBorder( ), buttonSize = size, interactionSource = interactionSource, + isLoading = isLoading ) } diff --git a/app/feature/feature-payin-account/src/main/graphql/SetAsDefaultMutation.graphql b/app/feature/feature-payin-account/src/main/graphql/SetAsDefaultMutation.graphql new file mode 100644 index 0000000000..7150867bb7 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/graphql/SetAsDefaultMutation.graphql @@ -0,0 +1,7 @@ +mutation SetAsDefaultPayin($provider: MemberPaymentProvider!) { + paymentMethodSetDefaultPayin( + provider: $provider + ) { + message + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index a52a637ff2..9c80308f47 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -8,6 +8,7 @@ import com.apollographql.apollo.cache.normalized.fetchPolicy import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.logger.logcat import octopus.GetPayinMethodsQuery import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodBankAccountDetails import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodInvoiceDetails @@ -25,6 +26,7 @@ internal class GetPayinAccountUseCase( private val apolloClient: ApolloClient, ) { suspend fun invoke(): Either = either { + logcat { "Mariia: GetPayinAccountUseCase launching" } val result = apolloClient .query(GetPayinMethodsQuery()) .fetchPolicy(FetchPolicy.NetworkOnly) @@ -59,7 +61,7 @@ internal class GetPayinAccountUseCase( MemberPaymentProvider.INVOICE -> { val invoiceDetails = method.details?.asPaymentMethodInvoiceDetails() PayinAccount.Invoice( - delivery = invoiceDetails?.delivery, + delivery = invoiceDetails?.delivery?.toDeliveryString(), email = invoiceDetails?.email, isPending = isPending, isDefault = method.isDefault @@ -76,10 +78,12 @@ internal class GetPayinAccountUseCase( .filter { it.supportsPayin } .map { it.provider } - PayinAccountData( + val finalResult = PayinAccountData( currentMethods = currentMethods, availablePayinMethods = availablePayinMethods, ) + logcat { "Mariia: GetPayinAccountUseCase finalResult: $finalResult" } + finalResult } } @@ -123,9 +127,18 @@ internal sealed interface PayinAccount { ) : PayinAccount data class Invoice( - val delivery: PaymentMethodInvoiceDelivery?, + val delivery: String?, val email: String?, override val isPending: Boolean, override val isDefault: Boolean, ) : PayinAccount } + +private fun PaymentMethodInvoiceDelivery?.toDeliveryString(): String? { + return when(this) { + PaymentMethodInvoiceDelivery.KIVRA -> "Kivra" //todo + PaymentMethodInvoiceDelivery.MAIL -> "Email" //todo + PaymentMethodInvoiceDelivery.UNKNOWN__ -> "" + else -> null + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt new file mode 100644 index 0000000000..cee0a1b9db --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -0,0 +1,47 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +import arrow.core.raise.context.bind +import arrow.core.raise.context.either +import arrow.core.raise.context.raise +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.ErrorMessage +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.logger.logcat +import octopus.SetAsDefaultPayinMutation +import octopus.type.MemberPaymentProvider + +internal interface SetAsDefaultUseCase { + suspend fun invoke(provider: MemberPaymentProvider): Either +} + + +internal class SetAsDefaultUseCaseImpl( + private val apolloClient: ApolloClient, + private val getPayinAccountUseCase: GetPayinAccountUseCase, +) : SetAsDefaultUseCase { + override suspend fun invoke(provider: MemberPaymentProvider): Either { + return either { + logcat { "Mariia: Starting SetAsDefaultUseCaseImpl with provider: $provider" } + apolloClient + .mutation(SetAsDefaultPayinMutation(provider)) + .safeExecute(::ErrorMessage) + .fold( + ifLeft = { + logcat { "Mariia: SetAsDefaultUseCaseImpl error: $it" } + logcat { "SetAsDefaultUseCaseImpl error: $it" } + raise(ErrorMessage()) + }, + ifRight = { result -> + val userError = result.paymentMethodSetDefaultPayin?.message + if (userError != null) { + raise(ErrorMessage(userError)) + } + logcat { "Mariia: SetAsDefaultUseCaseImpl launching getPayinAccountUseCase" } + getPayinAccountUseCase.invoke().bind() + }, + ) + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt index efab20f540..a3a6fd5a83 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt @@ -3,6 +3,8 @@ package com.hedvig.android.feature.payin.account.di import com.apollographql.apollo.ApolloClient import com.hedvig.android.apollo.NetworkCacheManager import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase +import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase +import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCaseImpl import com.hedvig.android.feature.payin.account.data.SetupInvoicePayinUseCase import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCaseImpl @@ -16,9 +18,20 @@ val payinAccountModule = module { single { SetupSwishPayinUseCaseImpl(get(), get()) } - single {GetPayinAccountUseCase(get())} + single { GetPayinAccountUseCase(get()) } + single { + SetAsDefaultUseCaseImpl( + get(), + get(), + ) + } single { SetupInvoicePayinUseCase(get(), get()) } viewModel { SetupInvoicePayinViewModel(get()) } viewModel { SetupSwishPayinViewModel(get()) } - viewModel { PayinAccountOverviewViewModel(get()) } + viewModel { + PayinAccountOverviewViewModel( + get(), + get(), + ) + } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index e7303c6e77..73791d7755 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -46,15 +46,6 @@ fun NavGraphBuilder.payinAccountGraph( ) }, navigateUp = navigateUp, - onTrustlySelected = dropUnlessResumed { - navigateToConnectTrustly { - typedPopUpTo { - inclusive = true - } - } - }, - onSwishSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupSwishPayin) }, - onInvoiceSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupInvoicePayin) }, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 1b9e599e0f..53cf43f0d8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -17,9 +17,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.EmptyState import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigButtonGhostWithBorder import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced @@ -27,8 +30,6 @@ import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTextField -import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HighlightLabel import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults @@ -36,12 +37,11 @@ import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTa import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.feature.payin.account.data.PayinAccount -import hedvig.resources.PAYMENTS_ACCOUNT import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res +import hedvig.resources.something_went_wrong import hedvig.resources.swish -import hedvig.resources.trustly import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @@ -50,9 +50,6 @@ internal fun PayinAccountOverviewDestination( viewModel: PayinAccountOverviewViewModel, onConnectPayoutMethodClicked: () -> Unit, navigateUp: () -> Unit, - onTrustlySelected: () -> Unit, - onSwishSelected: () -> Unit, - onInvoiceSelected: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() PayinAccountOverviewScreen( @@ -60,9 +57,9 @@ internal fun PayinAccountOverviewDestination( onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, navigateUp = navigateUp, - onTrustlySelected = onTrustlySelected, - onSwishSelected = onSwishSelected, - onInvoiceSelected = onInvoiceSelected + setAsDefaultPayinMethod = { + viewModel.emit(PayinAccountOverviewEvent.SetDefaultMethod(it)) + }, ) } @@ -72,9 +69,7 @@ private fun PayinAccountOverviewScreen( onConnectPayoutMethodClicked: () -> Unit, onRetry: () -> Unit, navigateUp: () -> Unit, - onTrustlySelected: () -> Unit, - onSwishSelected: () -> Unit, - onInvoiceSelected: () -> Unit, + setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, ) { HedvigScaffold( topAppBarText = "Billing account", //todo! @@ -105,9 +100,9 @@ private fun PayinAccountOverviewScreen( availablePayinMethods = uiState.availablePayinMethods, onConnectPayinMethodClicked = onConnectPayoutMethodClicked, modifier = Modifier.weight(1f), - onTrustlySelected = onTrustlySelected, - onSwishSelected = onSwishSelected, - onInvoiceSelected = onInvoiceSelected + setAsDefaultPayinMethod = setAsDefaultPayinMethod, + loadingDefaultProvider = uiState.loadingDefaultProvider, + setDefaultProviderError = uiState.setDefaultProviderError, ) } } @@ -119,9 +114,9 @@ private fun PayoutAccountContent( currentMethods: List, availablePayinMethods: List, onConnectPayinMethodClicked: () -> Unit, - onTrustlySelected: () -> Unit, - onSwishSelected: () -> Unit, - onInvoiceSelected: () -> Unit, + setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, + loadingDefaultProvider: MemberPaymentProvider?, + setDefaultProviderError: ErrorMessage?, modifier: Modifier = Modifier, ) { Column(modifier) { @@ -136,7 +131,8 @@ private fun PayoutAccountContent( ) } } else { - HedvigText("Active billing methods", modifier = Modifier.padding(horizontal = 16.dp)) //todo + HedvigText("Active billing methods",//todo + modifier = Modifier.padding(horizontal = 16.dp)) Spacer(Modifier.height(16.dp)) Column( verticalArrangement = Arrangement.spacedBy(8.dp), @@ -153,40 +149,64 @@ private fun PayoutAccountContent( phoneNumber }, isDefault = method.isDefault, - onClick = onSwishSelected, - modifier = Modifier.padding(horizontal = 16.dp) + onClick = { + setAsDefaultPayinMethod(MemberPaymentProvider.SWISH) + }, + modifier = Modifier.padding(horizontal = 16.dp), + loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.SWISH, ) } is PayinAccount.Trustly -> { - val accountNumber = formatBankAccountNumber(method.clearingNumber, method.accountNumber) + val accountNumber = formatBankAccountNumber(method.clearingNumber, method.accountNumber, method.bankName) CurrentPayinMethodRow( - label = formatBankAccountLabel(stringResource(Res.string.trustly), method.bankName), + label = "Autogiro", //todo text = if (method.isPending && accountNumber.isBlank()) { stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) } else { accountNumber }, isDefault = method.isDefault, - onClick = onTrustlySelected, - modifier = Modifier.padding(horizontal = 16.dp) + onClick = { + setAsDefaultPayinMethod(MemberPaymentProvider.TRUSTLY) + }, + modifier = Modifier.padding(horizontal = 16.dp), + loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.TRUSTLY, ) } is PayinAccount.Invoice -> { CurrentPayinMethodRow( - stringResource(Res.string.PAYMENTS_ACCOUNT), stringResource(Res.string.PAYMENTS_INVOICE), + method.delivery ?: "", isDefault = method.isDefault, - onClick = onInvoiceSelected, - modifier = Modifier.padding(horizontal = 16.dp) + onClick = { + setAsDefaultPayinMethod(MemberPaymentProvider.INVOICE) + }, + modifier = Modifier.padding(horizontal = 16.dp), + loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.INVOICE, ) } } } } } - Spacer(Modifier.weight(1f)) + Column( + Modifier + .weight(1f) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + if (setDefaultProviderError != null) { + EmptyState( + text = stringResource(Res.string.something_went_wrong), + description = setDefaultProviderError.message, + modifier = Modifier.padding(horizontal = 16.dp), + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.ERROR, + ) + } + } Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { if (currentMethods.any { it.isPending }) { HedvigNotificationCard( @@ -212,30 +232,16 @@ private fun PayoutAccountContent( } } -@Composable -private fun PayinAccountReadOnlyTextField(label: String, text: String, modifier: Modifier = Modifier) { - HedvigTextField( - text = text, - onValueChange = {}, - labelText = label, - textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, - readOnly = true, - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) -} - @Composable private fun CurrentPayinMethodRow( label: String, text: String, isDefault: Boolean, + loadingDefaultProvider: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { HedvigCard( - onClick = onClick, modifier = modifier.fillMaxWidth(), ) { HorizontalItemsWithMaximumSpaceTaken( @@ -254,31 +260,34 @@ private fun CurrentPayinMethodRow( endSlot = { Row( horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ){ + verticalAlignment = Alignment.CenterVertically, + ) { if (isDefault) { HighlightLabel( labelText = "Default", //todo size = HighlightLabelDefaults.HighLightSize.Small, color = HighlightLabelDefaults.HighlightColor.Green( - HighlightLabelDefaults.HighlightShade.LIGHT - ) + HighlightLabelDefaults.HighlightShade.LIGHT, + ), + ) + } else { + HedvigButtonGhostWithBorder( + size = ButtonDefaults.ButtonSize.Small, + text = "Choose as default", //todo + onClick = onClick, + isLoading = loadingDefaultProvider, ) } } }, - spaceBetween = 6.dp + spaceBetween = 6.dp, ) } } -private fun formatBankAccountLabel(baseLabel: String, bankName: String?): String { - return if (bankName != null) "$baseLabel - $bankName" else baseLabel -} - -private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?): String { +private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?, bankName: String?): String { return when { - clearingNumber != null && accountNumber != null -> "$clearingNumber-$accountNumber" + clearingNumber != null && accountNumber != null && bankName != null -> "$bankName $clearingNumber-$accountNumber" else -> clearingNumber.orEmpty() } } @@ -295,8 +304,8 @@ private fun PreviewPayinAccountOverviewScreen( onConnectPayoutMethodClicked = {}, onRetry = {}, navigateUp = {}, - {},{}, -{} ) + {}, + ) } } } @@ -335,30 +344,56 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr ), PayinAccountOverviewUiState.Content( currentMethods = listOf( + PayinAccount.Trustly( + clearingNumber = "****", + accountNumber = "*45678", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ), PayinAccount.SwishPayin( phoneNumber = "070-123 45 67", - isPending = true, - isDefault = true, + isPending = false, + isDefault = false, ), ), availablePayinMethods = listOf(MemberPaymentProvider.SWISH), + loadingDefaultProvider = MemberPaymentProvider.SWISH, + ), + PayinAccountOverviewUiState.Content( + currentMethods = listOf( + PayinAccount.Trustly( + clearingNumber = "****", + accountNumber = "*45678", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ), + PayinAccount.SwishPayin( + phoneNumber = "070-123 45 67", + isPending = false, + isDefault = false, + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), ), PayinAccountOverviewUiState.Content( currentMethods = listOf( PayinAccount.Trustly( - clearingNumber = "8327", - accountNumber = "12345678", - bankName = "Mock Swedbank", + clearingNumber = "****", + accountNumber = "*45678", + bankName = "Swedbank", isPending = false, isDefault = true, ), PayinAccount.SwishPayin( phoneNumber = "070-123 45 67", - isPending = true, + isPending = false, isDefault = false, ), ), availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), + setDefaultProviderError = ErrorMessage("Not possible to set this method as default"), ), ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index da91d6f161..d855caa2ab 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -7,8 +7,11 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase +import com.hedvig.android.logger.logcat import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -16,13 +19,16 @@ import octopus.type.MemberPaymentProvider internal class PayinAccountOverviewViewModel( getPayinAccountUseCase: GetPayinAccountUseCase, + setAsDefaultUseCase: SetAsDefaultUseCase ) : MoleculeViewModel( PayinAccountOverviewUiState.Loading, - PayinAccountOverviewPresenter(getPayinAccountUseCase), + PayinAccountOverviewPresenter(getPayinAccountUseCase, setAsDefaultUseCase), ) internal sealed interface PayinAccountOverviewEvent { data object Retry : PayinAccountOverviewEvent + + data class SetDefaultMethod(val provider: MemberPaymentProvider): PayinAccountOverviewEvent } internal sealed interface PayinAccountOverviewUiState { @@ -33,17 +39,21 @@ internal sealed interface PayinAccountOverviewUiState { data class Content( val currentMethods: List, val availablePayinMethods: List, + val loadingDefaultProvider: MemberPaymentProvider? =null, + val setDefaultProviderError: ErrorMessage? = null ) : PayinAccountOverviewUiState } internal class PayinAccountOverviewPresenter( private val getPayinAccountUseCase: GetPayinAccountUseCase, + private val setAsDefaultUseCase: SetAsDefaultUseCase ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( lastState: PayinAccountOverviewUiState, ): PayinAccountOverviewUiState { var loadIteration by remember { mutableIntStateOf(0) } + var providerToSetAsDefault by remember { mutableStateOf(null) } var uiState by remember { mutableStateOf(lastState) } LaunchedEffect(loadIteration) { @@ -60,9 +70,36 @@ internal class PayinAccountOverviewPresenter( ) } + LaunchedEffect(providerToSetAsDefault) { + val provider = providerToSetAsDefault + if (provider!=null) { + logcat { "Mariia: Starting LaunchedEffect(providerToSetAsDefault) with provider: $provider" } + val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@LaunchedEffect + uiState = currentState.copy(loadingDefaultProvider = provider) + setAsDefaultUseCase.invoke(provider).fold( + ifLeft = { + providerToSetAsDefault = null + uiState = currentState.copy(setDefaultProviderError = it) + }, + ifRight = { data -> + providerToSetAsDefault = null + uiState = PayinAccountOverviewUiState.Content( + currentMethods = data.currentMethods, + availablePayinMethods = data.availablePayinMethods, + ) + }, + ) + } + } + CollectEvents { event -> when (event) { PayinAccountOverviewEvent.Retry -> loadIteration++ + is PayinAccountOverviewEvent.SetDefaultMethod -> { + val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@CollectEvents + uiState = currentState.copy(setDefaultProviderError = null) + providerToSetAsDefault = event.provider + } } } From 8aafceb0a856bbb1467b6ea2e9c34289f8585f3c Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 10:02:32 +0200 Subject: [PATCH 08/75] default and pending state --- .../account/data/GetPayinAccountUseCase.kt | 7 ++++--- .../payin/account/data/SetAsDefaultUseCase.kt | 1 + .../PayinAccountOverviewDestination.kt | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 9c80308f47..14d4076617 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -37,13 +37,14 @@ internal class GetPayinAccountUseCase( val currentMethods: List = paymentMethods.payinMethods.mapNotNull { method -> val isPending = method.status == MemberPaymentMethodStatus.PENDING + val isDefault = !isPending && method.isDefault when (method.provider) { MemberPaymentProvider.SWISH -> { val phoneNumber = method.details?.asPaymentMethodSwishDetails()?.phoneNumber PayinAccount.SwishPayin( phoneNumber = phoneNumber, isPending = isPending, - isDefault = method.isDefault, + isDefault = isDefault, ) } @@ -54,7 +55,7 @@ internal class GetPayinAccountUseCase( accountNumber = accountNumber, bankName = bankName, isPending = isPending, - isDefault = method.isDefault + isDefault = isDefault ) } @@ -64,7 +65,7 @@ internal class GetPayinAccountUseCase( delivery = invoiceDetails?.delivery?.toDeliveryString(), email = invoiceDetails?.email, isPending = isPending, - isDefault = method.isDefault + isDefault = isDefault ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt index cee0a1b9db..b427396737 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -36,6 +36,7 @@ internal class SetAsDefaultUseCaseImpl( ifRight = { result -> val userError = result.paymentMethodSetDefaultPayin?.message if (userError != null) { + logcat { "Mariia: SetAsDefaultUseCaseImpl userError not null: $userError" } raise(ErrorMessage(userError)) } logcat { "Mariia: SetAsDefaultUseCaseImpl launching getPayinAccountUseCase" } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 53cf43f0d8..99e6dba1b5 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -154,6 +154,7 @@ private fun PayoutAccountContent( }, modifier = Modifier.padding(horizontal = 16.dp), loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.SWISH, + isPending = method.isPending ) } @@ -172,6 +173,7 @@ private fun PayoutAccountContent( }, modifier = Modifier.padding(horizontal = 16.dp), loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.TRUSTLY, + isPending = method.isPending ) } @@ -185,6 +187,7 @@ private fun PayoutAccountContent( }, modifier = Modifier.padding(horizontal = 16.dp), loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.INVOICE, + isPending = method.isPending ) } } @@ -237,6 +240,7 @@ private fun CurrentPayinMethodRow( label: String, text: String, isDefault: Boolean, + isPending: Boolean, loadingDefaultProvider: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, @@ -271,12 +275,14 @@ private fun CurrentPayinMethodRow( ), ) } else { - HedvigButtonGhostWithBorder( - size = ButtonDefaults.ButtonSize.Small, - text = "Choose as default", //todo - onClick = onClick, - isLoading = loadingDefaultProvider, - ) + if (!isPending) { + HedvigButtonGhostWithBorder( + size = ButtonDefaults.ButtonSize.Small, + text = "Choose as default", //todo + onClick = onClick, + isLoading = loadingDefaultProvider, + ) + } } } }, From d4af5cee3081862a57a55e11f5a9ac224fc07c21 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 11:29:22 +0200 Subject: [PATCH 09/75] swish payin impl --- .../hedvig/android/apollo/ApolloCallExt.kt | 18 ++- .../android/app/navigation/HedvigNavHost.kt | 1 + .../graphql/SetupSwishPayinMutation.graphql | 9 ++ .../payin/account/data/SetAsDefaultUseCase.kt | 2 +- .../account/data/SetupSwishPayinUseCase.kt | 55 ++++++++- .../payin/account/navigation/PayinNavGraph.kt | 5 + .../setupswish/SetupSwishPayinDestination.kt | 105 ++++++++++++++---- .../ui/setupswish/SetupSwishPayinViewModel.kt | 48 ++++++-- 8 files changed, 202 insertions(+), 41 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql diff --git a/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt b/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt index e031d5eea0..b300006bfc 100644 --- a/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt +++ b/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt @@ -20,6 +20,7 @@ import com.hedvig.android.apollo.ApolloOperationError.OperationError import com.hedvig.android.apollo.ApolloOperationError.OperationException import com.hedvig.android.apollo.parseResponse import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.logger.logcat import kotlin.jvm.JvmInline import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -127,6 +128,7 @@ private fun IorRaise>.parseRespon } raise(OperationException(exception)) } + logcat{"Mariia: apollo parseResponse data: $data errors: $errors"} return iorFromErrorsAndData(errors.mapToOperationErrors(), data).bind() } @@ -136,6 +138,7 @@ private fun List?.mapToOperationErrors(): Nel? { if (error.extensionErrorType() == ExtensionErrorType.Unauthenticated) { OperationError.Unathenticated } else { + logcat{"Mariia: apollo mapToOperationErrors error: $error"} OperationError.Other( buildString { append(error.message) @@ -175,9 +178,18 @@ private fun iorFromErrorsAndData( data: D?, ): Ior, D> { return when { - errors != null && data != null -> Ior.Both(errors, data) - errors != null -> Ior.Left(errors) - data != null -> Ior.Right(data) + errors != null && data != null -> { + logcat{"Mariia: apollo iorFromErrorsAndData Ior.Both. data: $data errors: $errors"} + Ior.Both(errors, data) + } + errors != null -> { + logcat{"Mariia: apollo iorFromErrorsAndData Ior.Left"} + Ior.Left(errors) + } + data != null -> { + logcat{"Mariia: apollo iorFromErrorsAndData Ior.Right"} + Ior.Right(data) + } else -> error("Non compliant server") } } diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index d9d6762d11..971ac4e3bc 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -368,6 +368,7 @@ internal fun HedvigNavHost( hedvigDeepLinkContainer = hedvigDeepLinkContainer, navigateToConnectTrustly = ::navigateToConnectTrustly, navigateUp = navController::navigateUp, + openUrl = openUrl ) profileGraph( settingsDestinationNestedGraphs = { diff --git a/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql b/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql new file mode 100644 index 0000000000..200394d827 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql @@ -0,0 +1,9 @@ +mutation SetupSwishPayin($input: PaymentMethodSetupSwishInput!) { + paymentMethodSetupSwishPayin(input: $input) { + error { + message + } + status + url + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt index b427396737..98dd14c735 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -26,7 +26,7 @@ internal class SetAsDefaultUseCaseImpl( logcat { "Mariia: Starting SetAsDefaultUseCaseImpl with provider: $provider" } apolloClient .mutation(SetAsDefaultPayinMutation(provider)) - .safeExecute(::ErrorMessage) + .safeExecute(::ErrorMessage) //tosdoL user safeExecuteAllowingPartialResponses .fold( ifLeft = { logcat { "Mariia: SetAsDefaultUseCaseImpl error: $it" } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt index f7e0ca5e7a..46ffbd9a84 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -1,19 +1,66 @@ package com.hedvig.android.feature.payin.account.data import arrow.core.Either +import arrow.core.raise.context.bind +import arrow.core.raise.context.either import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.NetworkCacheManager +import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.logger.logcat +import octopus.SetupSwishPayinMutation +import octopus.type.PaymentMethodSetupStatus +import octopus.type.PaymentMethodSetupSwishInput + internal interface SetupSwishPayinUseCase { - suspend fun invoke(): Either + suspend fun invoke(phoneNumber: String): Either } internal class SetupSwishPayinUseCaseImpl( private val apolloClient: ApolloClient, private val networkCacheManager: NetworkCacheManager, -): SetupSwishPayinUseCase { - override suspend fun invoke(): Either { - TODO("Not yet implemented") +) : SetupSwishPayinUseCase { + override suspend fun invoke(phoneNumber: String): Either = either { + val result = apolloClient + .mutation(SetupSwishPayinMutation(PaymentMethodSetupSwishInput(phoneNumber))) + .safeExecute(::ErrorMessage) + .bind() + + val output = result.paymentMethodSetupSwishPayin + when (output.status) { + PaymentMethodSetupStatus.ACTIVE -> { + logcat { + "Mariia: SetupSwishPayinMutation ACTIVE url: $output.url" + } + networkCacheManager.clearCache() + SetupSwishResponse.Success(output.url) + + } + + PaymentMethodSetupStatus.PENDING -> { + logcat { + "Mariia: SetupSwishPayinMutation PENDING url: $output.url" + } + networkCacheManager.clearCache() + SetupSwishResponse.Pending(output.url) + } + + PaymentMethodSetupStatus.FAILED, PaymentMethodSetupStatus.UNKNOWN__ -> { + logcat { + "SetupSwishPayinMutation failed with: output.error?.message" + } + val userMessage = output.error?.message + SetupSwishResponse.Failure(ErrorMessage(userMessage)) + } + } } } + + +internal sealed interface SetupSwishResponse { + data class Failure(val error: ErrorMessage) : SetupSwishResponse + data class Success(val url: String?) : SetupSwishResponse + data class Pending(val url: String?) : SetupSwishResponse +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index 73791d7755..bdecc2e0a8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -28,6 +28,7 @@ fun NavGraphBuilder.payinAccountGraph( hedvigDeepLinkContainer: HedvigDeepLinkContainer, navigateToConnectTrustly: (builder: NavOptionsBuilder.() -> Unit) -> Unit, navigateUp: () -> Unit, + openUrl: (String) -> Unit, ) { navgraph( startDestination = PayinAccountDestinations.Overview::class, @@ -74,6 +75,10 @@ fun NavGraphBuilder.payinAccountGraph( navController.typedPopBackStack(inclusive = true) }, navigateUp = navController::navigateUp, + openUrl = { + navController.typedPopBackStack(inclusive = true) + openUrl(it) + } ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 4bee2c08d6..a4ef479bfa 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -14,10 +14,17 @@ import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.simulateHotReload +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.design.system.hedvig.EmptyState +import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard @@ -33,6 +40,7 @@ import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL import hedvig.resources.Res import hedvig.resources.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION import hedvig.resources.general_save_button +import hedvig.resources.something_went_wrong import hedvig.resources.swish import org.jetbrains.compose.resources.stringResource @@ -42,6 +50,7 @@ internal fun SetupSwishPayinDestination( globalSnackBarState: GlobalSnackBarState, onSuccessfullyConnected: () -> Unit, navigateUp: () -> Unit, + openUrl: (String) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() SetupSwishPayoutScreen( @@ -53,6 +62,7 @@ internal fun SetupSwishPayinDestination( onSuccessfullyConnected() }, navigateUp = navigateUp, + openUrl = openUrl ) } @@ -65,6 +75,7 @@ private fun SetupSwishPayoutScreen( onSave: () -> Unit, showedSnackBar: () -> Unit, navigateUp: () -> Unit, + openUrl: (String) -> Unit, ) { val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) LaunchedEffect(uiState.showSuccessSnackBar) { @@ -78,6 +89,37 @@ private fun SetupSwishPayoutScreen( navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { + Spacer(Modifier.weight(1f)) + if (uiState.error != null) { + EmptyState( + text = stringResource(Res.string.something_went_wrong), + description = uiState.error.message, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.ERROR, + ) + } + if (uiState.successUrl!=null) { + Column( + modifier= Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + EmptyState( + text = "The process started", + description = "Please confirm in Swish app", + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SUCCESS_WITH_WARNING, + ) + Spacer(Modifier.height(16.dp)) + HedvigButton( + "Open Swish", + enabled = true, + onClick = { + openUrl(uiState.successUrl) + } + ) + } + + } Spacer(Modifier.weight(1f)) Column(Modifier.padding(horizontal = 16.dp)) { HedvigTextField( @@ -90,26 +132,14 @@ private fun SetupSwishPayoutScreen( modifier = Modifier.fillMaxWidth(), ) } - AnimatedVisibility( - visible = uiState.errorMessage != null, - enter = expandVertically(), - exit = shrinkVertically(), - ) { - HedvigNotificationCard( - message = uiState.errorMessage?.message - ?: stringResource(Res.string.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION), - priority = NotificationPriority.Attention, - modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 4.dp) - .fillMaxWidth(), - ) - } + Spacer(Modifier.height(16.dp)) HedvigButton( text = stringResource(Res.string.general_save_button), onClick = onSave, - enabled = !uiState.isLoading && uiState.phoneNumberState.text.length >= 10, + enabled = !uiState.isLoading && + uiState.phoneNumberState.text.length >= 10 && + uiState.successUrl==null, isLoading = uiState.isLoading, modifier = Modifier .fillMaxWidth() @@ -121,21 +151,50 @@ private fun SetupSwishPayoutScreen( @Composable @HedvigPreview -private fun PreviewSetupSwishPayinScreen() { +private fun PreviewSetupSwishPayinScreen( + @PreviewParameter(SetupSwishPayinUiStateProvider::class) uiState: SetupSwishPayoutUiState, +) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { SetupSwishPayoutScreen( - uiState = SetupSwishPayoutUiState( - phoneNumberState = TextFieldState(), - isLoading = false, - errorMessage = null, - showSuccessSnackBar = false, - ), + uiState = uiState, globalSnackBarState = GlobalSnackBarState(), onSave = {}, showedSnackBar = {}, navigateUp = {}, + {} ) } } } + + +private class SetupSwishPayinUiStateProvider : CollectionPreviewParameterProvider( + listOf( + SetupSwishPayoutUiState( + phoneNumberState = TextFieldState("287334432273"), + isLoading = false, + error = null, + showSuccessSnackBar = false, + ), + SetupSwishPayoutUiState( + phoneNumberState = TextFieldState(), + isLoading = false, + error = ErrorMessage(), + showSuccessSnackBar = false, + ), + SetupSwishPayoutUiState( + phoneNumberState = TextFieldState("837286428"), + isLoading = true, + error = null, + showSuccessSnackBar = false, + ), + SetupSwishPayoutUiState( + phoneNumberState = TextFieldState("83728644428"), + isLoading = false, + error = null, + showSuccessSnackBar = false, + successUrl = "hwdjhew" + ) + ), +) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt index a365317fa4..a2659100cb 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase +import com.hedvig.android.feature.payin.account.data.SetupSwishResponse import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -16,7 +17,13 @@ import com.hedvig.android.molecule.public.MoleculeViewModel internal class SetupSwishPayinViewModel( setupSwishPayoutUseCase: SetupSwishPayinUseCase, ) : MoleculeViewModel( - SetupSwishPayoutUiState(TextFieldState(), false, null, false), + SetupSwishPayoutUiState( + phoneNumberState = TextFieldState(), + isLoading = false, + error = null, + showSuccessSnackBar = false, + successUrl = null + ), SetupSwishPayoutPresenter(setupSwishPayoutUseCase), ) @@ -26,11 +33,14 @@ internal sealed interface SetupSwishPayoutEvent { data object ShowedSnackBar : SetupSwishPayoutEvent } -internal data class SetupSwishPayoutUiState( - val phoneNumberState: TextFieldState, - val isLoading: Boolean, - val errorMessage: ErrorMessage?, - val showSuccessSnackBar: Boolean, +internal data class SetupSwishPayoutUiState ( + val showSuccessSnackBar: Boolean, + val successUrl: String? = null, + val phoneNumberState: TextFieldState, + val isLoading: Boolean, + val error: ErrorMessage?, + val resultIsPending: Boolean = false + ) internal class SetupSwishPayoutPresenter( @@ -44,23 +54,40 @@ internal class SetupSwishPayoutPresenter( var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } var showSuccessSnackBar by remember { mutableStateOf(false) } + var resultIsPending by remember { mutableStateOf(false) } var saveIteration by remember { mutableStateOf(null) } + var urlToRedirect by remember { mutableStateOf(null) } + var uiState by remember { mutableStateOf(lastState) } val currentSave = saveIteration if (currentSave != null) { + val currentState = uiState LaunchedEffect(currentSave) { isLoading = true errorMessage = null - setupSwishPayoutUseCase.invoke().fold( + resultIsPending = false + setupSwishPayoutUseCase.invoke(phoneNumberState.text.toString()).fold( ifLeft = { isLoading = false errorMessage = it saveIteration = null }, - ifRight = { + ifRight = { result -> isLoading = false - showSuccessSnackBar = true saveIteration = null + when(result) { + is SetupSwishResponse.Failure -> { + errorMessage = result.error + } + is SetupSwishResponse.Pending -> { + urlToRedirect = result.url + resultIsPending = true + } + is SetupSwishResponse.Success -> { + showSuccessSnackBar = true + urlToRedirect = result.url + } + } }, ) } @@ -83,8 +110,9 @@ internal class SetupSwishPayoutPresenter( return SetupSwishPayoutUiState( phoneNumberState = phoneNumberState, isLoading = isLoading, - errorMessage = errorMessage, + error = errorMessage, showSuccessSnackBar = showSuccessSnackBar, + successUrl = urlToRedirect ) } } From c31397a634736884674d5d841049c32c3a645207 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 11:43:31 +0200 Subject: [PATCH 10/75] always show Manage payment methods --- .../MemberPaymentDetailsDestination.kt | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt index 1c266595af..9112416eaf 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt @@ -220,30 +220,15 @@ private fun MemberPaymentDetailsSuccessScreen( } Spacer(Modifier.weight(1f)) Spacer(Modifier.height(16.dp)) - when { - paymentDetails.paymentMethod == PaymentMethod.TRUSTLY -> { - HedvigButton( - text = stringResource(R.string.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT), - onClick = onChangeBankAccount, - enabled = true, - buttonStyle = ButtonDefaults.ButtonStyle.Secondary, - modifier = Modifier - .fillMaxWidth() - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) - } - - paymentDetails.paymentMethod == PaymentMethod.INVOICE && account == PaymentAccount.Kivra -> { - HedvigNotificationCard( - message = stringResource(Res.string.KIVRA_NOTIFICATION_BOX_TEXT), - priority = NotificationDefaults.NotificationPriority.Info, - style = NotificationDefaults.InfoCardStyle.Button( - buttonText = stringResource(Res.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON), - onButtonClick = onChangeBankAccount, - ), - ) - } - } + HedvigButton( + text = "Manage payment methods",//todo + onClick = onChangeBankAccount, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + modifier = Modifier + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + ) Spacer(Modifier.height(16.dp)) } From 6d760c931b665deb91feff535d84fa2abc5d0ef5 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 12:02:23 +0200 Subject: [PATCH 11/75] help center nav --- .../kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt | 2 +- .../src/androidMain/res/values-sv-rSE/strings.xml | 4 +++- .../core-resources/src/androidMain/res/values/strings.xml | 2 ++ .../src/commonMain/composeResources/values-sv-rSE/strings.xml | 4 +++- .../src/commonMain/composeResources/values/strings.xml | 2 ++ .../payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt | 2 +- .../android/feature/help/center/data/GetQuickLinksUseCase.kt | 3 ++- .../memberpaymentdetails/MemberPaymentDetailsDestination.kt | 3 ++- 8 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 971ac4e3bc..ec3f9f45ad 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -493,7 +493,7 @@ internal fun HedvigNavHost( } QuickLinkConnectPayment -> { - TrustlyDestination + PayinAccountDestination.Graph } QuickLinkTermination -> { diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 1121f472a0..5c6838f5e0 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -522,6 +522,7 @@ Fråga angående skadeanmälan - Fordon reg. %1$s Välj land och språk Logga ut + Manage your billing methods Aktivera ditt försäkringsskydd igen genom att kontakta oss när din betalning har registrerats Få ett prisförslag Se över kontaktuppgifter @@ -680,7 +681,7 @@ Reseintyg Din profil Se guider - I valpguiden hittar du användbara artiklar som hjälper dig med allt från första veterinärbesöket till hur du väljer rätt foder. + I Valpguiden får du svar på de vanligaste frågorna som rör valpens första tid. Utvalda guider Läst Inte hjälpsam @@ -1366,6 +1367,7 @@ Du är inbjuden Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code + Återuppta handlingen Koppla ditt EuroBonus Lägg till ditt nummer Du tjänar 100 poäng per månad för varje aktiv försäkring du har hos Hedvig diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index ab249a9e6d..4beb026ee8 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -522,6 +522,7 @@ Question regarding claim, Vehicle reg. %1$s Preferences Logout + Manage your billing methods Activate your coverage again by contacting us once your payment has been processed Get a price quote Review contact info @@ -1366,6 +1367,7 @@ You’re invited Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code + Resume shopping Connect your EuroBonus Connect your number You earn 100 points per month for each active insurance you have with Hedvig diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index ee88808b69..df6c4c7f86 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -522,6 +522,7 @@ Fråga angående skadeanmälan - Fordon reg. %1$s Välj land och språk Logga ut + Manage your billing methods Aktivera ditt försäkringsskydd igen genom att kontakta oss när din betalning har registrerats Få ett prisförslag Se över kontaktuppgifter @@ -680,7 +681,7 @@ Reseintyg Din profil Se guider - I valpguiden hittar du användbara artiklar som hjälper dig med allt från första veterinärbesöket till hur du väljer rätt foder. + I Valpguiden får du svar på de vanligaste frågorna som rör valpens första tid. Utvalda guider Läst Inte hjälpsam @@ -1366,6 +1367,7 @@ Du är inbjuden Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code + Återuppta handlingen Koppla ditt EuroBonus Lägg till ditt nummer Du tjänar 100 poäng per månad för varje aktiv försäkring du har hos Hedvig diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index ff57e81a6a..7206a8403b 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -522,6 +522,7 @@ Question regarding claim, Vehicle reg. %1$s Preferences Logout + Manage your billing methods Activate your coverage again by contacting us once your payment has been processed Get a price quote Review contact info @@ -1366,6 +1367,7 @@ You’re invited Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code + Resume shopping Connect your EuroBonus Connect your number You earn 100 points per month for each active insurance you have with Hedvig diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt index ae0d4d83e0..cd235732ca 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/ConnectTrustlyPaymentGraph.kt @@ -15,7 +15,7 @@ fun NavGraphBuilder.connectPaymentGraph( ) { navdestination( deepLinks = navDeepLinks( - hedvigDeepLinkContainer.connectPayment, + //hedvigDeepLinkContainer.connectPayment, hedvigDeepLinkContainer.directDebit, ), ) { diff --git a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetQuickLinksUseCase.kt b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetQuickLinksUseCase.kt index 88b57633ba..9ff947e786 100644 --- a/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetQuickLinksUseCase.kt +++ b/app/feature/feature-help-center/src/main/kotlin/com/hedvig/android/feature/help/center/data/GetQuickLinksUseCase.kt @@ -38,6 +38,7 @@ import hedvig.resources.HC_QUICK_ACTIONS_TRAVEL_CERTIFICATE import hedvig.resources.HC_QUICK_ACTIONS_TRAVEL_CERTIFICATE_SUBTITLE import hedvig.resources.HC_QUICK_ACTIONS_UPGRADE_COVERAGE_SUBTITLE import hedvig.resources.HC_QUICK_ACTIONS_UPGRADE_COVERAGE_TITLE +import hedvig.resources.MANAGE_BILLING_METHODS_BUTTON import hedvig.resources.Res import kotlinx.coroutines.flow.first import octopus.AvailableSelfServiceOnContractsQuery @@ -118,7 +119,7 @@ internal class GetQuickLinksUseCase( StandaloneQuickLink( quickLinkDestination = QuickLinkDestination.OuterDestination.QuickLinkConnectPayment, titleRes = Res.string.HC_QUICK_ACTIONS_PAYMENTS_TITLE, - hintTextRes = Res.string.HC_QUICK_ACTIONS_PAYMENTS_SUBTITLE, + hintTextRes = Res.string.MANAGE_BILLING_METHODS_BUTTON, //todo!!! ), ) } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt index 9112416eaf..d2cb93d77b 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt @@ -56,6 +56,7 @@ import com.hedvig.android.placeholder.PlaceholderHighlight import com.hedvig.android.placeholder.shimmer import hedvig.resources.DASHBOARD_OPEN_CHAT import hedvig.resources.KIVRA_NOTIFICATION_BOX_TEXT +import hedvig.resources.MANAGE_BILLING_METHODS_BUTTON import hedvig.resources.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON import hedvig.resources.R import hedvig.resources.Res @@ -221,7 +222,7 @@ private fun MemberPaymentDetailsSuccessScreen( Spacer(Modifier.weight(1f)) Spacer(Modifier.height(16.dp)) HedvigButton( - text = "Manage payment methods",//todo + text = stringResource(Res.string.MANAGE_BILLING_METHODS_BUTTON),//todo onClick = onChangeBankAccount, enabled = true, buttonStyle = ButtonDefaults.ButtonStyle.Secondary, From d6cafcec797d0c095fcaf5904895cc64334f30ad Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 13:09:00 +0200 Subject: [PATCH 12/75] ReminderCardConnectPayment copy --- .../hedvig/android/memberreminders/ui/MemberReminderCards.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt index c6fea8b964..4b279f62fe 100644 --- a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt +++ b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt @@ -431,7 +431,7 @@ private fun ReminderCardConnectPayment( modifier = modifier, priority = NotificationPriority.Attention, style = InfoCardStyle.Button( - buttonText = stringResource(Res.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON), + buttonText = "Setup payment method", //todo onButtonClick = navigateToConnectPayment, ), minLines = minLines, From 493621d28b54976cdd83b902285d295fef9e5b53 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 14:19:31 +0200 Subject: [PATCH 13/75] qrcode and swish icon --- .../design/system/hedvig/EmptyState.kt | 15 +- .../system/hedvig/icon/colored/Swish.kt | 393 ++++++++++++++++++ .../feature-payin-account/build.gradle.kts | 1 + .../account/data/GetPayinAccountUseCase.kt | 15 +- .../payin/account/data/SetAsDefaultUseCase.kt | 3 +- .../account/data/SetupSwishPayinUseCase.kt | 5 +- .../payin/account/di/PayinAccountModule.kt | 2 +- .../payin/account/navigation/PayinNavGraph.kt | 2 +- .../PayinAccountOverviewDestination.kt | 26 +- .../overview/PayinAccountOverviewViewModel.kt | 36 +- .../SelectPayinMethodDestination.kt | 8 +- .../SetupInvoicePayinDestination.kt | 19 +- .../SetupInvoicePayinViewModel.kt | 8 +- .../payin/account/ui/setupswish/QRCode.kt | 59 +++ .../setupswish/SetupSwishPayinDestination.kt | 177 ++++++-- .../ui/setupswish/SetupSwishPayinViewModel.kt | 54 +-- 16 files changed, 701 insertions(+), 122 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/QRCode.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt index 567de7b4fd..30610c2a98 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt @@ -1,5 +1,6 @@ package com.hedvig.android.design.system.hedvig +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -37,6 +38,7 @@ import com.hedvig.android.design.system.hedvig.icon.CheckFilled import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.InfoFilled import com.hedvig.android.design.system.hedvig.icon.WarningFilled +import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.tokens.EmptyStateTokens @Composable @@ -148,6 +150,15 @@ private fun ColumnScope.EmptyStateIcon(iconStyle: EmptyStateIconStyle) { ) Spacer(Modifier.height(16.dp)) } + + EmptyStateIconStyle.SWISH -> { + Image( + HedvigIcons.Swish, + null, + modifier = Modifier.size(48.dp), + ) + Spacer(Modifier.height(16.dp)) + } } } @@ -161,7 +172,9 @@ object EmptyStateDefaults { SUCCESS, BANK_ID, NO_ICON, - SUCCESS_WITH_WARNING + SUCCESS_WITH_WARNING, + + SWISH } sealed class EmptyStateButtonStyle { diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt new file mode 100644 index 0000000000..6eb4b18285 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt @@ -0,0 +1,393 @@ +package com.hedvig.android.design.system.hedvig.icon.colored + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.icon.CheckFilled +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons + +val HedvigIcons.Swish: ImageVector + get() { + if (_Swish != null) { + return _Swish!! + } + _Swish = ImageVector.Builder( + name = "Swish", + defaultWidth = 420.dp, + defaultHeight = 566.dp, + viewportWidth = 420f, + viewportHeight = 566f + ).apply { + path(fill = SolidColor(Color(0xFF17191F))) { + moveTo(381.4f, 485.9f) + curveToRelative(0f, -2f, 0.4f, -3.9f, 1.1f, -5.7f) + curveToRelative(0.7f, -1.8f, 1.8f, -3.3f, 3f, -4.6f) + curveToRelative(1.3f, -1.3f, 2.8f, -2.4f, 4.5f, -3.1f) + curveToRelative(1.7f, -0.8f, 3.6f, -1.1f, 5.5f, -1.1f) + reflectiveCurveToRelative(3.9f, 0.4f, 5.6f, 1.1f) + curveToRelative(1.7f, 0.8f, 3.3f, 1.8f, 4.6f, 3.1f) + reflectiveCurveToRelative(2.3f, 2.9f, 3.1f, 4.6f) + curveToRelative(0.7f, 1.8f, 1.1f, 3.7f, 1.1f, 5.7f) + reflectiveCurveToRelative(-0.4f, 3.9f, -1.1f, 5.7f) + curveToRelative(-0.7f, 1.8f, -1.8f, 3.3f, -3.1f, 4.6f) + curveToRelative(-1.3f, 1.3f, -2.8f, 2.4f, -4.6f, 3.1f) + curveToRelative(-1.7f, 0.8f, -3.6f, 1.1f, -5.6f, 1.1f) + reflectiveCurveToRelative(-3.8f, -0.4f, -5.5f, -1.1f) + curveToRelative(-1.7f, -0.8f, -3.2f, -1.8f, -4.5f, -3.1f) + curveToRelative(-1.3f, -1.3f, -2.3f, -2.9f, -3f, -4.6f) + curveToRelative(-0.7f, -1.8f, -1.1f, -3.7f, -1.1f, -5.7f) + close() + moveTo(384.3f, 485.9f) + curveToRelative(0f, 1.7f, 0.3f, 3.2f, 0.9f, 4.7f) + curveToRelative(0.6f, 1.4f, 1.4f, 2.7f, 2.4f, 3.8f) + curveToRelative(1f, 1.1f, 2.2f, 1.9f, 3.6f, 2.5f) + curveToRelative(1.4f, 0.6f, 2.9f, 0.9f, 4.5f, 0.9f) + reflectiveCurveToRelative(3.1f, -0.3f, 4.5f, -0.9f) + curveToRelative(1.4f, -0.6f, 2.6f, -1.5f, 3.6f, -2.5f) + reflectiveCurveToRelative(1.8f, -2.3f, 2.4f, -3.8f) + curveToRelative(0.6f, -1.4f, 0.9f, -3f, 0.9f, -4.7f) + reflectiveCurveToRelative(-0.3f, -3.2f, -0.9f, -4.7f) + curveToRelative(-0.6f, -1.4f, -1.4f, -2.7f, -2.4f, -3.8f) + curveToRelative(-1f, -1.1f, -2.2f, -1.9f, -3.6f, -2.5f) + curveToRelative(-1.4f, -0.6f, -2.9f, -0.9f, -4.5f, -0.9f) + reflectiveCurveToRelative(-3.1f, 0.3f, -4.5f, 0.9f) + curveToRelative(-1.4f, 0.6f, -2.6f, 1.5f, -3.6f, 2.5f) + curveToRelative(-1f, 1.1f, -1.8f, 2.3f, -2.4f, 3.8f) + curveToRelative(-0.6f, 1.4f, -0.9f, 3f, -0.9f, 4.7f) + close() + moveTo(390.7f, 479.6f) + curveToRelative(0f, -0.9f, 0.4f, -1.3f, 1.3f, -1.3f) + horizontalLineToRelative(4.5f) + curveToRelative(1.4f, 0f, 2.6f, 0.4f, 3.4f, 1.2f) + curveToRelative(0.9f, 0.8f, 1.3f, 1.9f, 1.3f, 3.4f) + reflectiveCurveToRelative(0f, 1.1f, -0.3f, 1.6f) + curveToRelative(-0.2f, 0.5f, -0.4f, 0.8f, -0.7f, 1.2f) + curveToRelative(-0.3f, 0.3f, -0.6f, 0.6f, -0.9f, 0.8f) + reflectiveCurveToRelative(-0.6f, 0.4f, -1f, 0.4f) + horizontalLineToRelative(0f) + curveToRelative(0f, 0.1f, 0f, 0.2f, 0.1f, 0.2f) + curveToRelative(0f, 0f, 0.1f, 0.1f, 0.2f, 0.3f) + curveToRelative(0f, 0.1f, 0.1f, 0.3f, 0.2f, 0.4f) + lineToRelative(2.1f, 4f) + curveToRelative(0.2f, 0.5f, 0.3f, 0.8f, 0.2f, 1.1f) + curveToRelative(-0.1f, 0.3f, -0.4f, 0.4f, -0.9f, 0.4f) + horizontalLineToRelative(-0.5f) + curveToRelative(-0.7f, 0f, -1.3f, -0.3f, -1.6f, -1f) + lineToRelative(-2.3f, -4.9f) + horizontalLineToRelative(-2.5f) + verticalLineToRelative(4.6f) + curveToRelative(0f, 0.9f, -0.4f, 1.3f, -1.2f, 1.3f) + horizontalLineToRelative(-0.4f) + curveToRelative(-0.8f, 0f, -1.2f, -0.4f, -1.2f, -1.3f) + verticalLineToRelative(-12.5f) + close() + moveTo(396f, 485.4f) + curveToRelative(0.8f, 0f, 1.4f, -0.2f, 1.8f, -0.7f) + reflectiveCurveToRelative(0.6f, -1.1f, 0.6f, -1.9f) + reflectiveCurveToRelative(-0.2f, -1.4f, -0.6f, -1.8f) + curveToRelative(-0.4f, -0.4f, -1f, -0.6f, -1.8f, -0.6f) + horizontalLineToRelative(-2.4f) + verticalLineToRelative(5f) + horizontalLineToRelative(2.4f) + close() + moveTo(279.3f, 495f) + curveToRelative(4.5f, 0f, 8.4f, 0.6f, 11.5f, 1.7f) + curveToRelative(3.2f, 1.2f, 5.6f, 2.2f, 7.4f, 3.2f) + curveToRelative(1.5f, 0.8f, 2.4f, 1.9f, 2.7f, 3.2f) + curveToRelative(0.3f, 1.3f, 0f, 2.7f, -0.7f, 4.3f) + lineToRelative(-1.3f, 2.4f) + curveToRelative(-0.8f, 1.6f, -1.8f, 2.5f, -3.1f, 2.8f) + reflectiveCurveToRelative(-2.7f, 0f, -4.4f, -0.7f) + curveToRelative(-1.5f, -0.7f, -3.3f, -1.4f, -5.5f, -2.2f) + reflectiveCurveToRelative(-4.6f, -1.1f, -7.5f, -1.1f) + reflectiveCurveToRelative(-5.2f, 0.6f, -6.8f, 1.7f) + curveToRelative(-1.6f, 1.2f, -2.4f, 2.8f, -2.4f, 4.9f) + reflectiveCurveToRelative(0.8f, 3.4f, 2.5f, 4.5f) + curveToRelative(1.6f, 1.2f, 3.7f, 2.2f, 6.3f, 3.1f) + curveToRelative(2.5f, 0.9f, 5.2f, 1.8f, 8.1f, 2.9f) + curveToRelative(2.9f, 1f, 5.6f, 2.3f, 8.1f, 3.9f) + reflectiveCurveToRelative(4.6f, 3.6f, 6.3f, 6.1f) + curveToRelative(1.6f, 2.5f, 2.5f, 5.6f, 2.5f, 9.4f) + reflectiveCurveToRelative(-0.6f, 5.8f, -1.8f, 8.3f) + reflectiveCurveToRelative(-2.9f, 4.7f, -5.2f, 6.6f) + curveToRelative(-2.3f, 1.9f, -5f, 3.3f, -8.2f, 4.4f) + curveToRelative(-3.2f, 1.1f, -6.7f, 1.6f, -10.7f, 1.6f) + reflectiveCurveToRelative(-10.1f, -0.8f, -13.9f, -2.4f) + curveToRelative(-3.8f, -1.6f, -6.7f, -3.1f, -8.7f, -4.5f) + curveToRelative(-1.5f, -0.9f, -2.4f, -2f, -2.5f, -3.3f) + curveToRelative(-0.2f, -1.3f, 0.2f, -2.7f, 1.2f, -4.3f) + lineToRelative(1.6f, -2.4f) + curveToRelative(1f, -1.4f, 2.1f, -2.2f, 3.3f, -2.4f) + reflectiveCurveToRelative(2.6f, 0.2f, 4.3f, 1.1f) + curveToRelative(1.6f, 0.9f, 3.7f, 1.9f, 6.2f, 3f) + curveToRelative(2.5f, 1.1f, 5.5f, 1.7f, 9f, 1.7f) + reflectiveCurveToRelative(5.2f, -0.6f, 6.9f, -1.9f) + reflectiveCurveToRelative(2.5f, -2.9f, 2.5f, -5.1f) + reflectiveCurveToRelative(-0.8f, -3.3f, -2.5f, -4.5f) + curveToRelative(-1.6f, -1.1f, -3.7f, -2.1f, -6.3f, -3.1f) + curveToRelative(-2.5f, -0.9f, -5.2f, -1.9f, -8.1f, -3f) + curveToRelative(-2.9f, -1.1f, -5.6f, -2.4f, -8.1f, -4f) + curveToRelative(-2.5f, -1.6f, -4.6f, -3.6f, -6.3f, -6.1f) + curveToRelative(-1.6f, -2.5f, -2.5f, -5.7f, -2.5f, -9.6f) + reflectiveCurveToRelative(0.7f, -6.2f, 2.1f, -8.8f) + curveToRelative(1.4f, -2.6f, 3.2f, -4.7f, 5.6f, -6.4f) + reflectiveCurveToRelative(5.1f, -3f, 8.3f, -3.9f) + curveToRelative(3.2f, -0.9f, 6.5f, -1.3f, 10.1f, -1.3f) + close() + moveTo(81.3f, 495f) + curveToRelative(4.5f, 0f, 8.4f, 0.6f, 11.5f, 1.7f) + reflectiveCurveToRelative(5.6f, 2.2f, 7.4f, 3.2f) + curveToRelative(1.5f, 0.8f, 2.4f, 1.9f, 2.7f, 3.2f) + curveToRelative(0.3f, 1.3f, 0f, 2.7f, -0.7f, 4.3f) + lineToRelative(-1.3f, 2.4f) + curveToRelative(-0.8f, 1.6f, -1.8f, 2.5f, -3.1f, 2.8f) + reflectiveCurveToRelative(-2.7f, 0f, -4.4f, -0.7f) + curveToRelative(-1.5f, -0.7f, -3.3f, -1.4f, -5.5f, -2.2f) + curveToRelative(-2.1f, -0.8f, -4.6f, -1.1f, -7.5f, -1.1f) + reflectiveCurveToRelative(-5.2f, 0.6f, -6.8f, 1.7f) + curveToRelative(-1.6f, 1.2f, -2.4f, 2.8f, -2.4f, 4.9f) + reflectiveCurveToRelative(0.8f, 3.4f, 2.5f, 4.5f) + curveToRelative(1.6f, 1.2f, 3.7f, 2.2f, 6.3f, 3.1f) + curveToRelative(2.5f, 0.9f, 5.2f, 1.8f, 8.1f, 2.9f) + curveToRelative(2.9f, 1f, 5.6f, 2.3f, 8.1f, 3.9f) + curveToRelative(2.5f, 1.6f, 4.6f, 3.6f, 6.3f, 6.1f) + curveToRelative(1.6f, 2.5f, 2.5f, 5.6f, 2.5f, 9.4f) + reflectiveCurveToRelative(-0.6f, 5.8f, -1.8f, 8.3f) + reflectiveCurveToRelative(-2.9f, 4.7f, -5.2f, 6.6f) + curveToRelative(-2.3f, 1.9f, -5f, 3.3f, -8.2f, 4.4f) + curveToRelative(-3.2f, 1.1f, -6.7f, 1.6f, -10.7f, 1.6f) + reflectiveCurveToRelative(-10.1f, -0.8f, -13.9f, -2.4f) + curveToRelative(-3.8f, -1.6f, -6.7f, -3.1f, -8.7f, -4.5f) + curveToRelative(-1.5f, -0.9f, -2.4f, -2f, -2.5f, -3.3f) + curveToRelative(-0.2f, -1.3f, 0.2f, -2.7f, 1.2f, -4.3f) + lineToRelative(1.6f, -2.4f) + curveToRelative(1f, -1.4f, 2.1f, -2.2f, 3.3f, -2.4f) + curveToRelative(1.2f, -0.2f, 2.6f, 0.2f, 4.3f, 1.1f) + curveToRelative(1.6f, 0.9f, 3.7f, 1.9f, 6.2f, 3f) + curveToRelative(2.5f, 1.1f, 5.5f, 1.7f, 9f, 1.7f) + reflectiveCurveToRelative(5.2f, -0.6f, 6.9f, -1.9f) + curveToRelative(1.7f, -1.2f, 2.5f, -2.9f, 2.5f, -5.1f) + reflectiveCurveToRelative(-0.8f, -3.3f, -2.5f, -4.5f) + curveToRelative(-1.6f, -1.1f, -3.7f, -2.1f, -6.3f, -3.1f) + curveToRelative(-2.5f, -0.9f, -5.2f, -1.9f, -8.1f, -3f) + curveToRelative(-2.9f, -1.1f, -5.6f, -2.4f, -8.1f, -4f) + curveToRelative(-2.5f, -1.6f, -4.6f, -3.6f, -6.3f, -6.1f) + reflectiveCurveToRelative(-2.5f, -5.7f, -2.5f, -9.6f) + reflectiveCurveToRelative(0.7f, -6.2f, 2.1f, -8.8f) + curveToRelative(1.4f, -2.6f, 3.2f, -4.7f, 5.6f, -6.4f) + reflectiveCurveToRelative(5.1f, -3f, 8.3f, -3.9f) + curveToRelative(3.2f, -0.9f, 6.5f, -1.3f, 10.1f, -1.3f) + close() + moveTo(324.1f, 470f) + curveToRelative(3.7f, 0f, 5.6f, 1.9f, 5.6f, 5.6f) + verticalLineToRelative(27.4f) + curveToRelative(0f, 0.9f, 0f, 1.7f, 0f, 2.3f) + curveToRelative(0f, 0.7f, -0.1f, 1.3f, -0.2f, 1.8f) + curveToRelative(0f, 0.6f, -0.1f, 1.2f, -0.1f, 1.6f) + horizontalLineToRelative(0.3f) + curveToRelative(0.8f, -1.6f, 1.9f, -3.2f, 3.4f, -4.9f) + curveToRelative(1.5f, -1.6f, 3.2f, -3.1f, 5.2f, -4.5f) + curveToRelative(2f, -1.3f, 4.3f, -2.4f, 6.8f, -3.2f) + curveToRelative(2.5f, -0.8f, 5.3f, -1.2f, 8.2f, -1.2f) + curveToRelative(7.5f, 0f, 13.4f, 2f, 17.5f, 6.1f) + curveToRelative(4.1f, 4.1f, 6.2f, 10.6f, 6.2f, 19.7f) + verticalLineToRelative(38f) + curveToRelative(0f, 3.7f, -1.9f, 5.6f, -5.6f, 5.6f) + horizontalLineToRelative(-5.7f) + curveToRelative(-3.7f, 0f, -5.6f, -1.9f, -5.6f, -5.6f) + verticalLineToRelative(-34.6f) + curveToRelative(0f, -4.2f, -0.7f, -7.5f, -2.1f, -10f) + reflectiveCurveToRelative(-4.3f, -3.8f, -8.5f, -3.8f) + reflectiveCurveToRelative(-5.6f, 0.6f, -8.1f, 1.7f) + curveToRelative(-2.4f, 1.2f, -4.5f, 2.7f, -6.2f, 4.7f) + curveToRelative(-1.7f, 2f, -3f, 4.4f, -3.9f, 7.1f) + curveToRelative(-0.9f, 2.7f, -1.4f, 5.7f, -1.4f, 8.9f) + verticalLineToRelative(25.9f) + curveToRelative(0f, 3.7f, -1.9f, 5.6f, -5.6f, 5.6f) + horizontalLineToRelative(-5.7f) + curveToRelative(-3.7f, 0f, -5.6f, -1.9f, -5.6f, -5.6f) + verticalLineToRelative(-83.2f) + curveToRelative(0f, -3.7f, 1.9f, -5.6f, 5.6f, -5.6f) + horizontalLineToRelative(5.7f) + close() + moveTo(235.7f, 496.6f) + curveToRelative(3.6f, 0f, 5.5f, 1.9f, 5.5f, 5.6f) + verticalLineToRelative(56.6f) + curveToRelative(0f, 3.7f, -1.8f, 5.6f, -5.5f, 5.6f) + horizontalLineToRelative(-5.9f) + curveToRelative(-3.6f, 0f, -5.5f, -1.9f, -5.5f, -5.6f) + verticalLineToRelative(-56.6f) + curveToRelative(0f, -3.7f, 1.8f, -5.6f, 5.5f, -5.6f) + horizontalLineToRelative(5.9f) + close() + moveTo(122.7f, 496.6f) + curveToRelative(3.4f, 0f, 5.4f, 1.6f, 6f, 4.9f) + lineToRelative(10.9f, 39.6f) + curveToRelative(0.2f, 1f, 0.3f, 1.9f, 0.5f, 2.7f) + curveToRelative(0.1f, 0.8f, 0.3f, 1.6f, 0.5f, 2.3f) + curveToRelative(0.2f, 0.8f, 0.3f, 1.6f, 0.4f, 2.3f) + horizontalLineToRelative(0.3f) + curveToRelative(0f, -0.7f, 0.2f, -1.5f, 0.4f, -2.3f) + curveToRelative(0.2f, -0.7f, 0.3f, -1.5f, 0.5f, -2.3f) + curveToRelative(0.1f, -0.8f, 0.3f, -1.7f, 0.6f, -2.7f) + lineToRelative(11.5f, -39.6f) + curveToRelative(0.6f, -3.2f, 2.7f, -4.8f, 6.1f, -4.8f) + horizontalLineToRelative(5.1f) + curveToRelative(3.3f, 0f, 5.3f, 1.6f, 6.1f, 4.8f) + lineToRelative(11.3f, 39.6f) + curveToRelative(0.3f, 1f, 0.5f, 1.9f, 0.6f, 2.7f) + curveToRelative(0.1f, 0.8f, 0.3f, 1.6f, 0.5f, 2.3f) + curveToRelative(0.2f, 0.8f, 0.3f, 1.6f, 0.4f, 2.3f) + horizontalLineToRelative(0.3f) + curveToRelative(0f, -0.7f, 0.2f, -1.5f, 0.4f, -2.3f) + curveToRelative(0.2f, -0.7f, 0.3f, -1.5f, 0.5f, -2.3f) + curveToRelative(0.1f, -0.8f, 0.3f, -1.7f, 0.6f, -2.7f) + lineToRelative(10.8f, -39.6f) + curveToRelative(0.8f, -3.3f, 2.8f, -4.9f, 6.1f, -4.9f) + horizontalLineToRelative(6.1f) + curveToRelative(2f, 0f, 3.5f, 0.6f, 4.3f, 1.7f) + curveToRelative(0.8f, 1.2f, 0.9f, 2.7f, 0.4f, 4.5f) + lineToRelative(-17.4f, 56.9f) + curveToRelative(-0.9f, 3.1f, -3f, 4.7f, -6.3f, 4.7f) + horizontalLineToRelative(-8.9f) + curveToRelative(-3.4f, 0f, -5.5f, -1.6f, -6.3f, -4.8f) + lineToRelative(-10.3f, -33.9f) + curveToRelative(-0.3f, -0.9f, -0.5f, -1.8f, -0.7f, -2.7f) + reflectiveCurveToRelative(-0.4f, -1.7f, -0.5f, -2.4f) + curveToRelative(-0.2f, -0.8f, -0.3f, -1.6f, -0.4f, -2.3f) + horizontalLineToRelative(-0.3f) + curveToRelative(-0.2f, 0.7f, -0.4f, 1.5f, -0.5f, 2.3f) + curveToRelative(-0.2f, 0.7f, -0.4f, 1.5f, -0.5f, 2.4f) + curveToRelative(-0.2f, 0.9f, -0.4f, 1.8f, -0.7f, 2.7f) + lineToRelative(-10.3f, 33.9f) + curveToRelative(-0.8f, 3.2f, -2.8f, 4.8f, -6.1f, 4.8f) + horizontalLineToRelative(-9.2f) + curveToRelative(-3.2f, 0f, -5.2f, -1.6f, -6.1f, -4.7f) + lineToRelative(-17.6f, -56.9f) + curveToRelative(-0.5f, -1.9f, -0.4f, -3.4f, 0.5f, -4.5f) + curveToRelative(0.8f, -1.2f, 2.2f, -1.7f, 4.2f, -1.7f) + horizontalLineToRelative(6.4f) + close() + } + path( + fill = Brush.linearGradient( + colorStops = arrayOf( + 0f to Color(0xFFEF2131), + 1f to Color(0xFFFECF2C) + ), + start = Offset(237.8f, 289.7f), + end = Offset(177.74f, 104.45f) + ), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(119.3f, 399.2f) + curveToRelative(84.3f, 40.3f, 188.3f, 20.4f, 251.2f, -54.5f) + curveToRelative(74.5f, -88.8f, 62.9f, -221.1f, -25.8f, -295.5f) + lineToRelative(-59f, 70.3f) + curveToRelative(69.3f, 58.2f, 78.4f, 161.5f, 20.2f, 230.9f) + curveToRelative(-46.4f, 55.3f, -122.8f, 73.7f, -186.5f, 48.9f) + } + path( + fill = Brush.linearGradient( + colorStops = arrayOf( + 0f to Color(0xFFFBC52C), + 0.3f to Color(0xFFF87130), + 0.6f to Color(0xFFEF52E2), + 1f to Color(0xFF661EEC) + ), + start = Offset(379.9f, 129.59f), + end = Offset(243f, 399.77f) + ), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(119.3f, 399.2f) + curveToRelative(84.3f, 40.3f, 188.3f, 20.4f, 251.2f, -54.5f) + curveToRelative(7.7f, -9.2f, 14.5f, -18.8f, 20.3f, -28.8f) + curveToRelative(9.9f, -61.7f, -11.9f, -126.9f, -63.2f, -169.9f) + curveToRelative(-13f, -10.9f, -27.2f, -19.8f, -41.9f, -26.5f) + curveToRelative(69.3f, 58.2f, 78.4f, 161.5f, 20.2f, 230.9f) + curveToRelative(-46.4f, 55.3f, -122.8f, 73.7f, -186.5f, 48.9f) + } + path( + fill = Brush.linearGradient( + colorStops = arrayOf( + 0f to Color(0xFF78F6D8), + 0.3f to Color(0xFF77D1F6), + 0.6f to Color(0xFF70A4F3), + 1f to Color(0xFF661EEC) + ), + start = Offset(118.21f, 92.5f), + end = Offset(178.27f, 277.75f) + ), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(300.3f, 20.4f) + curveTo(216f, -19.9f, 111.9f, 0f, 49.1f, 74.9f) + curveToRelative(-74.5f, 88.8f, -62.9f, 221.1f, 25.8f, 295.5f) + lineToRelative(59f, -70.3f) + curveToRelative(-69.3f, -58.2f, -78.4f, -161.5f, -20.2f, -230.9f) + curveTo(160.2f, 14f, 236.6f, -4.5f, 300.3f, 20.4f) + } + path( + fill = Brush.linearGradient( + colorStops = arrayOf( + 0f to Color(0xFF536EED), + 0.2f to Color(0xFF54C3EC), + 0.6f to Color(0xFF64D769), + 1f to Color(0xFFFECF2C) + ), + start = Offset(95.13f, 220.03f), + end = Offset(232.03f, -50.15f) + ), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(300.3f, 20.4f) + curveTo(216f, -19.9f, 111.9f, 0f, 49.1f, 74.9f) + curveToRelative(-7.7f, 9.2f, -14.5f, 18.8f, -20.3f, 28.8f) + curveToRelative(-9.9f, 61.7f, 11.9f, 126.9f, 63.2f, 169.9f) + curveToRelative(13f, 10.9f, 27.2f, 19.8f, 41.9f, 26.5f) + curveToRelative(-69.3f, -58.2f, -78.4f, -161.5f, -20.2f, -230.9f) + curveTo(160.2f, 14f, 236.6f, -4.5f, 300.3f, 20.4f) + } + }.build() + + return _Swish!! + } + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + imageVector = HedvigIcons.Swish, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((24.0).dp) + .height((24.0).dp), + ) + } + } +} + + +@Suppress("ObjectPropertyName") +private var _Swish: ImageVector? = null diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts index 105feaab33..ce5712c76a 100644 --- a/app/feature/feature-payin-account/build.gradle.kts +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -31,4 +31,5 @@ dependencies { implementation(projects.navigationCompose) implementation(projects.navigationComposeTyped) implementation(projects.navigationCore) + implementation(libs.zXing) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 14d4076617..d02ff4491a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -55,7 +55,7 @@ internal class GetPayinAccountUseCase( accountNumber = accountNumber, bankName = bankName, isPending = isPending, - isDefault = isDefault + isDefault = isDefault, ) } @@ -65,7 +65,7 @@ internal class GetPayinAccountUseCase( delivery = invoiceDetails?.delivery?.toDeliveryString(), email = invoiceDetails?.email, isPending = isPending, - isDefault = isDefault + isDefault = isDefault, ) } @@ -136,10 +136,15 @@ internal sealed interface PayinAccount { } private fun PaymentMethodInvoiceDelivery?.toDeliveryString(): String? { - return when(this) { - PaymentMethodInvoiceDelivery.KIVRA -> "Kivra" //todo - PaymentMethodInvoiceDelivery.MAIL -> "Email" //todo + return when (this) { + PaymentMethodInvoiceDelivery.KIVRA -> "Kivra" + + // todo + PaymentMethodInvoiceDelivery.MAIL -> "Email" + + // todo PaymentMethodInvoiceDelivery.UNKNOWN__ -> "" + else -> null } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt index 98dd14c735..f21b715793 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -16,7 +16,6 @@ internal interface SetAsDefaultUseCase { suspend fun invoke(provider: MemberPaymentProvider): Either } - internal class SetAsDefaultUseCaseImpl( private val apolloClient: ApolloClient, private val getPayinAccountUseCase: GetPayinAccountUseCase, @@ -26,7 +25,7 @@ internal class SetAsDefaultUseCaseImpl( logcat { "Mariia: Starting SetAsDefaultUseCaseImpl with provider: $provider" } apolloClient .mutation(SetAsDefaultPayinMutation(provider)) - .safeExecute(::ErrorMessage) //tosdoL user safeExecuteAllowingPartialResponses + .safeExecute(::ErrorMessage) // tosdoL user safeExecuteAllowingPartialResponses .fold( ifLeft = { logcat { "Mariia: SetAsDefaultUseCaseImpl error: $it" } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt index 46ffbd9a84..9a1759b6ef 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -13,7 +13,6 @@ import octopus.SetupSwishPayinMutation import octopus.type.PaymentMethodSetupStatus import octopus.type.PaymentMethodSetupSwishInput - internal interface SetupSwishPayinUseCase { suspend fun invoke(phoneNumber: String): Either } @@ -36,7 +35,6 @@ internal class SetupSwishPayinUseCaseImpl( } networkCacheManager.clearCache() SetupSwishResponse.Success(output.url) - } PaymentMethodSetupStatus.PENDING -> { @@ -58,9 +56,10 @@ internal class SetupSwishPayinUseCaseImpl( } } - internal sealed interface SetupSwishResponse { data class Failure(val error: ErrorMessage) : SetupSwishResponse + data class Success(val url: String?) : SetupSwishResponse + data class Pending(val url: String?) : SetupSwishResponse } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt index a3a6fd5a83..516f144333 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt @@ -31,7 +31,7 @@ val payinAccountModule = module { viewModel { PayinAccountOverviewViewModel( get(), - get(), + get(), ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt index bdecc2e0a8..57b0d62d57 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt @@ -78,7 +78,7 @@ fun NavGraphBuilder.payinAccountGraph( openUrl = { navController.typedPopBackStack(inclusive = true) openUrl(it) - } + }, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 99e6dba1b5..c4a9772040 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -72,7 +72,7 @@ private fun PayinAccountOverviewScreen( setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, ) { HedvigScaffold( - topAppBarText = "Billing account", //todo! + topAppBarText = "Billing account", // todo! navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -125,14 +125,16 @@ private fun PayoutAccountContent( if (availablePayinMethods.isNotEmpty()) { Spacer(Modifier.weight(1f)) EmptyState( - text = "You haven’t added a billing method yet. Add one to pay for your insurance.", //todo + text = "You haven’t added a billing method yet. Add one to pay for your insurance.", // todo description = null, iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, ) } } else { - HedvigText("Active billing methods",//todo - modifier = Modifier.padding(horizontal = 16.dp)) + HedvigText( + "Active billing methods", // todo + modifier = Modifier.padding(horizontal = 16.dp), + ) Spacer(Modifier.height(16.dp)) Column( verticalArrangement = Arrangement.spacedBy(8.dp), @@ -154,14 +156,14 @@ private fun PayoutAccountContent( }, modifier = Modifier.padding(horizontal = 16.dp), loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.SWISH, - isPending = method.isPending + isPending = method.isPending, ) } is PayinAccount.Trustly -> { val accountNumber = formatBankAccountNumber(method.clearingNumber, method.accountNumber, method.bankName) CurrentPayinMethodRow( - label = "Autogiro", //todo + label = "Autogiro", // todo text = if (method.isPending && accountNumber.isBlank()) { stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) } else { @@ -173,7 +175,7 @@ private fun PayoutAccountContent( }, modifier = Modifier.padding(horizontal = 16.dp), loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.TRUSTLY, - isPending = method.isPending + isPending = method.isPending, ) } @@ -187,7 +189,7 @@ private fun PayoutAccountContent( }, modifier = Modifier.padding(horizontal = 16.dp), loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.INVOICE, - isPending = method.isPending + isPending = method.isPending, ) } } @@ -213,7 +215,7 @@ private fun PayoutAccountContent( Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { if (currentMethods.any { it.isPending }) { HedvigNotificationCard( - message = "You have just added or changed a billing method, it will appear here soon.", //todo + message = "You have just added or changed a billing method, it will appear here soon.", // todo priority = NotificationPriority.Info, modifier = Modifier .fillMaxWidth() @@ -222,7 +224,7 @@ private fun PayoutAccountContent( } if (availablePayinMethods.isNotEmpty()) { HedvigButton( - text = "Add a billing method", //todo! + text = "Add a billing method", // todo! onClick = onConnectPayinMethodClicked, enabled = true, modifier = Modifier @@ -268,7 +270,7 @@ private fun CurrentPayinMethodRow( ) { if (isDefault) { HighlightLabel( - labelText = "Default", //todo + labelText = "Default", // todo size = HighlightLabelDefaults.HighLightSize.Small, color = HighlightLabelDefaults.HighlightColor.Green( HighlightLabelDefaults.HighlightShade.LIGHT, @@ -278,7 +280,7 @@ private fun CurrentPayinMethodRow( if (!isPending) { HedvigButtonGhostWithBorder( size = ButtonDefaults.ButtonSize.Small, - text = "Choose as default", //todo + text = "Choose as default", // todo onClick = onClick, isLoading = loadingDefaultProvider, ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index d855caa2ab..c54498db31 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -19,16 +19,16 @@ import octopus.type.MemberPaymentProvider internal class PayinAccountOverviewViewModel( getPayinAccountUseCase: GetPayinAccountUseCase, - setAsDefaultUseCase: SetAsDefaultUseCase + setAsDefaultUseCase: SetAsDefaultUseCase, ) : MoleculeViewModel( - PayinAccountOverviewUiState.Loading, - PayinAccountOverviewPresenter(getPayinAccountUseCase, setAsDefaultUseCase), -) + PayinAccountOverviewUiState.Loading, + PayinAccountOverviewPresenter(getPayinAccountUseCase, setAsDefaultUseCase), + ) internal sealed interface PayinAccountOverviewEvent { data object Retry : PayinAccountOverviewEvent - data class SetDefaultMethod(val provider: MemberPaymentProvider): PayinAccountOverviewEvent + data class SetDefaultMethod(val provider: MemberPaymentProvider) : PayinAccountOverviewEvent } internal sealed interface PayinAccountOverviewUiState { @@ -39,14 +39,14 @@ internal sealed interface PayinAccountOverviewUiState { data class Content( val currentMethods: List, val availablePayinMethods: List, - val loadingDefaultProvider: MemberPaymentProvider? =null, - val setDefaultProviderError: ErrorMessage? = null + val loadingDefaultProvider: MemberPaymentProvider? = null, + val setDefaultProviderError: ErrorMessage? = null, ) : PayinAccountOverviewUiState } internal class PayinAccountOverviewPresenter( private val getPayinAccountUseCase: GetPayinAccountUseCase, - private val setAsDefaultUseCase: SetAsDefaultUseCase + private val setAsDefaultUseCase: SetAsDefaultUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( @@ -62,25 +62,24 @@ internal class PayinAccountOverviewPresenter( ifLeft = { uiState = PayinAccountOverviewUiState.Error }, ifRight = { data -> uiState = PayinAccountOverviewUiState.Content( - currentMethods = data.currentMethods, - availablePayinMethods = data.availablePayinMethods, - ) - + currentMethods = data.currentMethods, + availablePayinMethods = data.availablePayinMethods, + ) }, ) } LaunchedEffect(providerToSetAsDefault) { val provider = providerToSetAsDefault - if (provider!=null) { + if (provider != null) { logcat { "Mariia: Starting LaunchedEffect(providerToSetAsDefault) with provider: $provider" } - val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@LaunchedEffect + val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@LaunchedEffect uiState = currentState.copy(loadingDefaultProvider = provider) setAsDefaultUseCase.invoke(provider).fold( ifLeft = { providerToSetAsDefault = null uiState = currentState.copy(setDefaultProviderError = it) - }, + }, ifRight = { data -> providerToSetAsDefault = null uiState = PayinAccountOverviewUiState.Content( @@ -94,9 +93,12 @@ internal class PayinAccountOverviewPresenter( CollectEvents { event -> when (event) { - PayinAccountOverviewEvent.Retry -> loadIteration++ + PayinAccountOverviewEvent.Retry -> { + loadIteration++ + } + is PayinAccountOverviewEvent.SetDefaultMethod -> { - val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@CollectEvents + val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@CollectEvents uiState = currentState.copy(setDefaultProviderError = null) providerToSetAsDefault = event.provider } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 17da4a8c69..c3b6e1de0f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -39,7 +39,7 @@ internal fun SelectPayinMethodDestination( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = "Select billing method to add", //todo + topAppBarText = "Select billing method to add", // todo navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -50,7 +50,7 @@ internal fun SelectPayinMethodDestination( MemberPaymentProvider.TRUSTLY -> { PayinMethodRow( title = stringResource(Res.string.trustly), - subtitle = "Connect your bank via Trustly", //todo + subtitle = "Connect your bank via Trustly", // todo onClick = onTrustlySelected, ) } @@ -58,7 +58,7 @@ internal fun SelectPayinMethodDestination( MemberPaymentProvider.SWISH -> { PayinMethodRow( title = stringResource(Res.string.swish), - subtitle = "Monthly auto-payments through Swish", //todo + subtitle = "Monthly auto-payments through Swish", // todo onClick = onSwishSelected, ) } @@ -66,7 +66,7 @@ internal fun SelectPayinMethodDestination( MemberPaymentProvider.INVOICE -> { PayinMethodRow( title = stringResource(Res.string.PAYMENTS_INVOICE), - subtitle = stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION), //todo + subtitle = stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION), // todo onClick = onInvoiceSelected, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index 007cc46e50..d1059266e9 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -72,18 +72,20 @@ private fun SetupInvoicePayinScreen( navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { - //todo: some text here?? + // todo: some text here?? Column( modifier = Modifier .weight(1f) .fillMaxWidth() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { - HedvigText("You can choose invoice as your billing method. " + - "You will then get a monthly invoice via Kivra or email if you don't have Kivra account.", - textAlign = TextAlign.Center) + HedvigText( + "You can choose invoice as your billing method. " + + "You will then get a monthly invoice via Kivra or email if you don't have Kivra account.", + textAlign = TextAlign.Center, + ) } AnimatedVisibility( visible = uiState.errorMessage != null, @@ -101,7 +103,7 @@ private fun SetupInvoicePayinScreen( } Spacer(Modifier.height(16.dp)) HedvigButton( - text = "Set invoice as billing method", //todo + text = "Set invoice as billing method", // todo onClick = onConnect, enabled = !uiState.isLoading, isLoading = uiState.isLoading, @@ -113,7 +115,6 @@ private fun SetupInvoicePayinScreen( } } - @Composable @HedvigPreview private fun PreviewPayoutAccountOverviewScreen() { @@ -126,7 +127,9 @@ private fun PreviewPayoutAccountOverviewScreen() { showSuccessSnackBar = false, ), globalSnackBarState = GlobalSnackBarState(), - {}, {}, {}, + {}, + {}, + {}, ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt index c5364c5f65..43057acb91 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt @@ -15,9 +15,9 @@ import com.hedvig.android.molecule.public.MoleculeViewModel internal class SetupInvoicePayinViewModel( setupInvoicePayinUseCase: SetupInvoicePayinUseCase, ) : MoleculeViewModel( - SetupInvoicePayinUiState(false, null, false), - SetupInvoicePayinPresenter(setupInvoicePayinUseCase), -) + SetupInvoicePayinUiState(false, null, false), + SetupInvoicePayinPresenter(setupInvoicePayinUseCase), + ) internal sealed interface SetupInvoicePayoutEvent { data object Connect : SetupInvoicePayoutEvent @@ -32,7 +32,7 @@ internal data class SetupInvoicePayinUiState( ) internal class SetupInvoicePayinPresenter( - private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, + private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/QRCode.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/QRCode.kt new file mode 100644 index 0000000000..4d65e4355a --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/QRCode.kt @@ -0,0 +1,59 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import android.annotation.SuppressLint +import android.graphics.Bitmap +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize +import com.google.zxing.BarcodeFormat +import com.google.zxing.common.BitMatrix +import com.google.zxing.qrcode.QRCodeWriter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@SuppressLint("ProduceStateDoesNotAssignValue") +@Composable +internal fun QRCode(token: String, modifier: Modifier = Modifier) { + var intSize: IntSize? by remember { mutableStateOf(null) } + val painter by produceState(ColorPainter(Color.Transparent), intSize, token) { + val size = intSize + if (size == null) { + value = ColorPainter(Color.Transparent) + return@produceState + } + val bitmapPainter: BitmapPainter = withContext(Dispatchers.Default) { + val bitMatrix: BitMatrix = QRCodeWriter().encode( + token, + BarcodeFormat.QR_CODE, + size.width, + size.height, + ) + val bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.RGB_565) + for (x in 0 until size.width) { + for (y in 0 until size.height) { + val color = if (bitMatrix.get(x, y)) android.graphics.Color.BLACK else android.graphics.Color.WHITE + bitmap.setPixel(x, y, color) + } + } + BitmapPainter(bitmap.asImageBitmap()) + } + value = bitmapPainter + } + Image( + painter, + null, + modifier.onSizeChanged { intSize = it }, + ) +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index a4ef479bfa..26ef20cec2 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -3,26 +3,42 @@ package com.hedvig.android.feature.payin.account.ui.setupswish import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.runtime.simulateHotReload import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.EmptyState import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.GlobalSnackBarState @@ -62,11 +78,14 @@ internal fun SetupSwishPayinDestination( onSuccessfullyConnected() }, navigateUp = navigateUp, - openUrl = openUrl + openUrl = openUrl, + updateText = { + viewModel.emit(SetupSwishPayoutEvent.UpdateText(it)) + }, ) } -//todo fetch payment methods continuously to see if it already not in pending state +// todo fetch payment methods continuously to see if it already not in pending state @Composable private fun SetupSwishPayoutScreen( @@ -76,7 +95,9 @@ private fun SetupSwishPayoutScreen( showedSnackBar: () -> Unit, navigateUp: () -> Unit, openUrl: (String) -> Unit, + updateText: (String) -> Unit, ) { + val focusManager = LocalFocusManager.current val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) LaunchedEffect(uiState.showSuccessSnackBar) { if (!uiState.showSuccessSnackBar) return@LaunchedEffect @@ -98,54 +119,130 @@ private fun SetupSwishPayoutScreen( iconStyle = EmptyStateDefaults.EmptyStateIconStyle.ERROR, ) } - if (uiState.successUrl!=null) { + AnimatedVisibility(uiState.successUrl != null) { + if (uiState.successUrl != null) Column( - modifier= Modifier.fillMaxWidth().padding(horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { EmptyState( text = "The process started", - description = "Please confirm in Swish app", - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SUCCESS_WITH_WARNING, + description = "Please give your approval in the Swish app", + modifier = Modifier.fillMaxWidth(), + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SWISH, ) Spacer(Modifier.height(16.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + QRCode( + token = uiState.successUrl, + modifier = Modifier + .size(180.dp), + ) + } + Spacer(Modifier.height(32.dp)) HedvigButton( "Open Swish", enabled = true, onClick = { openUrl(uiState.successUrl) - } + }, + buttonStyle = ButtonDefaults.ButtonStyle.PrimaryAlt, + buttonSize = ButtonDefaults.ButtonSize.Medium, ) } - } Spacer(Modifier.weight(1f)) - Column(Modifier.padding(horizontal = 16.dp)) { - HedvigTextField( - state = uiState.phoneNumberState, - labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), - textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Phone, - ), - modifier = Modifier.fillMaxWidth(), - ) + AnimatedVisibility(uiState.successUrl == null) { + Column { + Spacer(Modifier.height(16.dp)) + Column(Modifier.padding(horizontal = 16.dp)) { + var input by remember { mutableStateOf(uiState.phoneNumber) } + val mask = "000-000-00-00" + val maskColor = HedvigTheme.colorScheme.textTertiary + val visualTransformation = ChipIdVisualTransformation(mask, maskColor) + val interactionSource = remember { MutableInteractionSource() } + HedvigTextField( + text = input, + labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), + textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone, + ), + modifier = Modifier.fillMaxWidth(), + visualTransformation = visualTransformation, + errorState = HedvigTextFieldDefaults.ErrorState.NoError, + interactionSource = interactionSource, + onValueChange = { + if (it.length <= 15) { + updateText(it) + input = it + } + }, + ) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_save_button), + onClick = { + focusManager.clearFocus() + onSave() + }, + enabled = !uiState.isLoading && + uiState.phoneNumber.length >= 8 && + uiState.phoneNumber.length <= 15, + isLoading = uiState.isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } } Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(Res.string.general_save_button), - onClick = onSave, - enabled = !uiState.isLoading && - uiState.phoneNumberState.text.length >= 10 && - uiState.successUrl==null, - isLoading = uiState.isLoading, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - Spacer(Modifier.height(16.dp)) + } +} + +private class ChipIdVisualTransformation( + private val mask: String, + private val maskColor: Color, +) : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText { + val trimmed = if (text.text.length >= 15) text.text.substring(0..14) else text.text + + val annotatedString = buildAnnotatedString { + for (i in trimmed.indices) { + append(trimmed[i]) + if (i in listOf(2, 5, 7)) { + append("-") + } + } + withStyle(SpanStyle(color = maskColor)) { + append(mask.takeLast((mask.length - length).coerceAtLeast(0))) + } + } + + val personalNumberOffsetTranslator = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + return when { + offset <= 2 -> offset + offset <= 5 -> offset + 1 + offset <= 7 -> offset + 2 + else -> offset + 3 + } + } + + override fun transformedToOriginal(offset: Int): Int { + return when { + offset <= 3 -> offset + offset <= 7 -> offset - 1 + offset <= 10 -> offset - 2 + else -> offset - 3 + }.coerceAtMost(text.length) + } + } + return TransformedText(annotatedString, personalNumberOffsetTranslator) } } @@ -162,39 +259,39 @@ private fun PreviewSetupSwishPayinScreen( onSave = {}, showedSnackBar = {}, navigateUp = {}, - {} + {}, + {}, ) } } } - private class SetupSwishPayinUiStateProvider : CollectionPreviewParameterProvider( listOf( SetupSwishPayoutUiState( - phoneNumberState = TextFieldState("287334432273"), + phoneNumber = "287334432273", isLoading = false, error = null, showSuccessSnackBar = false, ), SetupSwishPayoutUiState( - phoneNumberState = TextFieldState(), + phoneNumber = "", isLoading = false, error = ErrorMessage(), showSuccessSnackBar = false, ), SetupSwishPayoutUiState( - phoneNumberState = TextFieldState("837286428"), + phoneNumber = "837286428", isLoading = true, error = null, showSuccessSnackBar = false, ), SetupSwishPayoutUiState( - phoneNumberState = TextFieldState("83728644428"), + phoneNumber = "83728644428", isLoading = false, error = null, showSuccessSnackBar = false, - successUrl = "hwdjhew" - ) + successUrl = "hwdjhew", + ), ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt index a2659100cb..14e24ca1b0 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -17,30 +17,31 @@ import com.hedvig.android.molecule.public.MoleculeViewModel internal class SetupSwishPayinViewModel( setupSwishPayoutUseCase: SetupSwishPayinUseCase, ) : MoleculeViewModel( - SetupSwishPayoutUiState( - phoneNumberState = TextFieldState(), - isLoading = false, - error = null, - showSuccessSnackBar = false, - successUrl = null - ), - SetupSwishPayoutPresenter(setupSwishPayoutUseCase), -) + SetupSwishPayoutUiState( + phoneNumber = "", + isLoading = false, + error = null, + showSuccessSnackBar = false, + successUrl = null, + ), + SetupSwishPayoutPresenter(setupSwishPayoutUseCase), + ) internal sealed interface SetupSwishPayoutEvent { data object Save : SetupSwishPayoutEvent data object ShowedSnackBar : SetupSwishPayoutEvent -} -internal data class SetupSwishPayoutUiState ( - val showSuccessSnackBar: Boolean, - val successUrl: String? = null, - val phoneNumberState: TextFieldState, - val isLoading: Boolean, - val error: ErrorMessage?, - val resultIsPending: Boolean = false + data class UpdateText(val newText: String) : SetupSwishPayoutEvent +} +internal data class SetupSwishPayoutUiState( + val showSuccessSnackBar: Boolean, + val successUrl: String? = null, + val phoneNumber: String, + val isLoading: Boolean, + val error: ErrorMessage?, + val resultIsPending: Boolean = false, ) internal class SetupSwishPayoutPresenter( @@ -50,7 +51,7 @@ internal class SetupSwishPayoutPresenter( override fun MoleculePresenterScope.present( lastState: SetupSwishPayoutUiState, ): SetupSwishPayoutUiState { - val phoneNumberState = remember { lastState.phoneNumberState } + var phoneNumberState by remember { mutableStateOf(lastState.phoneNumber) } var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } var showSuccessSnackBar by remember { mutableStateOf(false) } @@ -61,12 +62,11 @@ internal class SetupSwishPayoutPresenter( val currentSave = saveIteration if (currentSave != null) { - val currentState = uiState LaunchedEffect(currentSave) { isLoading = true errorMessage = null resultIsPending = false - setupSwishPayoutUseCase.invoke(phoneNumberState.text.toString()).fold( + setupSwishPayoutUseCase.invoke(phoneNumberState).fold( ifLeft = { isLoading = false errorMessage = it @@ -75,14 +75,16 @@ internal class SetupSwishPayoutPresenter( ifRight = { result -> isLoading = false saveIteration = null - when(result) { + when (result) { is SetupSwishResponse.Failure -> { errorMessage = result.error } + is SetupSwishResponse.Pending -> { urlToRedirect = result.url resultIsPending = true } + is SetupSwishResponse.Success -> { showSuccessSnackBar = true urlToRedirect = result.url @@ -97,22 +99,26 @@ internal class SetupSwishPayoutPresenter( when (event) { SetupSwishPayoutEvent.Save -> { if (!isLoading) { - saveIteration = phoneNumberState.text.toString() + saveIteration = phoneNumberState } } SetupSwishPayoutEvent.ShowedSnackBar -> { showSuccessSnackBar = false } + + is SetupSwishPayoutEvent.UpdateText -> { + phoneNumberState = event.newText + } } } return SetupSwishPayoutUiState( - phoneNumberState = phoneNumberState, + phoneNumber = phoneNumberState, isLoading = isLoading, error = errorMessage, showSuccessSnackBar = showSuccessSnackBar, - successUrl = urlToRedirect + successUrl = urlToRedirect, ) } } From fff8a290b25600ec90c09065ce28c8bfcfee532a Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 21 May 2026 14:21:46 +0200 Subject: [PATCH 14/75] don't pass on the error message if not user error --- .../feature/payin/account/data/SetupSwishPayinUseCase.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt index 9a1759b6ef..da4761e1cd 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -3,6 +3,7 @@ package com.hedvig.android.feature.payin.account.data import arrow.core.Either import arrow.core.raise.context.bind import arrow.core.raise.context.either +import arrow.core.raise.context.raise import com.apollographql.apollo.ApolloClient import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.NetworkCacheManager @@ -24,7 +25,12 @@ internal class SetupSwishPayinUseCaseImpl( override suspend fun invoke(phoneNumber: String): Either = either { val result = apolloClient .mutation(SetupSwishPayinMutation(PaymentMethodSetupSwishInput(phoneNumber))) - .safeExecute(::ErrorMessage) + .safeExecute { + logcat { + "Mariia: SetupSwishPayinMutation error: $it" + } + raise(ErrorMessage()) + } .bind() val output = result.paymentMethodSetupSwishPayin From e7291a321ac739a24ca39196ec3a139846d76c2a Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 02:13:50 +0200 Subject: [PATCH 15/75] safeExecuteAllowingPartialResponses --- .../account/data/SetupSwishPayinUseCase.kt | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt index da4761e1cd..1088ea52a5 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -1,13 +1,12 @@ package com.hedvig.android.feature.payin.account.data import arrow.core.Either -import arrow.core.raise.context.bind import arrow.core.raise.context.either import arrow.core.raise.context.raise import com.apollographql.apollo.ApolloClient import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.NetworkCacheManager -import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.apollo.safeExecuteAllowingPartialResponses import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.logger.logcat import octopus.SetupSwishPayinMutation @@ -23,42 +22,47 @@ internal class SetupSwishPayinUseCaseImpl( private val networkCacheManager: NetworkCacheManager, ) : SetupSwishPayinUseCase { override suspend fun invoke(phoneNumber: String): Either = either { - val result = apolloClient + apolloClient .mutation(SetupSwishPayinMutation(PaymentMethodSetupSwishInput(phoneNumber))) - .safeExecute { - logcat { - "Mariia: SetupSwishPayinMutation error: $it" - } - raise(ErrorMessage()) - } - .bind() + .safeExecuteAllowingPartialResponses() + .fold( + fa = { error -> + logcat { "SetupSwishPayinMutation error: $error" } + raise(ErrorMessage()) + }, + fb = { result -> + val output = result.paymentMethodSetupSwishPayin + when (output.status) { + PaymentMethodSetupStatus.ACTIVE -> { + logcat { + "Mariia: SetupSwishPayinMutation ACTIVE url: $output.url" + } + networkCacheManager.clearCache() + SetupSwishResponse.Success(output.url) + } - val output = result.paymentMethodSetupSwishPayin - when (output.status) { - PaymentMethodSetupStatus.ACTIVE -> { - logcat { - "Mariia: SetupSwishPayinMutation ACTIVE url: $output.url" - } - networkCacheManager.clearCache() - SetupSwishResponse.Success(output.url) - } + PaymentMethodSetupStatus.PENDING -> { + logcat { + "Mariia: SetupSwishPayinMutation PENDING url: $output.url" + } + networkCacheManager.clearCache() + SetupSwishResponse.Pending(output.url) + } - PaymentMethodSetupStatus.PENDING -> { - logcat { - "Mariia: SetupSwishPayinMutation PENDING url: $output.url" - } - networkCacheManager.clearCache() - SetupSwishResponse.Pending(output.url) - } - - PaymentMethodSetupStatus.FAILED, PaymentMethodSetupStatus.UNKNOWN__ -> { - logcat { - "SetupSwishPayinMutation failed with: output.error?.message" - } - val userMessage = output.error?.message - SetupSwishResponse.Failure(ErrorMessage(userMessage)) - } - } + PaymentMethodSetupStatus.FAILED, PaymentMethodSetupStatus.UNKNOWN__ -> { + logcat { + "SetupSwishPayinMutation failed with: output.error?.message" + } + val userMessage = output.error?.message + SetupSwishResponse.Failure(ErrorMessage(userMessage)) + } + } + }, + fab = { errors, _ -> + logcat { "SetupSwishPayinMutation dataa with errors: $errors" } + raise(ErrorMessage()) + }, + ) } } From cb10b86a1b21d99f5c3b048bb4b282e98860aba1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 02:51:52 +0200 Subject: [PATCH 16/75] icons in overview --- .../design/system/hedvig/icon/Autogiro.kt | 168 +++++++++ .../system/hedvig/icon/colored/Kivra.kt | 333 ++++++++++++++++++ .../account/data/GetPayinAccountUseCase.kt | 6 +- .../PayinAccountOverviewDestination.kt | 88 +++-- 4 files changed, 569 insertions(+), 26 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt new file mode 100644 index 0000000000..00aab9af82 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt @@ -0,0 +1,168 @@ +package com.hedvig.android.design.system.hedvig.icon + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.icon.colored.Swish + +val HedvigIcons.Autogiro: ImageVector + get() { + if (_NounCardSubscriptionPayment8118834 != null) { + return _NounCardSubscriptionPayment8118834!! + } + _NounCardSubscriptionPayment8118834 = ImageVector.Builder( + name = "NounCardSubscriptionPayment8118834", + defaultWidth = 110.dp, + defaultHeight = 135.dp, + viewportWidth = 110f, + viewportHeight = 135f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveToRelative(93.54f, 57.92f) + verticalLineToRelative(-6.67f) + curveToRelative(0f, -4.71f, -0f, -8.07f, -0.22f, -10.69f) + curveToRelative(-0.21f, -2.59f, -0.61f, -4.19f, -1.26f, -5.46f) + curveToRelative(-1.3f, -2.55f, -3.37f, -4.62f, -5.92f, -5.92f) + curveToRelative(-1.27f, -0.64f, -2.87f, -1.05f, -5.46f, -1.26f) + curveToRelative(-2.62f, -0.21f, -5.98f, -0.22f, -10.69f, -0.22f) + horizontalLineToRelative(-30f) + curveToRelative(-4.71f, 0f, -8.07f, 0f, -10.69f, 0.22f) + curveToRelative(-2.59f, 0.21f, -4.19f, 0.61f, -5.46f, 1.26f) + curveToRelative(-2.55f, 1.3f, -4.62f, 3.37f, -5.92f, 5.92f) + curveToRelative(-0.64f, 1.27f, -1.05f, 2.87f, -1.26f, 5.46f) + curveToRelative(-0.21f, 2.62f, -0.22f, 5.98f, -0.22f, 10.69f) + verticalLineToRelative(13.33f) + curveToRelative(0f, 4.72f, 0f, 8.07f, 0.22f, 10.69f) + curveToRelative(0.21f, 2.59f, 0.61f, 4.19f, 1.26f, 5.46f) + curveToRelative(1.3f, 2.55f, 3.37f, 4.62f, 5.92f, 5.92f) + curveToRelative(1.27f, 0.64f, 2.87f, 1.05f, 5.46f, 1.26f) + curveToRelative(2.62f, 0.21f, 5.98f, 0.21f, 10.69f, 0.21f) + horizontalLineToRelative(17.08f) + curveToRelative(1.73f, 0f, 3.13f, 1.4f, 3.13f, 3.13f) + reflectiveCurveToRelative(-1.4f, 3.13f, -3.13f, 3.13f) + horizontalLineToRelative(-17.08f) + curveToRelative(-4.61f, 0f, -8.27f, 0f, -11.2f, -0.23f) + curveToRelative(-2.97f, -0.25f, -5.49f, -0.75f, -7.79f, -1.92f) + curveToRelative(-3.72f, -1.9f, -6.75f, -4.93f, -8.65f, -8.65f) + curveToRelative(-1.17f, -2.3f, -1.68f, -4.81f, -1.92f, -7.78f) + curveToRelative(-0.24f, -2.94f, -0.24f, -6.59f, -0.24f, -11.2f) + verticalLineToRelative(-13.33f) + curveToRelative(0f, -4.61f, -0f, -8.27f, 0.24f, -11.2f) + curveToRelative(0.24f, -2.97f, 0.75f, -5.49f, 1.92f, -7.79f) + curveToRelative(1.9f, -3.72f, 4.93f, -6.75f, 8.65f, -8.65f) + curveToRelative(2.3f, -1.17f, 4.81f, -1.68f, 7.79f, -1.92f) + curveToRelative(2.93f, -0.24f, 6.59f, -0.24f, 11.2f, -0.24f) + horizontalLineToRelative(30f) + curveToRelative(4.61f, 0f, 8.27f, -0f, 11.2f, 0.24f) + curveToRelative(2.97f, 0.24f, 5.49f, 0.75f, 7.79f, 1.92f) + curveToRelative(3.72f, 1.9f, 6.75f, 4.93f, 8.65f, 8.65f) + curveToRelative(1.17f, 2.3f, 1.68f, 4.81f, 1.92f, 7.79f) + curveToRelative(0.24f, 2.93f, 0.24f, 6.59f, 0.24f, 11.2f) + verticalLineToRelative(6.67f) + curveToRelative(0f, 1.72f, -1.4f, 3.13f, -3.13f, 3.13f) + curveToRelative(-1.73f, 0f, -3.13f, -1.4f, -3.13f, -3.13f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveToRelative(94.58f, 42.29f) + verticalLineToRelative(6.25f) + horizontalLineToRelative(-79.16f) + verticalLineToRelative(-6.25f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveToRelative(46.67f, 71.46f) + curveToRelative(1.72f, 0f, 3.13f, 1.4f, 3.13f, 3.13f) + curveToRelative(0f, 1.73f, -1.4f, 3.13f, -3.13f, 3.13f) + horizontalLineToRelative(-20.84f) + curveToRelative(-1.72f, 0f, -3.13f, -1.4f, -3.13f, -3.13f) + curveToRelative(0f, -1.72f, 1.4f, -3.13f, 3.13f, -3.13f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveToRelative(93.54f, 76.67f) + curveToRelative(0f, -2.88f, -2.33f, -5.21f, -5.21f, -5.21f) + horizontalLineToRelative(-18.75f) + curveToRelative(-1.72f, 0f, -3.13f, -1.4f, -3.13f, -3.13f) + curveToRelative(0f, -1.72f, 1.4f, -3.13f, 3.13f, -3.13f) + horizontalLineToRelative(18.75f) + curveToRelative(6.33f, 0f, 11.46f, 5.13f, 11.46f, 11.46f) + curveToRelative(0f, 1.72f, -1.4f, 3.13f, -3.13f, 3.13f) + curveToRelative(-1.73f, 0f, -3.13f, -1.4f, -3.13f, -3.13f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveToRelative(71.54f, 59.88f) + curveToRelative(1.22f, -1.22f, 3.2f, -1.22f, 4.42f, 0f) + curveToRelative(1.22f, 1.22f, 1.22f, 3.2f, 0f, 4.42f) + lineToRelative(-4.04f, 4.04f) + lineToRelative(4.04f, 4.04f) + curveToRelative(1.22f, 1.22f, 1.22f, 3.2f, 0f, 4.42f) + curveToRelative(-1.22f, 1.22f, -3.2f, 1.22f, -4.42f, 0f) + lineToRelative(-6.25f, -6.25f) + curveToRelative(-1.22f, -1.22f, -1.22f, -3.2f, 0f, -4.42f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveToRelative(70.63f, 82.92f) + curveToRelative(0f, 2.88f, 2.33f, 5.21f, 5.21f, 5.21f) + horizontalLineToRelative(18.75f) + curveToRelative(1.73f, 0f, 3.13f, 1.4f, 3.13f, 3.13f) + reflectiveCurveToRelative(-1.4f, 3.13f, -3.13f, 3.13f) + horizontalLineToRelative(-18.75f) + curveToRelative(-6.33f, 0f, -11.46f, -5.13f, -11.46f, -11.46f) + curveToRelative(0f, -1.73f, 1.4f, -3.13f, 3.13f, -3.13f) + reflectiveCurveToRelative(3.13f, 1.4f, 3.13f, 3.13f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveToRelative(92.63f, 99.71f) + curveToRelative(-1.22f, 1.22f, -3.2f, 1.22f, -4.42f, 0f) + curveToRelative(-1.22f, -1.22f, -1.22f, -3.2f, 0f, -4.42f) + lineToRelative(4.04f, -4.04f) + lineToRelative(-4.04f, -4.04f) + curveToRelative(-1.22f, -1.22f, -1.22f, -3.2f, 0f, -4.42f) + curveToRelative(1.22f, -1.22f, 3.2f, -1.22f, 4.42f, 0f) + lineToRelative(6.25f, 6.25f) + curveToRelative(1.22f, 1.22f, 1.22f, 3.2f, 0f, 4.42f) + close() + } + }.build() + + return _NounCardSubscriptionPayment8118834!! + } + +@Suppress("ObjectPropertyName") +private var _NounCardSubscriptionPayment8118834: ImageVector? = null + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = HedvigIcons.Autogiro, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((24.0).dp) + .height((24.0).dp), + ) + } + } +} diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt new file mode 100644 index 0000000000..3df5b7911a --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt @@ -0,0 +1,333 @@ +package com.hedvig.android.design.system.hedvig.icon.colored + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons + + +val HedvigIcons.Kivra: ImageVector + get() { + if (_KivraLogo1920X1080Green != null) { + return _KivraLogo1920X1080Green!! + } + _KivraLogo1920X1080Green = ImageVector.Builder( + name = "KivraLogo1920X1080Green", + defaultWidth = 1912.dp, + defaultHeight = 1015.dp, + viewportWidth = 1912f, + viewportHeight = 1015f + ).apply { + path(fill = SolidColor(Color(0xFF75C72E))) { + moveTo(1274f, 292f) + curveTo(1274.9f, 292.2f, 1275.7f, 292.4f, 1276.6f, 292.6f) + curveTo(1303.7f, 299.6f, 1327.7f, 312.8f, 1348f, 332f) + curveTo(1348.8f, 332.7f, 1349.6f, 333.5f, 1350.5f, 334.3f) + curveTo(1376.4f, 358.9f, 1389.9f, 394.6f, 1391.2f, 429.9f) + curveTo(1392.1f, 467.8f, 1380.2f, 502.3f, 1354f, 530f) + curveTo(1353f, 531.1f, 1353f, 531.1f, 1352f, 532.2f) + curveTo(1338.7f, 546.2f, 1319.6f, 558.6f, 1301f, 564f) + curveTo(1300f, 564f, 1299f, 564f, 1298f, 564f) + curveTo(1300.6f, 566.9f, 1303.7f, 568.9f, 1307f, 571.1f) + curveTo(1351.8f, 601.8f, 1380f, 651.6f, 1391.3f, 704f) + curveTo(1391.5f, 704.7f, 1391.6f, 705.5f, 1391.8f, 706.2f) + curveTo(1393.1f, 712.5f, 1393.5f, 718.4f, 1394f, 725f) + curveTo(1366.3f, 725f, 1338.6f, 725f, 1310f, 725f) + curveTo(1308.3f, 716.8f, 1306.7f, 708.5f, 1305f, 700f) + curveTo(1297.1f, 673.4f, 1285.3f, 649.9f, 1267f, 629f) + curveTo(1266.2f, 628.1f, 1265.4f, 627.2f, 1264.6f, 626.2f) + curveTo(1258.9f, 619.7f, 1252.8f, 614.3f, 1246f, 609f) + curveTo(1245.1f, 608.3f, 1244.2f, 607.5f, 1243.2f, 606.8f) + curveTo(1226.4f, 593.6f, 1206.7f, 584.4f, 1186.2f, 578.9f) + curveTo(1183f, 578f, 1183f, 578f, 1182f, 577f) + curveTo(1181.9f, 575.5f, 1181.9f, 574f, 1181.9f, 572.5f) + curveTo(1181.9f, 571f, 1181.9f, 571f, 1181.9f, 569.6f) + curveTo(1181.9f, 568.5f, 1181.9f, 567.5f, 1181.9f, 566.4f) + curveTo(1181.9f, 565.3f, 1181.9f, 564.3f, 1181.9f, 563.2f) + curveTo(1181.9f, 559.8f, 1181.9f, 556.4f, 1181.9f, 552.9f) + curveTo(1181.9f, 550.6f, 1181.9f, 548.3f, 1182f, 546f) + curveTo(1182f, 540.3f, 1182f, 534.7f, 1182f, 529f) + curveTo(1183.2f, 528.9f, 1183.2f, 528.9f, 1184.4f, 528.9f) + curveTo(1201.2f, 528f, 1217.6f, 526.8f, 1234f, 523f) + curveTo(1235.1f, 522.7f, 1235.1f, 522.7f, 1236.3f, 522.5f) + curveTo(1259.9f, 516.8f, 1282.9f, 504.6f, 1296.3f, 483.7f) + curveTo(1308.5f, 462.6f, 1311f, 439.4f, 1305f, 416f) + curveTo(1299.4f, 397.5f, 1287.8f, 382.6f, 1271f, 373f) + curveTo(1250.9f, 362.6f, 1225.7f, 360.1f, 1203.9f, 366.6f) + curveTo(1183f, 373.7f, 1166.3f, 387.1f, 1156f, 406.7f) + curveTo(1147.2f, 424.7f, 1144.9f, 443.9f, 1144.8f, 463.8f) + curveTo(1144.8f, 464.9f, 1144.8f, 466.1f, 1144.8f, 467.2f) + curveTo(1144.8f, 471f, 1144.8f, 474.7f, 1144.8f, 478.4f) + curveTo(1144.8f, 481.1f, 1144.8f, 483.8f, 1144.8f, 486.5f) + curveTo(1144.7f, 492.3f, 1144.7f, 498.1f, 1144.7f, 503.8f) + curveTo(1144.7f, 512.2f, 1144.7f, 520.5f, 1144.6f, 528.9f) + curveTo(1144.6f, 542.4f, 1144.5f, 556f, 1144.5f, 569.5f) + curveTo(1144.5f, 582.7f, 1144.4f, 595.9f, 1144.4f, 609f) + curveTo(1144.4f, 609.8f, 1144.4f, 610.6f, 1144.4f, 611.5f) + curveTo(1144.4f, 615.6f, 1144.3f, 619.6f, 1144.3f, 623.7f) + curveTo(1144.2f, 657.5f, 1144.1f, 691.2f, 1144f, 725f) + curveTo(1117.6f, 725f, 1091.2f, 725f, 1064f, 725f) + curveTo(1063.9f, 688f, 1063.9f, 651f, 1063.8f, 614f) + curveTo(1063.8f, 609.7f, 1063.8f, 605.3f, 1063.8f, 600.9f) + curveTo(1063.8f, 600f, 1063.8f, 599.2f, 1063.8f, 598.3f) + curveTo(1063.8f, 584.2f, 1063.8f, 570.1f, 1063.8f, 556.1f) + curveTo(1063.7f, 541.6f, 1063.7f, 527.2f, 1063.7f, 512.7f) + curveTo(1063.7f, 503.8f, 1063.7f, 494.9f, 1063.7f, 486f) + curveTo(1063.7f, 479.9f, 1063.7f, 473.7f, 1063.7f, 467.6f) + curveTo(1063.7f, 464.1f, 1063.7f, 460.6f, 1063.6f, 457.1f) + curveTo(1063.6f, 438f, 1064.2f, 419.5f, 1069.8f, 401.2f) + curveTo(1070.2f, 399.9f, 1070.2f, 399.9f, 1070.6f, 398.6f) + curveTo(1078.1f, 374f, 1090.2f, 353.5f, 1108f, 335f) + curveTo(1108.7f, 334.2f, 1109.4f, 333.4f, 1110.2f, 332.6f) + curveTo(1126.1f, 315.8f, 1147.4f, 304.6f, 1169f, 297f) + curveTo(1169.7f, 296.7f, 1170.4f, 296.5f, 1171.2f, 296.2f) + curveTo(1176f, 294.6f, 1181f, 293.2f, 1186f, 292f) + curveTo(1186.8f, 291.8f, 1187.6f, 291.6f, 1188.4f, 291.4f) + curveTo(1215.5f, 285.1f, 1247.1f, 285.3f, 1274f, 292f) + close() + } + path(fill = SolidColor(Color(0xFF75C72E))) { + moveTo(141f, 295f) + curveTo(167.4f, 295f, 193.8f, 295f, 221f, 295f) + curveTo(221.3f, 360.3f, 221.7f, 425.7f, 222f, 493f) + curveTo(225f, 489.4f, 227.9f, 485.7f, 231f, 482f) + curveTo(232.6f, 480.1f, 234.3f, 478.3f, 236f, 476.5f) + curveTo(239.3f, 472.9f, 242.4f, 469.3f, 245.5f, 465.5f) + curveTo(249.3f, 460.7f, 253.4f, 456.2f, 257.5f, 451.8f) + curveTo(259.9f, 449.1f, 262.2f, 446.3f, 264.5f, 443.5f) + curveTo(268.3f, 438.7f, 272.4f, 434.2f, 276.5f, 429.8f) + curveTo(278.9f, 427.1f, 281.2f, 424.3f, 283.5f, 421.5f) + curveTo(287.3f, 416.7f, 291.4f, 412.2f, 295.5f, 407.8f) + curveTo(297.9f, 405.1f, 300.2f, 402.3f, 302.5f, 399.5f) + curveTo(306.3f, 394.7f, 310.4f, 390.2f, 314.5f, 385.7f) + curveTo(316.9f, 383.1f, 319.2f, 380.4f, 321.4f, 377.6f) + curveTo(325.1f, 373f, 328.9f, 368.7f, 332.9f, 364.5f) + curveTo(336.7f, 360.4f, 340.2f, 356.1f, 343.8f, 351.7f) + curveTo(346.1f, 348.9f, 348.5f, 346.2f, 351f, 343.5f) + curveTo(354.3f, 339.9f, 357.4f, 336.3f, 360.5f, 332.5f) + curveTo(363.6f, 328.7f, 366.7f, 325.1f, 370f, 321.5f) + curveTo(374f, 317.1f, 377.8f, 312.6f, 381.5f, 308f) + curveTo(382.3f, 307f, 383.2f, 306f, 384f, 305f) + curveTo(384.5f, 304.2f, 384.9f, 303.4f, 385.4f, 302.6f) + curveTo(388f, 298.8f, 390.5f, 295.6f, 395.1f, 294.4f) + curveTo(401.8f, 293.6f, 408.4f, 293.9f, 415.1f, 294f) + curveTo(417.3f, 294f, 419.4f, 294.1f, 421.6f, 294.1f) + curveTo(427.3f, 294.1f, 432.9f, 294.2f, 438.6f, 294.3f) + curveTo(444.4f, 294.4f, 450.2f, 294.5f, 456f, 294.5f) + curveTo(467.3f, 294.6f, 478.7f, 294.8f, 490f, 295f) + curveTo(488.3f, 298.7f, 486.1f, 301.3f, 483.3f, 304.3f) + curveTo(482.4f, 305.2f, 481.5f, 306.2f, 480.6f, 307.2f) + curveTo(479.3f, 308.6f, 479.3f, 308.6f, 478f, 310f) + curveTo(474.7f, 313.7f, 471.5f, 317.4f, 468.3f, 321.2f) + curveTo(462.5f, 328f, 456.5f, 334.7f, 450.4f, 341.3f) + curveTo(446.3f, 345.9f, 442.3f, 350.5f, 438.3f, 355.1f) + curveTo(432.8f, 361.7f, 427.1f, 368.1f, 421.3f, 374.3f) + curveTo(418.2f, 377.6f, 415.4f, 380.9f, 412.6f, 384.4f) + curveTo(408f, 390.2f, 403f, 395.5f, 398f, 400.8f) + curveTo(393.6f, 405.5f, 389.5f, 410.3f, 385.3f, 415.1f) + curveTo(379.8f, 421.7f, 374.1f, 428.1f, 368.3f, 434.3f) + curveTo(364.5f, 438.4f, 360.9f, 442.7f, 357.4f, 447.1f) + curveTo(354f, 451.2f, 350.3f, 455.2f, 346.5f, 459.1f) + curveTo(345.5f, 460.2f, 344.4f, 461.3f, 343.4f, 462.4f) + curveTo(342.1f, 463.9f, 340.7f, 465.3f, 339.3f, 466.6f) + curveTo(337.7f, 470.8f, 338.3f, 471.7f, 340f, 475.7f) + curveTo(341.1f, 478f, 342.3f, 480.3f, 343.4f, 482.6f) + curveTo(344.1f, 483.9f, 344.7f, 485.1f, 345.3f, 486.4f) + curveTo(346.5f, 489f, 347.8f, 491.6f, 349.1f, 494.1f) + curveTo(352f, 500f, 354.8f, 506f, 357.5f, 512f) + curveTo(358.6f, 514.3f, 359.7f, 516.7f, 360.8f, 519f) + curveTo(363.4f, 524.8f, 366.1f, 530.6f, 368.8f, 536.4f) + curveTo(370.4f, 539.9f, 372.1f, 543.4f, 373.7f, 546.8f) + curveTo(375.7f, 551.1f, 377.6f, 555.4f, 379.6f, 559.7f) + curveTo(404.1f, 616.1f, 404.1f, 616.1f, 451.5f, 651.9f) + curveTo(459.7f, 654.5f, 467.4f, 655.3f, 476f, 655.2f) + curveTo(476.9f, 655.2f, 477.8f, 655.2f, 478.8f, 655.2f) + curveTo(485f, 655.1f, 490.8f, 654.6f, 497f, 654f) + curveTo(497f, 677.1f, 497f, 700.2f, 497f, 724f) + curveTo(485.2f, 726.1f, 474.5f, 727.5f, 462.7f, 727.4f) + curveTo(461.9f, 727.4f, 461.2f, 727.4f, 460.4f, 727.4f) + curveTo(448.2f, 727.4f, 436.7f, 726.5f, 425f, 723f) + curveTo(423.9f, 722.7f, 423.9f, 722.7f, 422.8f, 722.4f) + curveTo(401.1f, 715.8f, 383.2f, 703.6f, 367f, 688f) + curveTo(366.2f, 687.2f, 365.3f, 686.5f, 364.5f, 685.7f) + curveTo(333.2f, 656f, 316.1f, 612.6f, 298.4f, 574.3f) + curveTo(296f, 569f, 293.5f, 563.8f, 291.1f, 558.6f) + curveTo(290.4f, 557.2f, 289.8f, 555.9f, 289.1f, 554.5f) + curveTo(288.2f, 552.4f, 287.2f, 550.4f, 286.2f, 548.4f) + curveTo(285.4f, 546.5f, 284.5f, 544.6f, 283.6f, 542.8f) + curveTo(282.8f, 541.2f, 282.8f, 541.2f, 282.1f, 539.5f) + curveTo(281f, 537f, 281f, 537f, 281f, 535f) + curveTo(276f, 538.9f, 272.1f, 543.2f, 268f, 548f) + curveTo(266.1f, 550.3f, 264.2f, 552.5f, 262.3f, 554.8f) + curveTo(261.7f, 555.4f, 261.2f, 556f, 260.7f, 556.6f) + curveTo(256.4f, 561.6f, 251.9f, 566.5f, 247.5f, 571.3f) + curveTo(244.8f, 574.2f, 242.3f, 577.1f, 239.7f, 580f) + curveTo(237f, 583.1f, 234.2f, 586.2f, 231.4f, 589.3f) + curveTo(230.6f, 590.2f, 229.8f, 591.1f, 229f, 592f) + curveTo(228.4f, 592.5f, 227.9f, 593f, 227.3f, 593.6f) + curveTo(222.3f, 598.4f, 221.2f, 602.1f, 221.1f, 608.9f) + curveTo(221.1f, 610.1f, 221.1f, 611.2f, 221.1f, 612.4f) + curveTo(221.1f, 613.6f, 221.1f, 614.8f, 221.1f, 616.1f) + curveTo(221.1f, 619.4f, 221.1f, 622.7f, 221.1f, 626.1f) + curveTo(221.1f, 629.5f, 221.1f, 633f, 221.1f, 636.5f) + curveTo(221f, 643.1f, 221f, 649.7f, 221.1f, 656.2f) + curveTo(221.1f, 664.5f, 221f, 672.7f, 221f, 681f) + curveTo(221f, 695.6f, 221f, 710.3f, 221f, 725f) + curveTo(194.6f, 725f, 168.2f, 725f, 141f, 725f) + curveTo(141f, 583.1f, 141f, 441.2f, 141f, 295f) + close() + } + path(fill = SolidColor(Color(0xFF75C72E))) { + moveTo(1567f, 295f) + curveTo(1596.7f, 295f, 1626.4f, 295f, 1657f, 295f) + curveTo(1661.8f, 303.4f, 1666.6f, 311.8f, 1671f, 320.4f) + curveTo(1675f, 328.2f, 1675f, 328.2f, 1679.1f, 335.8f) + curveTo(1685.1f, 346.8f, 1690.3f, 358.2f, 1695.5f, 369.6f) + curveTo(1697.3f, 373.4f, 1699.1f, 377.3f, 1700.9f, 381.1f) + curveTo(1716.7f, 414.6f, 1730.4f, 449.1f, 1743f, 484f) + curveTo(1743.5f, 485.3f, 1743.5f, 485.3f, 1743.9f, 486.6f) + curveTo(1768.6f, 554.9f, 1787.8f, 624.7f, 1802.1f, 695.9f) + curveTo(1802.3f, 696.8f, 1802.5f, 697.8f, 1802.7f, 698.8f) + curveTo(1803.3f, 701.5f, 1803.8f, 704.2f, 1804.3f, 706.9f) + curveTo(1804.5f, 707.7f, 1804.6f, 708.5f, 1804.8f, 709.3f) + curveTo(1805.8f, 714.6f, 1806.4f, 719.7f, 1807f, 725f) + curveTo(1780.3f, 725f, 1753.5f, 725f, 1726f, 725f) + curveTo(1720.4f, 695.4f, 1720.4f, 695.4f, 1718.7f, 686.3f) + curveTo(1716.1f, 672.4f, 1713.3f, 658.5f, 1710.2f, 644.6f) + curveTo(1710f, 643.9f, 1709.9f, 643.3f, 1709.7f, 642.6f) + curveTo(1708.1f, 635.2f, 1708.1f, 635.2f, 1706f, 628f) + curveTo(1705.2f, 628.1f, 1704.4f, 628.3f, 1703.6f, 628.4f) + curveTo(1687.4f, 631.3f, 1671.4f, 633.9f, 1655f, 635f) + curveTo(1653.9f, 635.1f, 1652.7f, 635.2f, 1651.5f, 635.3f) + curveTo(1638.4f, 636.2f, 1625.4f, 636.2f, 1612.3f, 636.2f) + curveTo(1610.5f, 636.2f, 1610.5f, 636.2f, 1608.7f, 636.2f) + curveTo(1577.4f, 636.1f, 1546.8f, 633.8f, 1516f, 628f) + curveTo(1515.9f, 629.1f, 1515.8f, 630.2f, 1515.6f, 631.4f) + curveTo(1515f, 636.2f, 1514f, 640.9f, 1513f, 645.7f) + curveTo(1512.5f, 647.6f, 1512.1f, 649.5f, 1511.7f, 651.4f) + curveTo(1511.5f, 652.4f, 1511.3f, 653.4f, 1511f, 654.4f) + curveTo(1506.9f, 673.2f, 1503.2f, 692f, 1500.1f, 711f) + curveTo(1499.9f, 711.8f, 1499.8f, 712.6f, 1499.7f, 713.4f) + curveTo(1499.4f, 714.9f, 1499.2f, 716.4f, 1498.9f, 717.9f) + curveTo(1498.1f, 722.8f, 1498.1f, 722.8f, 1497f, 725f) + curveTo(1470.3f, 725f, 1443.5f, 725f, 1416f, 725f) + curveTo(1424.2f, 680.4f, 1424.2f, 680.4f, 1427.8f, 664.8f) + curveTo(1428.1f, 663.4f, 1428.1f, 663.4f, 1428.4f, 662f) + curveTo(1448.5f, 572.7f, 1477.2f, 485.7f, 1531f, 364f) + curveTo(1531.6f, 362.7f, 1532.3f, 361.3f, 1532.9f, 360f) + curveTo(1543.5f, 337.8f, 1554.8f, 316.3f, 1567f, 295f) + close() + moveTo(1611f, 367f) + curveTo(1600.2f, 388f, 1590.3f, 409.2f, 1580.9f, 430.9f) + curveTo(1580f, 432.9f, 1579.1f, 435f, 1578.3f, 437f) + curveTo(1563.9f, 470f, 1552f, 503.8f, 1541f, 538f) + curveTo(1540.8f, 538.7f, 1540.5f, 539.5f, 1540.3f, 540.2f) + curveTo(1539.6f, 542.4f, 1538.9f, 544.6f, 1538.2f, 546.9f) + curveTo(1537.8f, 547.9f, 1537.8f, 547.9f, 1537.5f, 548.9f) + curveTo(1536.6f, 551.7f, 1536f, 554f, 1536f, 557f) + curveTo(1539.5f, 557.6f, 1543.1f, 558.3f, 1546.6f, 558.9f) + curveTo(1547.6f, 559f, 1548.5f, 559.2f, 1549.5f, 559.4f) + curveTo(1569.9f, 562.9f, 1590.3f, 563.4f, 1611f, 563.3f) + curveTo(1612.9f, 563.3f, 1612.9f, 563.3f, 1614.9f, 563.3f) + curveTo(1633.8f, 563.3f, 1652.3f, 562.9f, 1671f, 560f) + curveTo(1671.9f, 559.9f, 1672.9f, 559.7f, 1673.8f, 559.6f) + curveTo(1678.6f, 558.8f, 1683.3f, 558f, 1688f, 557f) + curveTo(1671.9f, 500.9f, 1650.4f, 446.8f, 1625.6f, 394f) + curveTo(1625.1f, 392.9f, 1625.1f, 392.9f, 1624.6f, 391.8f) + curveTo(1620.7f, 383.4f, 1616.6f, 375.1f, 1612f, 367f) + curveTo(1611.7f, 367f, 1611.3f, 367f, 1611f, 367f) + close() + } + path(fill = SolidColor(Color(0xFF75C72E))) { + moveTo(657f, 295f) + curveTo(683.7f, 295f, 710.5f, 295f, 738f, 295f) + curveTo(739.9f, 305.1f, 739.9f, 305.1f, 741.9f, 315.5f) + curveTo(759.9f, 407.5f, 785.9f, 497.9f, 845.1f, 637.5f) + curveTo(845.5f, 638.4f, 845.9f, 639.2f, 846.3f, 640f) + curveTo(846.6f, 640.8f, 846.9f, 641.5f, 847.3f, 642.2f) + curveTo(848f, 644f, 848f, 644f, 848f, 646f) + curveTo(848.7f, 646f, 849.3f, 646f, 850f, 646f) + curveTo(850.3f, 645.2f, 850.5f, 644.3f, 850.8f, 643.5f) + curveTo(852.1f, 639.8f, 853.6f, 636.2f, 855.3f, 632.6f) + curveTo(856f, 631.2f, 856.6f, 629.7f, 857.3f, 628.2f) + curveTo(857.8f, 627f, 857.8f, 627f, 858.4f, 625.8f) + curveTo(874.2f, 591f, 888.4f, 555.6f, 901.1f, 519.5f) + curveTo(902f, 517f, 902.9f, 514.5f, 903.8f, 512f) + curveTo(927.1f, 446.2f, 944.6f, 378.5f, 957.8f, 310f) + curveTo(958.8f, 305f, 959.9f, 300f, 961f, 295f) + curveTo(987.4f, 295f, 1013.8f, 295f, 1041f, 295f) + curveTo(1040.4f, 301.7f, 1039.8f, 308.2f, 1038.5f, 314.8f) + curveTo(1038.4f, 315.6f, 1038.2f, 316.4f, 1038.1f, 317.2f) + curveTo(1037.6f, 319.8f, 1037.1f, 322.5f, 1036.6f, 325.1f) + curveTo(1036.4f, 326.5f, 1036.4f, 326.5f, 1036.1f, 327.9f) + curveTo(1023.6f, 394.5f, 1005.7f, 459.8f, 983.7f, 523.9f) + curveTo(983f, 526f, 982.3f, 528.1f, 981.6f, 530.2f) + curveTo(966.6f, 574.1f, 948.9f, 617.1f, 929f, 659f) + curveTo(928.7f, 659.6f, 928.4f, 660.2f, 928.1f, 660.8f) + curveTo(919.9f, 678.2f, 911.5f, 695.3f, 902f, 712f) + curveTo(901.4f, 713f, 901.4f, 713f, 900.8f, 714.1f) + curveTo(895.4f, 723.6f, 895.4f, 723.6f, 894f, 725f) + curveTo(891.2f, 725.1f, 888.4f, 725.1f, 885.5f, 725.1f) + curveTo(884.6f, 725.1f, 883.8f, 725.1f, 882.8f, 725.1f) + curveTo(879.9f, 725.1f, 876.9f, 725.1f, 874f, 725.1f) + curveTo(872f, 725.1f, 869.9f, 725.1f, 867.9f, 725.1f) + curveTo(862.5f, 725.1f, 857.1f, 725.1f, 851.7f, 725.1f) + curveTo(846.3f, 725.1f, 840.8f, 725.1f, 835.3f, 725f) + curveTo(824.5f, 725f, 813.8f, 725f, 803f, 725f) + curveTo(797.5f, 715.3f, 792.1f, 705.5f, 787.1f, 695.6f) + curveTo(786.6f, 694.6f, 786.1f, 693.6f, 785.6f, 692.6f) + curveTo(784f, 689.4f, 782.4f, 686.3f, 780.9f, 683.1f) + curveTo(780.3f, 682f, 779.8f, 680.9f, 779.2f, 679.8f) + curveTo(769.9f, 661.1f, 761.1f, 642.3f, 752.9f, 623.1f) + curveTo(751.7f, 620.3f, 750.5f, 617.6f, 749.3f, 614.9f) + curveTo(713.8f, 533.2f, 687f, 447.6f, 659.4f, 314.6f) + curveTo(659.3f, 313.9f, 659.2f, 313.2f, 659f, 312.4f) + curveTo(657f, 300.9f, 657f, 300.9f, 657f, 295f) + close() + } + path(fill = SolidColor(Color(0xFF76C72E))) { + moveTo(534f, 295f) + curveTo(560.7f, 295f, 587.5f, 295f, 615f, 295f) + curveTo(615f, 436.9f, 615f, 578.8f, 615f, 725f) + curveTo(588.3f, 725f, 561.5f, 725f, 534f, 725f) + curveTo(534f, 583.1f, 534f, 441.2f, 534f, 295f) + close() + } + }.build() + + return _KivraLogo1920X1080Green!! + } + +@Suppress("ObjectPropertyName") +private var _KivraLogo1920X1080Green: ImageVector? = null + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + imageVector = HedvigIcons.Kivra, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((24.0).dp) + .height((24.0).dp), + ) + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index d02ff4491a..3071232b66 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -62,7 +62,7 @@ internal class GetPayinAccountUseCase( MemberPaymentProvider.INVOICE -> { val invoiceDetails = method.details?.asPaymentMethodInvoiceDetails() PayinAccount.Invoice( - delivery = invoiceDetails?.delivery?.toDeliveryString(), + delivery = invoiceDetails?.delivery, email = invoiceDetails?.email, isPending = isPending, isDefault = isDefault, @@ -128,14 +128,14 @@ internal sealed interface PayinAccount { ) : PayinAccount data class Invoice( - val delivery: String?, + val delivery: PaymentMethodInvoiceDelivery?, val email: String?, override val isPending: Boolean, override val isDefault: Boolean, ) : PayinAccount } -private fun PaymentMethodInvoiceDelivery?.toDeliveryString(): String? { +fun PaymentMethodInvoiceDelivery?.toDeliveryString(): String? { return when (this) { PaymentMethodInvoiceDelivery.KIVRA -> "Kivra" diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index c4a9772040..cb343a9995 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -1,5 +1,6 @@ package com.hedvig.android.feature.payin.account.ui.overview +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,6 +9,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -34,15 +37,22 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HighlightLabel import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken +import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.icon.Autogiro +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.toDeliveryString import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res import hedvig.resources.something_went_wrong import hedvig.resources.swish import octopus.type.MemberPaymentProvider +import octopus.type.PaymentMethodInvoiceDelivery import org.jetbrains.compose.resources.stringResource @Composable @@ -151,12 +161,11 @@ private fun PayoutAccountContent( phoneNumber }, isDefault = method.isDefault, - onClick = { - setAsDefaultPayinMethod(MemberPaymentProvider.SWISH) - }, + onClick = setAsDefaultPayinMethod, modifier = Modifier.padding(horizontal = 16.dp), - loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.SWISH, + loadingDefaultProvider = loadingDefaultProvider, isPending = method.isPending, + provider = MemberPaymentProvider.SWISH, ) } @@ -170,26 +179,24 @@ private fun PayoutAccountContent( accountNumber }, isDefault = method.isDefault, - onClick = { - setAsDefaultPayinMethod(MemberPaymentProvider.TRUSTLY) - }, + onClick = setAsDefaultPayinMethod, modifier = Modifier.padding(horizontal = 16.dp), - loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.TRUSTLY, + loadingDefaultProvider = loadingDefaultProvider, isPending = method.isPending, + provider = MemberPaymentProvider.TRUSTLY, ) } is PayinAccount.Invoice -> { CurrentPayinMethodRow( stringResource(Res.string.PAYMENTS_INVOICE), - method.delivery ?: "", + method.delivery?.toDeliveryString() ?: "", isDefault = method.isDefault, - onClick = { - setAsDefaultPayinMethod(MemberPaymentProvider.INVOICE) - }, + onClick = setAsDefaultPayinMethod, modifier = Modifier.padding(horizontal = 16.dp), - loadingDefaultProvider = loadingDefaultProvider == MemberPaymentProvider.INVOICE, + loadingDefaultProvider = loadingDefaultProvider, isPending = method.isPending, + provider = MemberPaymentProvider.INVOICE, ) } } @@ -243,8 +250,9 @@ private fun CurrentPayinMethodRow( text: String, isDefault: Boolean, isPending: Boolean, - loadingDefaultProvider: Boolean, - onClick: () -> Unit, + loadingDefaultProvider: MemberPaymentProvider?, + onClick: (MemberPaymentProvider) -> Unit, + provider: MemberPaymentProvider, modifier: Modifier = Modifier, ) { HedvigCard( @@ -255,12 +263,38 @@ private fun CurrentPayinMethodRow( .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), startSlot = { - Column { - HedvigText(text = label) - HedvigText( - text = text, - color = HedvigTheme.colorScheme.textSecondary, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> Icon( + HedvigIcons.Autogiro, + null, //todo + modifier = Modifier.size(32.dp), + ) + + MemberPaymentProvider.SWISH -> Image( + HedvigIcons.Swish, + null, //todo + modifier = Modifier.size(32.dp), + ) + + MemberPaymentProvider.INVOICE -> Image( + HedvigIcons.Kivra, + null, //todo + modifier = Modifier.size(32.dp), + ) + else -> {} + } + + Spacer(Modifier.width(16.dp)) + Column { + HedvigText(text = label) + HedvigText( + text = text, + color = HedvigTheme.colorScheme.textSecondary, + ) + } } }, endSlot = { @@ -281,8 +315,10 @@ private fun CurrentPayinMethodRow( HedvigButtonGhostWithBorder( size = ButtonDefaults.ButtonSize.Small, text = "Choose as default", // todo - onClick = onClick, - isLoading = loadingDefaultProvider, + onClick = { + onClick(provider) + }, + isLoading = loadingDefaultProvider == provider, ) } } @@ -382,6 +418,12 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr isPending = false, isDefault = false, ), + PayinAccount.Invoice ( + delivery = PaymentMethodInvoiceDelivery.KIVRA, + isPending = false, + isDefault = false, + email = "" + ), ), availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), ), From f0da87aff185d1610d685a323377bd46f0c85e08 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 03:17:55 +0200 Subject: [PATCH 17/75] icons in select --- .../SelectPayinMethodDestination.kt | 130 ++++++++++++------ .../setupswish/SetupSwishPayinDestination.kt | 4 +- .../MemberPaymentDetailsViewModel.kt | 4 + 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index c3b6e1de0f..9147695057 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -1,14 +1,18 @@ package com.hedvig.android.feature.payin.account.ui.selectmethod -import androidx.compose.foundation.clickable +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigCard @@ -16,14 +20,14 @@ import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface -import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION -import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE +import com.hedvig.android.design.system.hedvig.icon.Autogiro +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYOUT_METHOD_INVOICE_DESCRIPTION -import hedvig.resources.PAYOUT_METHOD_SWISH_DESCRIPTION -import hedvig.resources.PAYOUT_METHOD_TRUSTLY_DESCRIPTION -import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD import hedvig.resources.Res import hedvig.resources.swish import hedvig.resources.trustly @@ -46,33 +50,12 @@ internal fun SelectPayinMethodDestination( Spacer(Modifier.height(8.dp)) Column(Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { for (provider in availableProviders) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - PayinMethodRow( - title = stringResource(Res.string.trustly), - subtitle = "Connect your bank via Trustly", // todo - onClick = onTrustlySelected, - ) - } - - MemberPaymentProvider.SWISH -> { - PayinMethodRow( - title = stringResource(Res.string.swish), - subtitle = "Monthly auto-payments through Swish", // todo - onClick = onSwishSelected, - ) - } - - MemberPaymentProvider.INVOICE -> { - PayinMethodRow( - title = stringResource(Res.string.PAYMENTS_INVOICE), - subtitle = stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION), // todo - onClick = onInvoiceSelected, - ) - } - - else -> {} - } + PayinMethodRow( + provider = provider, + onTrustlySelected = onTrustlySelected, + onSwishSelected = onSwishSelected, + onInvoiceSelected = onInvoiceSelected, + ) } } Spacer(Modifier.height(16.dp)) @@ -80,17 +63,81 @@ internal fun SelectPayinMethodDestination( } @Composable -private fun PayinMethodRow(title: String, subtitle: String, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun PayinMethodRow( + provider: MemberPaymentProvider, + onTrustlySelected: () -> Unit, + onSwishSelected: () -> Unit, + onInvoiceSelected: () -> Unit, + modifier: Modifier = Modifier, +) { HedvigCard( - onClick = onClick, + onClick = { + when (provider) { + MemberPaymentProvider.TRUSTLY -> { + onTrustlySelected() + } + + MemberPaymentProvider.SWISH -> { + onSwishSelected() + } + + MemberPaymentProvider.INVOICE -> { + onInvoiceSelected() + } + + else -> {} + } + }, modifier = modifier.fillMaxWidth(), ) { - Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) { - HedvigText(text = title) - HedvigText( - text = subtitle, - color = HedvigTheme.colorScheme.textSecondary, - ) + Row( + modifier = Modifier.padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> Icon( + HedvigIcons.Autogiro, + null, //todo + modifier = Modifier.size(32.dp), + ) + + MemberPaymentProvider.SWISH -> Image( + HedvigIcons.Swish, + null, //todo + modifier = Modifier.size(32.dp), + ) + + MemberPaymentProvider.INVOICE -> Image( + HedvigIcons.Kivra, + null, //todo + modifier = Modifier.size(32.dp), + ) + + else -> {} + } + + Spacer(Modifier.width(16.dp)) + Column(Modifier.padding(vertical = 12.dp)) { + HedvigText( + text = when (provider) { + MemberPaymentProvider.TRUSTLY -> "Autogiro" // todo + MemberPaymentProvider.SWISH -> stringResource(Res.string.swish) + MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENTS_INVOICE) + else -> "" + }, + ) + HedvigText( + text = when (provider) { + MemberPaymentProvider.TRUSTLY -> "Connect your bank via Trustly" // todo + MemberPaymentProvider.SWISH -> "Monthly auto-payments via Swish" // todo + MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION) + else -> "" + }, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + + } } } @@ -104,6 +151,7 @@ private fun PreviewSelectPayinMethodScreen() { availableProviders = listOf( MemberPaymentProvider.SWISH, MemberPaymentProvider.INVOICE, + MemberPaymentProvider.TRUSTLY, ), onTrustlySelected = {}, onSwishSelected = {}, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 26ef20cec2..43b080b172 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -126,8 +126,8 @@ private fun SetupSwishPayoutScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { EmptyState( - text = "The process started", - description = "Please give your approval in the Swish app", + text = "Please give your approval in the Swish app", + description = null, modifier = Modifier.fillMaxWidth(), iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SWISH, ) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt index 9bebee139c..9be2df5caa 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt @@ -12,6 +12,9 @@ import com.hedvig.android.feature.payments.data.MemberPaymentsDetails import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.WhileSubscribed internal class MemberPaymentDetailsViewModel( getMemberPaymentsDetailsUseCase: GetMemberPaymentsDetailsUseCase, @@ -20,6 +23,7 @@ internal class MemberPaymentDetailsViewModel( presenter = MemberPaymentDetailsPresenter( getMemberPaymentsDetailsUseCase, ), + sharingStarted = SharingStarted.WhileSubscribed(2.seconds), ) private class MemberPaymentDetailsPresenter( From 6762015c425d6ba2372cff4c7acb672ccf4ca6af Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 03:19:12 +0200 Subject: [PATCH 18/75] copy --- .../account/ui/selectmethod/SelectPayinMethodDestination.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 9147695057..db37cb4964 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -43,7 +43,7 @@ internal fun SelectPayinMethodDestination( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = "Select billing method to add", // todo + topAppBarText = "Add or change billing method", // todo navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { From 9a1421ad362b7541e6ca435d91012bc93cf848fa Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 03:28:18 +0200 Subject: [PATCH 19/75] bank name --- .../ui/overview/PayinAccountOverviewDestination.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index cb343a9995..fe0f7badf1 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -46,6 +46,7 @@ import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.toDeliveryString +import com.hedvig.android.logger.logcat import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res @@ -314,7 +315,7 @@ private fun CurrentPayinMethodRow( if (!isPending) { HedvigButtonGhostWithBorder( size = ButtonDefaults.ButtonSize.Small, - text = "Choose as default", // todo + text = "Set as default", // todo onClick = { onClick(provider) }, @@ -330,8 +331,14 @@ private fun CurrentPayinMethodRow( } private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?, bankName: String?): String { + logcat { "Mariia: clearingNumber: $clearingNumber accountNumber: $accountNumber bankName: $bankName" } +// return when { +// clearingNumber != null && accountNumber != null && bankName != null -> "$bankName $clearingNumber-$accountNumber" +// clearingNumber != null && bankName != null -> "$bankName $clearingNumber" +// else -> clearingNumber.orEmpty() +// } return when { - clearingNumber != null && accountNumber != null && bankName != null -> "$bankName $clearingNumber-$accountNumber" + bankName != null -> bankName else -> clearingNumber.orEmpty() } } From ff702a1c1eb7fcff71ae72c3e818ba7595e51ca5 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 03:43:05 +0200 Subject: [PATCH 20/75] format phone --- .../PayinAccountOverviewDestination.kt | 18 ++++++++++++++++-- .../setupswish/SetupSwishPayinDestination.kt | 8 ++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index fe0f7badf1..2ec1efcce7 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -159,7 +159,7 @@ private fun PayoutAccountContent( text = if (method.isPending && phoneNumber.isBlank()) { stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) } else { - phoneNumber + formatSwishPhoneNumber(phoneNumber) }, isDefault = method.isDefault, onClick = setAsDefaultPayinMethod, @@ -292,7 +292,7 @@ private fun CurrentPayinMethodRow( Column { HedvigText(text = label) HedvigText( - text = text, + text = text , color = HedvigTheme.colorScheme.textSecondary, ) } @@ -330,6 +330,20 @@ private fun CurrentPayinMethodRow( } } +internal fun formatSwishPhoneNumber(phoneNumber: String): String { + val digits = phoneNumber.take(15) + val sb = StringBuilder() + for (i in digits.indices) { + sb.append(digits[i]) + if (i in setOf(2, 5, 7) + ) { + sb.append("-") + } + } + return sb.toString() +} + + private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?, bankName: String?): String { logcat { "Mariia: clearingNumber: $clearingNumber accountNumber: $accountNumber bankName: $bankName" } // return when { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 43b080b172..075f82ea3c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -51,6 +51,7 @@ import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.feature.payin.account.ui.overview.formatSwishPhoneNumber import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL import hedvig.resources.Res @@ -212,12 +213,7 @@ private class ChipIdVisualTransformation( val trimmed = if (text.text.length >= 15) text.text.substring(0..14) else text.text val annotatedString = buildAnnotatedString { - for (i in trimmed.indices) { - append(trimmed[i]) - if (i in listOf(2, 5, 7)) { - append("-") - } - } + append(formatSwishPhoneNumber(trimmed)) withStyle(SpanStyle(color = maskColor)) { append(mask.takeLast((mask.length - length).coerceAtLeast(0))) } From 0042aa5402c1d9418a70991aad495219b40f88bc Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 09:51:26 +0200 Subject: [PATCH 21/75] copy --- .../account/ui/overview/PayinAccountOverviewDestination.kt | 2 +- .../account/ui/selectmethod/SelectPayinMethodDestination.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 2ec1efcce7..d82927fbe8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -173,7 +173,7 @@ private fun PayoutAccountContent( is PayinAccount.Trustly -> { val accountNumber = formatBankAccountNumber(method.clearingNumber, method.accountNumber, method.bankName) CurrentPayinMethodRow( - label = "Autogiro", // todo + label = "Direct debit", // todo text = if (method.isPending && accountNumber.isBlank()) { stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) } else { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index db37cb4964..500afcb37d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -120,7 +120,7 @@ private fun PayinMethodRow( Column(Modifier.padding(vertical = 12.dp)) { HedvigText( text = when (provider) { - MemberPaymentProvider.TRUSTLY -> "Autogiro" // todo + MemberPaymentProvider.TRUSTLY -> "Direct debit" // todo MemberPaymentProvider.SWISH -> stringResource(Res.string.swish) MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENTS_INVOICE) else -> "" From 46c46335d3a25d0bfb5f2ead05887c0c077a4b6e Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 10:37:22 +0200 Subject: [PATCH 22/75] icons in select payout method --- .../design/system/hedvig/icon/BankAccount.kt | 120 ++++++++++++++++++ .../SelectPayoutMethodDestination.kt | 86 ++++++++++++- 2 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt new file mode 100644 index 0000000000..44b6610074 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt @@ -0,0 +1,120 @@ +package com.hedvig.android.design.system.hedvig.icon + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra + +val HedvigIcons.BankAccount: ImageVector + get() { + if (_Icons8BankAccount48 != null) { + return _Icons8BankAccount48!! + } + _Icons8BankAccount48 = ImageVector.Builder( + name = "Icons8BankAccount48", + defaultWidth = 48.dp, + defaultHeight = 48.dp, + viewportWidth = 48f, + viewportHeight = 48f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(30.867f, 6.531f) + curveTo(32.209f, 7.176f, 33.545f, 7.832f, 34.875f, 8.5f) + curveTo(35.563f, 8.834f, 36.252f, 9.168f, 36.961f, 9.512f) + curveTo(38.646f, 10.331f, 40.324f, 11.164f, 42f, 12f) + curveTo(42f, 13.98f, 42f, 15.96f, 42f, 18f) + curveTo(40.68f, 18f, 39.36f, 18f, 38f, 18f) + curveTo(38f, 20.31f, 38f, 22.62f, 38f, 25f) + curveTo(36.02f, 25.99f, 36.02f, 25.99f, 34f, 27f) + curveTo(34f, 24.03f, 34f, 21.06f, 34f, 18f) + curveTo(32.68f, 18f, 31.36f, 18f, 30f, 18f) + curveTo(30f, 23.28f, 30f, 28.56f, 30f, 34f) + curveTo(28.68f, 34f, 27.36f, 34f, 26f, 34f) + curveTo(26f, 28.72f, 26f, 23.44f, 26f, 18f) + curveTo(24.68f, 18f, 23.36f, 18f, 22f, 18f) + curveTo(22f, 23.28f, 22f, 28.56f, 22f, 34f) + curveTo(20.68f, 34f, 19.36f, 34f, 18f, 34f) + curveTo(18f, 28.72f, 18f, 23.44f, 18f, 18f) + curveTo(16.68f, 18f, 15.36f, 18f, 14f, 18f) + curveTo(14f, 23.28f, 14f, 28.56f, 14f, 34f) + curveTo(12.68f, 34f, 11.36f, 34f, 10f, 34f) + curveTo(10f, 28.72f, 10f, 23.44f, 10f, 18f) + curveTo(8.68f, 18f, 7.36f, 18f, 6f, 18f) + curveTo(6f, 16.02f, 6f, 14.04f, 6f, 12f) + curveTo(8.369f, 10.819f, 10.744f, 9.655f, 13.125f, 8.5f) + curveTo(13.79f, 8.166f, 14.455f, 7.832f, 15.141f, 7.488f) + curveTo(20.813f, 4.763f, 24.925f, 3.968f, 30.867f, 6.531f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(40f, 39.75f) + curveTo(40.866f, 39.745f, 41.732f, 39.74f, 42.625f, 39.734f) + curveTo(45f, 40f, 45f, 40f, 48f, 42f) + curveTo(48f, 43.98f, 48f, 45.96f, 48f, 48f) + curveTo(42.72f, 48f, 37.44f, 48f, 32f, 48f) + curveTo(32f, 46.02f, 32f, 44.04f, 32f, 42f) + curveTo(35.239f, 39.841f, 36.249f, 39.728f, 40f, 39.75f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(6f, 38f) + curveTo(14.91f, 38f, 23.82f, 38f, 33f, 38f) + curveTo(30f, 42f, 30f, 42f, 27.456f, 42.454f) + curveTo(26.467f, 42.433f, 25.477f, 42.412f, 24.457f, 42.391f) + curveTo(23.384f, 42.378f, 22.311f, 42.365f, 21.205f, 42.352f) + curveTo(20.086f, 42.318f, 18.966f, 42.285f, 17.813f, 42.25f) + curveTo(16.681f, 42.232f, 15.55f, 42.214f, 14.385f, 42.195f) + curveTo(11.589f, 42.148f, 8.795f, 42.082f, 6f, 42f) + curveTo(6f, 40.68f, 6f, 39.36f, 6f, 38f) + close() + } + path(fill = SolidColor(Color.Black)) { + moveTo(40f, 27.875f) + curveTo(43f, 28f, 43f, 28f, 44f, 29f) + curveTo(44.125f, 32f, 44.125f, 32f, 44f, 35f) + curveTo(43f, 36f, 43f, 36f, 40f, 36.125f) + curveTo(37f, 36f, 37f, 36f, 36f, 35f) + curveTo(35.875f, 32f, 35.875f, 32f, 36f, 29f) + curveTo(37f, 28f, 37f, 28f, 40f, 27.875f) + close() + } + }.build() + + return _Icons8BankAccount48!! + } + +@Suppress("ObjectPropertyName") +private var _Icons8BankAccount48: ImageVector? = null + + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = HedvigIcons.BankAccount, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((24.0).dp) + .height((24.0).dp), + ) + } + } +} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index aa94ff4d35..bcdefff825 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -1,20 +1,35 @@ package com.hedvig.android.feature.payoutaccount.ui.selectmethod -import androidx.compose.foundation.clickable +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.icon.Autogiro +import com.hedvig.android.design.system.hedvig.icon.BankAccount +import com.hedvig.android.design.system.hedvig.icon.Card +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Link +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.design.system.hedvig.icon.flag.FlagSweden import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE import hedvig.resources.PAYMENTS_INVOICE @@ -50,6 +65,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.trustly), subtitle = stringResource(Res.string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), onClick = onTrustlySelected, + provider = provider ) } @@ -58,6 +74,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), subtitle = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_DESCRIPTION), onClick = onNordeaSelected, + provider = provider ) } @@ -66,6 +83,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.swish), subtitle = stringResource(Res.string.PAYOUT_METHOD_SWISH_DESCRIPTION), onClick = onSwishSelected, + provider = provider ) } @@ -78,16 +96,70 @@ internal fun SelectPayoutMethodDestination( } @Composable -private fun PayoutMethodRow(title: String, subtitle: String, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun PayoutMethodRow( + provider: MemberPaymentProvider, + title: String, + subtitle: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { HedvigCard( onClick = onClick, modifier = modifier.fillMaxWidth(), ) { - Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) { - HedvigText(text = title) - HedvigText( - text = subtitle, - color = HedvigTheme.colorScheme.textSecondary, + Row( + modifier = Modifier.padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> Icon( + HedvigIcons.Link, + null, //todo + modifier = Modifier.size(32.dp), + ) + + MemberPaymentProvider.SWISH -> Image( + HedvigIcons.Swish, + null, //todo + modifier = Modifier.size(32.dp), + ) + + MemberPaymentProvider.NORDEA -> Icon( + HedvigIcons.BankAccount, + null, //todo + modifier = Modifier.size(32.dp), + ) + + else -> {} + } + + Spacer(Modifier.width(16.dp)) + Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) { + HedvigText(text = title) + HedvigText( + text = subtitle, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + } + } +} + +@Composable +@HedvigPreview +private fun PreviewSelectPayoutMethodScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SelectPayoutMethodDestination( + availableProviders = listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.TRUSTLY, + MemberPaymentProvider.NORDEA, + ), + onTrustlySelected = {}, + onNordeaSelected = {}, + onSwishSelected = {}, + navigateUp = {}, ) } } From f2c4c7024d2e6aa11dc86f295d005d3026cb9561 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 10:37:55 +0200 Subject: [PATCH 23/75] ktlint --- .../android/design/system/hedvig/icon/BankAccount.kt | 4 +--- .../ui/selectmethod/SelectPayinMethodDestination.kt | 3 +-- .../ui/selectmethod/SelectPayoutMethodDestination.kt | 12 +++--------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt index 44b6610074..72e884b890 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt @@ -1,6 +1,5 @@ package com.hedvig.android.design.system.hedvig.icon -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height @@ -16,7 +15,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra val HedvigIcons.BankAccount: ImageVector get() { @@ -28,7 +26,7 @@ val HedvigIcons.BankAccount: ImageVector defaultWidth = 48.dp, defaultHeight = 48.dp, viewportWidth = 48f, - viewportHeight = 48f + viewportHeight = 48f, ).apply { path(fill = SolidColor(Color.Black)) { moveTo(30.867f, 6.531f) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 500afcb37d..ceebbdf186 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -30,7 +30,6 @@ import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYOUT_METHOD_INVOICE_DESCRIPTION import hedvig.resources.Res import hedvig.resources.swish -import hedvig.resources.trustly import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @@ -86,7 +85,7 @@ private fun PayinMethodRow( } else -> {} - } + } }, modifier = modifier.fillMaxWidth(), ) { diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index bcdefff825..52a7b0f298 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -22,18 +22,12 @@ import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.icon.Autogiro import com.hedvig.android.design.system.hedvig.icon.BankAccount -import com.hedvig.android.design.system.hedvig.icon.Card import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Link -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish -import com.hedvig.android.design.system.hedvig.icon.flag.FlagSweden import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE -import hedvig.resources.PAYMENTS_INVOICE -import hedvig.resources.PAYOUT_METHOD_INVOICE_DESCRIPTION import hedvig.resources.PAYOUT_METHOD_SWISH_DESCRIPTION import hedvig.resources.PAYOUT_METHOD_TRUSTLY_DESCRIPTION import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD @@ -65,7 +59,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.trustly), subtitle = stringResource(Res.string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), onClick = onTrustlySelected, - provider = provider + provider = provider, ) } @@ -74,7 +68,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), subtitle = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_DESCRIPTION), onClick = onNordeaSelected, - provider = provider + provider = provider, ) } @@ -83,7 +77,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.swish), subtitle = stringResource(Res.string.PAYOUT_METHOD_SWISH_DESCRIPTION), onClick = onSwishSelected, - provider = provider + provider = provider, ) } From a2463a0525a8c001f08381c0b62cfc7b80f1cc75 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 11:26:44 +0200 Subject: [PATCH 24/75] border and color for qr --- .../setupswish/SetupSwishPayinDestination.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 075f82ea3c..e4e2bcba42 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -136,11 +136,19 @@ private fun SetupSwishPayoutScreen( Column( horizontalAlignment = Alignment.CenterHorizontally, ) { - QRCode( - token = uiState.successUrl, - modifier = Modifier - .size(180.dp), - ) + Surface( + color = Color.White, + shape = HedvigTheme.shapes.cornerMedium, + border = HedvigTheme.colorScheme.borderPrimary, + modifier = Modifier.padding(16.dp), + ) { + QRCode( + token = uiState.successUrl, + modifier = Modifier + .size(180.dp) + .padding(16.dp), + ) + } } Spacer(Modifier.height(32.dp)) HedvigButton( From 8f98db763ff0895dab3b4146ec740b71de842a4b Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 11:28:37 +0200 Subject: [PATCH 25/75] border and color for qr --- .../payin/account/ui/setupswish/SetupSwishPayinDestination.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index e4e2bcba42..2556658d3c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -150,7 +150,7 @@ private fun SetupSwishPayoutScreen( ) } } - Spacer(Modifier.height(32.dp)) + Spacer(Modifier.height(16.dp)) HedvigButton( "Open Swish", enabled = true, From d1c9929cacc40f1b536104de4eb55068d07b4aff Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 13:38:20 +0200 Subject: [PATCH 26/75] copy --- .../ui/selectmethod/SelectPayoutMethodDestination.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 52a7b0f298..8515f65068 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -23,6 +23,7 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.icon.BankAccount +import com.hedvig.android.design.system.hedvig.icon.Card import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Link import com.hedvig.android.design.system.hedvig.icon.colored.Swish @@ -66,7 +67,7 @@ internal fun SelectPayoutMethodDestination( MemberPaymentProvider.NORDEA -> { PayoutMethodRow( title = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), - subtitle = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_DESCRIPTION), + subtitle = "Payout to a bank account", //todo: removed BANK_PAYOUT_METHOD_CARD_DESCRIPTION for demo onClick = onNordeaSelected, provider = provider, ) @@ -119,7 +120,7 @@ private fun PayoutMethodRow( ) MemberPaymentProvider.NORDEA -> Icon( - HedvigIcons.BankAccount, + HedvigIcons.Card, null, //todo modifier = Modifier.size(32.dp), ) From 335c488dc9e58f26240bf8c30430eda38b7fbd31 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 14:08:20 +0200 Subject: [PATCH 27/75] safeExecuteAllowingPartialResponses in SetAsDefaultUseCase.kt --- .../payin/account/data/SetAsDefaultUseCase.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt index f21b715793..64d2b8182f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -7,6 +7,7 @@ import arrow.core.raise.context.raise import com.apollographql.apollo.ApolloClient import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.apollo.safeExecuteAllowingPartialResponses import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.logger.logcat import octopus.SetAsDefaultPayinMutation @@ -22,25 +23,26 @@ internal class SetAsDefaultUseCaseImpl( ) : SetAsDefaultUseCase { override suspend fun invoke(provider: MemberPaymentProvider): Either { return either { - logcat { "Mariia: Starting SetAsDefaultUseCaseImpl with provider: $provider" } apolloClient .mutation(SetAsDefaultPayinMutation(provider)) - .safeExecute(::ErrorMessage) // tosdoL user safeExecuteAllowingPartialResponses + .safeExecuteAllowingPartialResponses() .fold( - ifLeft = { - logcat { "Mariia: SetAsDefaultUseCaseImpl error: $it" } - logcat { "SetAsDefaultUseCaseImpl error: $it" } + fa = { error -> + logcat { "SetAsDefaultUseCaseImpl error: $error" } raise(ErrorMessage()) }, - ifRight = { result -> + fb = { result -> val userError = result.paymentMethodSetDefaultPayin?.message if (userError != null) { - logcat { "Mariia: SetAsDefaultUseCaseImpl userError not null: $userError" } + logcat { "SetAsDefaultUseCaseImpl userError not null: $userError" } raise(ErrorMessage(userError)) } - logcat { "Mariia: SetAsDefaultUseCaseImpl launching getPayinAccountUseCase" } getPayinAccountUseCase.invoke().bind() }, + fab = {errors, _ -> + logcat { "SetAsDefaultUseCaseImpl data with errors: $errors" } + raise(ErrorMessage()) + } ) } } From cf0c0e0d8ce28d602c789b9b32ab31c71792b2bf Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 14:31:59 +0200 Subject: [PATCH 28/75] copy and phone number digit filtering --- .../androidMain/res/values-sv-rSE/strings.xml | 16 ++++++++++++++-- .../src/androidMain/res/values/strings.xml | 16 ++++++++++++++-- .../composeResources/values-sv-rSE/strings.xml | 16 ++++++++++++++-- .../composeResources/values/strings.xml | 16 ++++++++++++++-- .../overview/PayinAccountOverviewDestination.kt | 16 ++++++++-------- .../selectmethod/SelectPayinMethodDestination.kt | 2 +- .../ui/setupswish/SetupSwishPayinDestination.kt | 7 ++++--- 7 files changed, 69 insertions(+), 20 deletions(-) diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 5c6838f5e0..512bd2005e 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -522,7 +522,7 @@ Fråga angående skadeanmälan - Fordon reg. %1$s Välj land och språk Logga ut - Manage your billing methods + Manage your payment methods Aktivera ditt försäkringsskydd igen genom att kontakta oss när din betalning har registrerats Få ett prisförslag Se över kontaktuppgifter @@ -1280,6 +1280,19 @@ Läs om ditt fullständiga försäkringsskydd nedan. Appinformation Information + Fortsätt köpet + + Erbjudandet löper ut om %1$d dag + Erbjudandet löper ut om %1$d dagar + + + Erbjudandet löper ut om %1$d timme + Erbjudandet löper ut om %1$d timmar + + + Erbjudandet löper ut om %1$d minut + Erbjudandet löper ut om %1$d minuter + Skriv meddelande Autogiro är anslutet! Vi kunde inte ansluta ditt bankkonto. Vänligen försök igen eller skriv till oss direkt i appen. @@ -1367,7 +1380,6 @@ Du är inbjuden Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code - Återuppta handlingen Koppla ditt EuroBonus Lägg till ditt nummer Du tjänar 100 poäng per månad för varje aktiv försäkring du har hos Hedvig diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index 4beb026ee8..792da211c2 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -522,7 +522,7 @@ Question regarding claim, Vehicle reg. %1$s Preferences Logout - Manage your billing methods + Manage your payment methods Activate your coverage again by contacting us once your payment has been processed Get a price quote Review contact info @@ -1280,6 +1280,19 @@ Read the full coverage of your insurances below. App information Information + Continue purchase + + The offer expires in %1$d day + The offer expires in %1$d days + + + The offer expires in %1$d hour + The offer expires in %1$d hours + + + The offer expires in %1$d minute + The offer expires in %1$d minutes + Write message Direct debit connected! We were unable to add your payment method. Please try again or send us a message here in the app. @@ -1367,7 +1380,6 @@ You’re invited Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code - Resume shopping Connect your EuroBonus Connect your number You earn 100 points per month for each active insurance you have with Hedvig diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index df6c4c7f86..dcde5a4f26 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -522,7 +522,7 @@ Fråga angående skadeanmälan - Fordon reg. %1$s Välj land och språk Logga ut - Manage your billing methods + Manage your payment methods Aktivera ditt försäkringsskydd igen genom att kontakta oss när din betalning har registrerats Få ett prisförslag Se över kontaktuppgifter @@ -1280,6 +1280,19 @@ Läs om ditt fullständiga försäkringsskydd nedan. Appinformation Information + Fortsätt köpet + + Erbjudandet löper ut om %1$d dag + Erbjudandet löper ut om %1$d dagar + + + Erbjudandet löper ut om %1$d timme + Erbjudandet löper ut om %1$d timmar + + + Erbjudandet löper ut om %1$d minut + Erbjudandet löper ut om %1$d minuter + Skriv meddelande Autogiro är anslutet! Vi kunde inte ansluta ditt bankkonto. Vänligen försök igen eller skriv till oss direkt i appen. @@ -1367,7 +1380,6 @@ Du är inbjuden Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code - Återuppta handlingen Koppla ditt EuroBonus Lägg till ditt nummer Du tjänar 100 poäng per månad för varje aktiv försäkring du har hos Hedvig diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index 7206a8403b..10578b83ed 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -522,7 +522,7 @@ Question regarding claim, Vehicle reg. %1$s Preferences Logout - Manage your billing methods + Manage your payment methods Activate your coverage again by contacting us once your payment has been processed Get a price quote Review contact info @@ -1280,6 +1280,19 @@ Read the full coverage of your insurances below. App information Information + Continue purchase + + The offer expires in %1$d day + The offer expires in %1$d days + + + The offer expires in %1$d hour + The offer expires in %1$d hours + + + The offer expires in %1$d minute + The offer expires in %1$d minutes + Write message Direct debit connected! We were unable to add your payment method. Please try again or send us a message here in the app. @@ -1367,7 +1380,6 @@ You’re invited Hedvig Forever You’ve been officially invited to Hedvig Forever and can now invite your friends with your unique code - Resume shopping Connect your EuroBonus Connect your number You earn 100 points per month for each active insurance you have with Hedvig diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index d82927fbe8..ee262575bb 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -83,7 +83,7 @@ private fun PayinAccountOverviewScreen( setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, ) { HedvigScaffold( - topAppBarText = "Billing account", // todo! + topAppBarText = "Payment account", // todo! navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -143,7 +143,7 @@ private fun PayoutAccountContent( } } else { HedvigText( - "Active billing methods", // todo + "Connected payment methods", // todo modifier = Modifier.padding(horizontal = 16.dp), ) Spacer(Modifier.height(16.dp)) @@ -232,7 +232,7 @@ private fun PayoutAccountContent( } if (availablePayinMethods.isNotEmpty()) { HedvigButton( - text = "Add a billing method", // todo! + text = "Add a payment method", // todo! onClick = onConnectPayinMethodClicked, enabled = true, modifier = Modifier @@ -386,7 +386,7 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr PayinAccountOverviewUiState.Content( currentMethods = listOf( PayinAccount.SwishPayin( - phoneNumber = "070-123 45 67", + phoneNumber = "0701234567", isPending = false, isDefault = true, ), @@ -396,7 +396,7 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr PayinAccountOverviewUiState.Content( currentMethods = listOf( PayinAccount.SwishPayin( - phoneNumber = "070-123 45 67", + phoneNumber = "0701234567", isPending = false, isDefault = true, ), @@ -417,7 +417,7 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr isDefault = true, ), PayinAccount.SwishPayin( - phoneNumber = "070-123 45 67", + phoneNumber = "0701234567", isPending = false, isDefault = false, ), @@ -435,7 +435,7 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr isDefault = true, ), PayinAccount.SwishPayin( - phoneNumber = "070-123 45 67", + phoneNumber = "0701234567", isPending = false, isDefault = false, ), @@ -458,7 +458,7 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr isDefault = true, ), PayinAccount.SwishPayin( - phoneNumber = "070-123 45 67", + phoneNumber = "0701234567", isPending = false, isDefault = false, ), diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index ceebbdf186..8976ea1e99 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -42,7 +42,7 @@ internal fun SelectPayinMethodDestination( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = "Add or change billing method", // todo + topAppBarText = "Add or change payment method", // todo navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 2556658d3c..1fa8c9484e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -184,9 +184,10 @@ private fun SetupSwishPayoutScreen( errorState = HedvigTextFieldDefaults.ErrorState.NoError, interactionSource = interactionSource, onValueChange = { - if (it.length <= 15) { - updateText(it) - input = it + val digitsOnly = it.filter { char -> char.isDigit() } + if (digitsOnly.length <= 15) { + updateText(digitsOnly) + input = digitsOnly } }, ) From c46311139cdf0cac92075869863821de2a12f882 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 14:40:17 +0200 Subject: [PATCH 29/75] remove logs --- .../kotlin/com/hedvig/android/apollo/ApolloCallExt.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt b/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt index b300006bfc..8b3205bba4 100644 --- a/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt +++ b/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt @@ -128,7 +128,6 @@ private fun IorRaise>.parseRespon } raise(OperationException(exception)) } - logcat{"Mariia: apollo parseResponse data: $data errors: $errors"} return iorFromErrorsAndData(errors.mapToOperationErrors(), data).bind() } @@ -138,7 +137,6 @@ private fun List?.mapToOperationErrors(): Nel? { if (error.extensionErrorType() == ExtensionErrorType.Unauthenticated) { OperationError.Unathenticated } else { - logcat{"Mariia: apollo mapToOperationErrors error: $error"} OperationError.Other( buildString { append(error.message) @@ -179,15 +177,12 @@ private fun iorFromErrorsAndData( ): Ior, D> { return when { errors != null && data != null -> { - logcat{"Mariia: apollo iorFromErrorsAndData Ior.Both. data: $data errors: $errors"} Ior.Both(errors, data) } errors != null -> { - logcat{"Mariia: apollo iorFromErrorsAndData Ior.Left"} Ior.Left(errors) } data != null -> { - logcat{"Mariia: apollo iorFromErrorsAndData Ior.Right"} Ior.Right(data) } else -> error("Non compliant server") From 1d304efb4c224587096c2ead510079b581ac2771 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 22 May 2026 14:57:23 +0200 Subject: [PATCH 30/75] add multiscreen previews --- .../account/ui/overview/PayinAccountOverviewDestination.kt | 6 +++++- .../account/ui/selectmethod/SelectPayinMethodDestination.kt | 3 ++- .../account/ui/setupinvoice/SetupInvoicePayinDestination.kt | 3 ++- .../memberpaymentdetails/MemberPaymentDetailsDestination.kt | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index ee262575bb..36c52fc533 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -32,6 +32,7 @@ import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProg import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HighlightLabel @@ -100,6 +101,8 @@ private fun PayinAccountOverviewScreen( HedvigErrorSection( onButtonClick = onRetry, modifier = Modifier + .fillMaxWidth() + .padding(16.dp) .weight(1f) .wrapContentHeight(), ) @@ -139,6 +142,7 @@ private fun PayoutAccountContent( text = "You haven’t added a billing method yet. Add one to pay for your insurance.", // todo description = null, iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) ) } } else { @@ -358,7 +362,7 @@ private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: Stri } @Composable -@HedvigPreview +@HedvigShortMultiScreenPreview private fun PreviewPayinAccountOverviewScreen( @PreviewParameter(PayinAccountOverviewUiStateProvider::class) uiState: PayinAccountOverviewUiState, ) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 8976ea1e99..9caa88ad2c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon @@ -142,7 +143,7 @@ private fun PayinMethodRow( } @Composable -@HedvigPreview +@HedvigShortMultiScreenPreview private fun PreviewSelectPayinMethodScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index d1059266e9..378076a95c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -23,6 +23,7 @@ import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority @@ -116,7 +117,7 @@ private fun SetupInvoicePayinScreen( } @Composable -@HedvigPreview +@HedvigShortMultiScreenPreview private fun PreviewPayoutAccountOverviewScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt index d2cb93d77b..19415acfed 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt @@ -95,7 +95,8 @@ private fun MemberPaymentDetailsScreen( when (uiState) { MemberPaymentDetailsUiState.Failure -> { HedvigErrorSection( - modifier = Modifier.weight(1f), + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) + .weight(1f), onButtonClick = retry, buttonText = stringResource(R.string.GENERAL_RETRY), ) From cd415a13162715396ad3825a144680f71a15ae48 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 18 Aug 2026 16:05:25 +0200 Subject: [PATCH 31/75] bring module up to date with DI and nav --- .../hedvig/android/apollo/ApolloCallExt.kt | 7 +- .../app/navigation/HedvigEntryProvider.kt | 9 ++ .../android/design/system/hedvig/Button.kt | 4 +- .../design/system/hedvig/EmptyState.kt | 2 +- .../design/system/hedvig/icon/Autogiro.kt | 2 +- .../design/system/hedvig/icon/BankAccount.kt | 1 - .../system/hedvig/icon/colored/Kivra.kt | 3 +- .../system/hedvig/icon/colored/Swish.kt | 27 +++--- .../feature-payin-account/build.gradle.kts | 7 +- .../account/data/GetPayinAccountUseCase.kt | 5 + .../payin/account/data/SetAsDefaultUseCase.kt | 11 ++- .../account/data/SetupInvoicePayinUseCase.kt | 3 - .../account/data/SetupSwishPayinUseCase.kt | 67 +++++++------ .../payin/account/di/PayinAccountModule.kt | 37 ------- .../navigation/PayinAccountDestination.kt | 29 +++--- .../account/navigation/PayinAccountEntries.kt | 78 +++++++++++++++ .../payin/account/navigation/PayinNavGraph.kt | 97 ------------------- .../PayinAccountOverviewDestination.kt | 51 +++++----- .../overview/PayinAccountOverviewViewModel.kt | 5 + .../SelectPayinMethodDestination.kt | 53 ++++++---- .../SetupInvoicePayinDestination.kt | 4 +- .../SetupInvoicePayinViewModel.kt | 30 +++--- .../setupswish/SetupSwishPayinDestination.kt | 67 ++++++------- .../ui/setupswish/SetupSwishPayinViewModel.kt | 5 + .../MemberPaymentDetailsDestination.kt | 2 +- .../MemberPaymentDetailsViewModel.kt | 2 +- .../payoutaccount/data/PayoutAccount.kt | 1 - .../navigation/PayoutAccountDestination.kt | 3 - .../navigation/PayoutAccountEntries.kt | 12 --- .../PayoutAccountOverviewDestination.kt | 2 - .../SelectPayoutMethodDestination.kt | 38 +++++--- .../memberreminders/ui/MemberReminderCards.kt | 2 +- .../GetMemberQuickActionsUseCase.kt | 2 +- 33 files changed, 321 insertions(+), 347 deletions(-) delete mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt delete mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt diff --git a/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt b/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt index 8b3205bba4..f81fd3a4fc 100644 --- a/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt +++ b/app/apollo/apollo-core/src/commonMain/kotlin/com/hedvig/android/apollo/ApolloCallExt.kt @@ -179,13 +179,18 @@ private fun iorFromErrorsAndData( errors != null && data != null -> { Ior.Both(errors, data) } + errors != null -> { Ior.Left(errors) } + data != null -> { Ior.Right(data) } - else -> error("Non compliant server") + + else -> { + error("Non compliant server") + } } } diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index ee73f8632a..a8b7ce7c26 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -50,6 +50,7 @@ import com.hedvig.android.feature.movingflow.SelectContractForMovingKey import com.hedvig.android.feature.movingflow.movingFlowEntries import com.hedvig.android.feature.onboarding.data.ResetOnboardingSeenUseCase import com.hedvig.android.feature.onboarding.navigation.onboardingEntries +import com.hedvig.android.feature.payin.account.navigation.payinAccountEntries import com.hedvig.android.feature.payments.navigation.paymentsEntries import com.hedvig.android.feature.payoutaccount.navigation.PayoutAccountKey import com.hedvig.android.feature.payoutaccount.navigation.payoutAccountEntries @@ -157,6 +158,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( addPaymentsEntries( backstack = backstack, globalSnackBarState = globalSnackBarState, + openUrl = openUrl, navigateToConnectPayment = navigateToConnectPayment, navigateToPayoutAccount = navigateToPayoutAccount, navigateToNewConversation = navigateToNewConversation, @@ -456,6 +458,7 @@ private fun EntryProviderScope.addInsuranceEntries( private fun EntryProviderScope.addPaymentsEntries( backstack: BackstackController, globalSnackBarState: GlobalSnackBarState, + openUrl: (String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToPayoutAccount: () -> Unit, navigateToNewConversation: () -> Unit, @@ -471,6 +474,12 @@ private fun EntryProviderScope.addPaymentsEntries( globalSnackBarState = globalSnackBarState, navigateToConnectPayment = navigateToConnectPayment, ) + payinAccountEntries( + backstack = backstack, + globalSnackBarState = globalSnackBarState, + navigateToConnectPayment = navigateToConnectPayment, + openUrl = openUrl, + ) connectPaymentEntries(backstack = backstack) } diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt index 6666f98904..4616610531 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt @@ -263,7 +263,7 @@ fun HedvigButtonGhostWithBorder( enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, size: ButtonSize = ButtonSize.Medium, - isLoading: Boolean = false + isLoading: Boolean = false, ) { HedvigTextButton( text = text, @@ -276,7 +276,7 @@ fun HedvigButtonGhostWithBorder( ), buttonSize = size, interactionSource = interactionSource, - isLoading = isLoading + isLoading = isLoading, ) } diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt index 30610c2a98..ac9673521b 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/EmptyState.kt @@ -174,7 +174,7 @@ object EmptyStateDefaults { NO_ICON, SUCCESS_WITH_WARNING, - SWISH + SWISH, } sealed class EmptyStateButtonStyle { diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt index 00aab9af82..c46edc448f 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt @@ -28,7 +28,7 @@ val HedvigIcons.Autogiro: ImageVector defaultWidth = 110.dp, defaultHeight = 135.dp, viewportWidth = 110f, - viewportHeight = 135f + viewportHeight = 135f, ).apply { path(fill = SolidColor(Color.Black)) { moveToRelative(93.54f, 57.92f) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt index 72e884b890..1a9a3ee2d2 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt @@ -97,7 +97,6 @@ val HedvigIcons.BankAccount: ImageVector @Suppress("ObjectPropertyName") private var _Icons8BankAccount48: ImageVector? = null - @Preview @Composable private fun IconPreview() { diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt index 3df5b7911a..6cc8993f9e 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Kivra.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.icon.HedvigIcons - val HedvigIcons.Kivra: ImageVector get() { if (_KivraLogo1920X1080Green != null) { @@ -28,7 +27,7 @@ val HedvigIcons.Kivra: ImageVector defaultWidth = 1912.dp, defaultHeight = 1015.dp, viewportWidth = 1912f, - viewportHeight = 1015f + viewportHeight = 1015f, ).apply { path(fill = SolidColor(Color(0xFF75C72E))) { moveTo(1274f, 292f) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt index 6eb4b18285..3c77566c1c 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt @@ -31,7 +31,7 @@ val HedvigIcons.Swish: ImageVector defaultWidth = 420.dp, defaultHeight = 566.dp, viewportWidth = 420f, - viewportHeight = 566f + viewportHeight = 566f, ).apply { path(fill = SolidColor(Color(0xFF17191F))) { moveTo(381.4f, 485.9f) @@ -288,12 +288,12 @@ val HedvigIcons.Swish: ImageVector fill = Brush.linearGradient( colorStops = arrayOf( 0f to Color(0xFFEF2131), - 1f to Color(0xFFFECF2C) + 1f to Color(0xFFFECF2C), ), start = Offset(237.8f, 289.7f), - end = Offset(177.74f, 104.45f) + end = Offset(177.74f, 104.45f), ), - pathFillType = PathFillType.EvenOdd + pathFillType = PathFillType.EvenOdd, ) { moveTo(119.3f, 399.2f) curveToRelative(84.3f, 40.3f, 188.3f, 20.4f, 251.2f, -54.5f) @@ -308,12 +308,12 @@ val HedvigIcons.Swish: ImageVector 0f to Color(0xFFFBC52C), 0.3f to Color(0xFFF87130), 0.6f to Color(0xFFEF52E2), - 1f to Color(0xFF661EEC) + 1f to Color(0xFF661EEC), ), start = Offset(379.9f, 129.59f), - end = Offset(243f, 399.77f) + end = Offset(243f, 399.77f), ), - pathFillType = PathFillType.EvenOdd + pathFillType = PathFillType.EvenOdd, ) { moveTo(119.3f, 399.2f) curveToRelative(84.3f, 40.3f, 188.3f, 20.4f, 251.2f, -54.5f) @@ -329,12 +329,12 @@ val HedvigIcons.Swish: ImageVector 0f to Color(0xFF78F6D8), 0.3f to Color(0xFF77D1F6), 0.6f to Color(0xFF70A4F3), - 1f to Color(0xFF661EEC) + 1f to Color(0xFF661EEC), ), start = Offset(118.21f, 92.5f), - end = Offset(178.27f, 277.75f) + end = Offset(178.27f, 277.75f), ), - pathFillType = PathFillType.EvenOdd + pathFillType = PathFillType.EvenOdd, ) { moveTo(300.3f, 20.4f) curveTo(216f, -19.9f, 111.9f, 0f, 49.1f, 74.9f) @@ -349,12 +349,12 @@ val HedvigIcons.Swish: ImageVector 0f to Color(0xFF536EED), 0.2f to Color(0xFF54C3EC), 0.6f to Color(0xFF64D769), - 1f to Color(0xFFFECF2C) + 1f to Color(0xFFFECF2C), ), start = Offset(95.13f, 220.03f), - end = Offset(232.03f, -50.15f) + end = Offset(232.03f, -50.15f), ), - pathFillType = PathFillType.EvenOdd + pathFillType = PathFillType.EvenOdd, ) { moveTo(300.3f, 20.4f) curveTo(216f, -19.9f, 111.9f, 0f, 49.1f, 74.9f) @@ -388,6 +388,5 @@ private fun IconPreview() { } } - @Suppress("ObjectPropertyName") private var _Swish: ImageVector? = null diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts index ce5712c76a..5509a2aac3 100644 --- a/app/feature/feature-payin-account/build.gradle.kts +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -7,6 +7,8 @@ hedvig { apollo("octopus") serialization() compose() + navKeys() + viewModels() } dependencies { @@ -19,9 +21,6 @@ dependencies { implementation(projects.coreBuildConstants) implementation(libs.jetbrains.compose.runtime) implementation(libs.jetbrains.lifecycle.runtime.compose) - implementation(libs.jetbrains.navigation.compose) - implementation(libs.koin.composeViewModel) - implementation(libs.koin.core) implementation(projects.composeUi) implementation(projects.coreCommonPublic) implementation(projects.coreResources) @@ -29,7 +28,5 @@ dependencies { implementation(projects.moleculePublic) implementation(projects.navigationCommon) implementation(projects.navigationCompose) - implementation(projects.navigationComposeTyped) - implementation(projects.navigationCore) implementation(libs.zXing) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 3071232b66..654b2a8ace 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -8,7 +8,10 @@ import com.apollographql.apollo.cache.normalized.fetchPolicy import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.AppScope import com.hedvig.android.logger.logcat +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn import octopus.GetPayinMethodsQuery import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodBankAccountDetails import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodInvoiceDetails @@ -22,6 +25,8 @@ internal data class PayinAccountData( val availablePayinMethods: List, ) +@SingleIn(AppScope::class) +@Inject internal class GetPayinAccountUseCase( private val apolloClient: ApolloClient, ) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt index 64d2b8182f..15a7f6c2e1 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -9,7 +9,11 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.apollo.safeExecuteAllowingPartialResponses import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.AppScope import com.hedvig.android.logger.logcat +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn import octopus.SetAsDefaultPayinMutation import octopus.type.MemberPaymentProvider @@ -17,6 +21,9 @@ internal interface SetAsDefaultUseCase { suspend fun invoke(provider: MemberPaymentProvider): Either } +@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +@Inject internal class SetAsDefaultUseCaseImpl( private val apolloClient: ApolloClient, private val getPayinAccountUseCase: GetPayinAccountUseCase, @@ -39,10 +46,10 @@ internal class SetAsDefaultUseCaseImpl( } getPayinAccountUseCase.invoke().bind() }, - fab = {errors, _ -> + fab = { errors, _ -> logcat { "SetAsDefaultUseCaseImpl data with errors: $errors" } raise(ErrorMessage()) - } + }, ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt index 984a55e41a..c9b03eb91e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt @@ -10,14 +10,11 @@ import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn -import octopus.SetupInvoicePayoutMutation -import octopus.type.PaymentMethodInvoiceDelivery import octopus.SetupInvoicePayinMutation import octopus.type.PaymentMethodSetupStatus @SingleIn(AppScope::class) @Inject -internal class SetupInvoicePayoutUseCase( internal class SetupInvoicePayinUseCase( private val apolloClient: ApolloClient, private val networkCacheManager: NetworkCacheManager, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt index 1088ea52a5..d5f114f4c8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -8,7 +8,11 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.NetworkCacheManager import com.hedvig.android.apollo.safeExecuteAllowingPartialResponses import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.AppScope import com.hedvig.android.logger.logcat +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn import octopus.SetupSwishPayinMutation import octopus.type.PaymentMethodSetupStatus import octopus.type.PaymentMethodSetupSwishInput @@ -17,6 +21,9 @@ internal interface SetupSwishPayinUseCase { suspend fun invoke(phoneNumber: String): Either } +@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +@Inject internal class SetupSwishPayinUseCaseImpl( private val apolloClient: ApolloClient, private val networkCacheManager: NetworkCacheManager, @@ -26,42 +33,42 @@ internal class SetupSwishPayinUseCaseImpl( .mutation(SetupSwishPayinMutation(PaymentMethodSetupSwishInput(phoneNumber))) .safeExecuteAllowingPartialResponses() .fold( - fa = { error -> - logcat { "SetupSwishPayinMutation error: $error" } - raise(ErrorMessage()) - }, - fb = { result -> - val output = result.paymentMethodSetupSwishPayin - when (output.status) { - PaymentMethodSetupStatus.ACTIVE -> { - logcat { - "Mariia: SetupSwishPayinMutation ACTIVE url: $output.url" - } - networkCacheManager.clearCache() - SetupSwishResponse.Success(output.url) + fa = { error -> + logcat { "SetupSwishPayinMutation error: $error" } + raise(ErrorMessage()) + }, + fb = { result -> + val output = result.paymentMethodSetupSwishPayin + when (output.status) { + PaymentMethodSetupStatus.ACTIVE -> { + logcat { + "Mariia: SetupSwishPayinMutation ACTIVE url: $output.url" } + networkCacheManager.clearCache() + SetupSwishResponse.Success(output.url) + } - PaymentMethodSetupStatus.PENDING -> { - logcat { - "Mariia: SetupSwishPayinMutation PENDING url: $output.url" - } - networkCacheManager.clearCache() - SetupSwishResponse.Pending(output.url) + PaymentMethodSetupStatus.PENDING -> { + logcat { + "Mariia: SetupSwishPayinMutation PENDING url: $output.url" } + networkCacheManager.clearCache() + SetupSwishResponse.Pending(output.url) + } - PaymentMethodSetupStatus.FAILED, PaymentMethodSetupStatus.UNKNOWN__ -> { - logcat { - "SetupSwishPayinMutation failed with: output.error?.message" - } - val userMessage = output.error?.message - SetupSwishResponse.Failure(ErrorMessage(userMessage)) + PaymentMethodSetupStatus.FAILED, PaymentMethodSetupStatus.UNKNOWN__ -> { + logcat { + "SetupSwishPayinMutation failed with: output.error?.message" } + val userMessage = output.error?.message + SetupSwishResponse.Failure(ErrorMessage(userMessage)) } - }, - fab = { errors, _ -> - logcat { "SetupSwishPayinMutation dataa with errors: $errors" } - raise(ErrorMessage()) - }, + } + }, + fab = { errors, _ -> + logcat { "SetupSwishPayinMutation dataa with errors: $errors" } + raise(ErrorMessage()) + }, ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt deleted file mode 100644 index 516f144333..0000000000 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/di/PayinAccountModule.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.hedvig.android.feature.payin.account.di - -import com.apollographql.apollo.ApolloClient -import com.hedvig.android.apollo.NetworkCacheManager -import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase -import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase -import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCaseImpl -import com.hedvig.android.feature.payin.account.data.SetupInvoicePayinUseCase -import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase -import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCaseImpl -import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel -import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel -import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinViewModel -import org.koin.core.module.dsl.viewModel -import org.koin.dsl.module - -val payinAccountModule = module { - single { - SetupSwishPayinUseCaseImpl(get(), get()) - } - single { GetPayinAccountUseCase(get()) } - single { - SetAsDefaultUseCaseImpl( - get(), - get(), - ) - } - single { SetupInvoicePayinUseCase(get(), get()) } - viewModel { SetupInvoicePayinViewModel(get()) } - viewModel { SetupSwishPayinViewModel(get()) } - viewModel { - PayinAccountOverviewViewModel( - get(), - get(), - ) - } -} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 6539776797..eb416e7207 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -1,25 +1,18 @@ package com.hedvig.android.feature.payin.account.navigation -import com.hedvig.android.navigation.common.Destination +import com.hedvig.android.navigation.common.HedvigNavKey import kotlinx.serialization.Serializable -sealed interface PayinAccountDestination { - @Serializable - data object Graph : PayinAccountDestination, Destination -} +@Serializable +data object PayinAccountKey : HedvigNavKey -internal sealed interface PayinAccountDestinations { - @Serializable - data object Overview : PayinAccountDestinations, Destination +@Serializable +internal data class SelectPayinMethodKey( + val availableProviders: List, +) : HedvigNavKey - @Serializable - data class SelectPayinMethod( - val availableProviders: List, - ) : PayinAccountDestinations, Destination +@Serializable +internal data object SetupSwishPayinKey : HedvigNavKey - @Serializable - data object SetupSwishPayin : PayinAccountDestinations, Destination - - @Serializable - data object SetupInvoicePayin : PayinAccountDestinations, Destination -} +@Serializable +internal data object SetupInvoicePayinKey : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt new file mode 100644 index 0000000000..5e91522973 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -0,0 +1,78 @@ +package com.hedvig.android.feature.payin.account.navigation + +import androidx.lifecycle.compose.dropUnlessResumed +import androidx.navigation3.runtime.EntryProviderScope +import com.hedvig.android.design.system.hedvig.GlobalSnackBarState +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewDestination +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState +import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel +import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodDestination +import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination +import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel +import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinDestination +import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinViewModel +import com.hedvig.android.navigation.common.HedvigNavKey +import com.hedvig.android.navigation.compose.Backstack +import com.hedvig.android.navigation.compose.add +import com.hedvig.android.navigation.compose.popUpTo +import dev.zacsweers.metrox.viewmodel.metroViewModel +import octopus.type.MemberPaymentProvider + +fun EntryProviderScope.payinAccountEntries( + backstack: Backstack, + globalSnackBarState: GlobalSnackBarState, + navigateToConnectPayment: () -> Unit, + openUrl: (String) -> Unit, +) { + entry { + val viewModel: PayinAccountOverviewViewModel = metroViewModel() + PayinAccountOverviewDestination( + viewModel = viewModel, + onConnectPayoutMethodClicked = dropUnlessResumed { + val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content + backstack.add( + SelectPayinMethodKey( + availableProviders = content?.availablePayinMethods?.map { it.rawValue } ?: emptyList(), + ), + ) + }, + navigateUp = backstack::navigateUp, + ) + } + + entry { key -> + SelectPayinMethodDestination( + availableProviders = key.availableProviders.map { MemberPaymentProvider.safeValueOf(it) }, + onTrustlySelected = dropUnlessResumed { + backstack.popUpTo(inclusive = true) + navigateToConnectPayment() + }, + onSwishSelected = dropUnlessResumed { backstack.add(SetupSwishPayinKey) }, + onInvoiceSelected = dropUnlessResumed { backstack.add(SetupInvoicePayinKey) }, + navigateUp = backstack::navigateUp, + ) + } + + entry { + val viewModel: SetupSwishPayinViewModel = metroViewModel() + SetupSwishPayinDestination( + viewModel = viewModel, + globalSnackBarState = globalSnackBarState, + onSuccessfullyConnected = { backstack.popUpTo(inclusive = true) }, + navigateUp = backstack::navigateUp, + openUrl = { + backstack.popUpTo(inclusive = true) + openUrl(it) + }, + ) + } + + entry { + val viewModel: SetupInvoicePayinViewModel = metroViewModel() + SetupInvoicePayinDestination( + viewModel = viewModel, + globalSnackBarState = globalSnackBarState, + navigateUp = backstack::navigateUp, + ) + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt deleted file mode 100644 index 57b0d62d57..0000000000 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinNavGraph.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.hedvig.android.feature.payin.account.navigation - -import androidx.lifecycle.compose.dropUnlessResumed -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptionsBuilder -import com.hedvig.android.design.system.hedvig.GlobalSnackBarState -import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewDestination -import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState -import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel -import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodDestination -import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination -import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel -import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinDestination -import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinViewModel -import com.hedvig.android.navigation.compose.navDeepLinks -import com.hedvig.android.navigation.compose.navdestination -import com.hedvig.android.navigation.compose.navgraph -import com.hedvig.android.navigation.compose.typedPopBackStack -import com.hedvig.android.navigation.compose.typedPopUpTo -import com.hedvig.android.navigation.core.HedvigDeepLinkContainer -import octopus.type.MemberPaymentProvider -import org.koin.compose.viewmodel.koinViewModel - -fun NavGraphBuilder.payinAccountGraph( - navController: NavController, - globalSnackBarState: GlobalSnackBarState, - hedvigDeepLinkContainer: HedvigDeepLinkContainer, - navigateToConnectTrustly: (builder: NavOptionsBuilder.() -> Unit) -> Unit, - navigateUp: () -> Unit, - openUrl: (String) -> Unit, -) { - navgraph( - startDestination = PayinAccountDestinations.Overview::class, - deepLinks = navDeepLinks(hedvigDeepLinkContainer.connectPayment), - ) { - navdestination { - val viewModel: PayinAccountOverviewViewModel = koinViewModel() - PayinAccountOverviewDestination( - viewModel = viewModel, - onConnectPayoutMethodClicked = dropUnlessResumed { - val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content - navController.navigate( - PayinAccountDestinations.SelectPayinMethod( - availableProviders = content?.availablePayinMethods?.map { it.rawValue } ?: emptyList(), - ), - ) - }, - navigateUp = navigateUp, - ) - } - - navdestination { - SelectPayinMethodDestination( - availableProviders = this.availableProviders.map { MemberPaymentProvider.safeValueOf(it) }, - onTrustlySelected = dropUnlessResumed { - navigateToConnectTrustly { - typedPopUpTo { - inclusive = true - } - } - }, - onSwishSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupSwishPayin) }, - onInvoiceSelected = dropUnlessResumed { navController.navigate(PayinAccountDestinations.SetupInvoicePayin) }, - navigateUp = navController::navigateUp, - ) - } - - navdestination { - val viewModel: SetupSwishPayinViewModel = koinViewModel() - SetupSwishPayinDestination( - viewModel = viewModel, - globalSnackBarState = globalSnackBarState, - onSuccessfullyConnected = { - navController.typedPopBackStack(inclusive = true) - }, - navigateUp = navController::navigateUp, - openUrl = { - navController.typedPopBackStack(inclusive = true) - openUrl(it) - }, - ) - } - - navdestination { - val viewModel: SetupInvoicePayinViewModel = koinViewModel() - SetupInvoicePayinDestination( - viewModel = viewModel, - globalSnackBarState = globalSnackBarState, - onSuccessfullyConnected = { - navController.typedPopBackStack(inclusive = true) - }, - navigateUp = navController::navigateUp, - ) - } - } -} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 36c52fc533..3e30feaa45 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -142,7 +142,7 @@ private fun PayoutAccountContent( text = "You haven’t added a billing method yet. Add one to pay for your insurance.", // todo description = null, iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), ) } } else { @@ -272,23 +272,30 @@ private fun CurrentPayinMethodRow( verticalAlignment = Alignment.CenterVertically, ) { when (provider) { - MemberPaymentProvider.TRUSTLY -> Icon( - HedvigIcons.Autogiro, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.TRUSTLY -> { + Icon( + HedvigIcons.Autogiro, + null, // todo + modifier = Modifier.size(32.dp), + ) + } - MemberPaymentProvider.SWISH -> Image( - HedvigIcons.Swish, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.SWISH -> { + Image( + HedvigIcons.Swish, + null, // todo + modifier = Modifier.size(32.dp), + ) + } + + MemberPaymentProvider.INVOICE -> { + Image( + HedvigIcons.Kivra, + null, // todo + modifier = Modifier.size(32.dp), + ) + } - MemberPaymentProvider.INVOICE -> Image( - HedvigIcons.Kivra, - null, //todo - modifier = Modifier.size(32.dp), - ) else -> {} } @@ -296,7 +303,7 @@ private fun CurrentPayinMethodRow( Column { HedvigText(text = label) HedvigText( - text = text , + text = text, color = HedvigTheme.colorScheme.textSecondary, ) } @@ -339,15 +346,13 @@ internal fun formatSwishPhoneNumber(phoneNumber: String): String { val sb = StringBuilder() for (i in digits.indices) { sb.append(digits[i]) - if (i in setOf(2, 5, 7) - ) { + if (i in setOf(2, 5, 7)) { sb.append("-") } } return sb.toString() } - private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?, bankName: String?): String { logcat { "Mariia: clearingNumber: $clearingNumber accountNumber: $accountNumber bankName: $bankName" } // return when { @@ -443,11 +448,11 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr isPending = false, isDefault = false, ), - PayinAccount.Invoice ( - delivery = PaymentMethodInvoiceDelivery.KIVRA, + PayinAccount.Invoice( + delivery = PaymentMethodInvoiceDelivery.KIVRA, isPending = false, isDefault = false, - email = "" + email = "", ), ), availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index c54498db31..060361cefb 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -8,6 +8,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.ActivityRetainedScope +import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase @@ -15,8 +17,11 @@ import com.hedvig.android.logger.logcat import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel +import dev.zacsweers.metro.Inject import octopus.type.MemberPaymentProvider +@Inject +@HedvigViewModel(ActivityRetainedScope::class) internal class PayinAccountOverviewViewModel( getPayinAccountUseCase: GetPayinAccountUseCase, setAsDefaultUseCase: SetAsDefaultUseCase, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 9caa88ad2c..d98e20105b 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -95,23 +95,29 @@ private fun PayinMethodRow( verticalAlignment = Alignment.CenterVertically, ) { when (provider) { - MemberPaymentProvider.TRUSTLY -> Icon( - HedvigIcons.Autogiro, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.TRUSTLY -> { + Icon( + HedvigIcons.Autogiro, + null, // todo + modifier = Modifier.size(32.dp), + ) + } - MemberPaymentProvider.SWISH -> Image( - HedvigIcons.Swish, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.SWISH -> { + Image( + HedvigIcons.Swish, + null, // todo + modifier = Modifier.size(32.dp), + ) + } - MemberPaymentProvider.INVOICE -> Image( - HedvigIcons.Kivra, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.INVOICE -> { + Image( + HedvigIcons.Kivra, + null, // todo + modifier = Modifier.size(32.dp), + ) + } else -> {} } @@ -120,24 +126,31 @@ private fun PayinMethodRow( Column(Modifier.padding(vertical = 12.dp)) { HedvigText( text = when (provider) { - MemberPaymentProvider.TRUSTLY -> "Direct debit" // todo + MemberPaymentProvider.TRUSTLY -> "Direct debit" + + // todo MemberPaymentProvider.SWISH -> stringResource(Res.string.swish) + MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENTS_INVOICE) + else -> "" }, ) HedvigText( text = when (provider) { - MemberPaymentProvider.TRUSTLY -> "Connect your bank via Trustly" // todo - MemberPaymentProvider.SWISH -> "Monthly auto-payments via Swish" // todo + MemberPaymentProvider.TRUSTLY -> "Connect your bank via Trustly" + + // todo + MemberPaymentProvider.SWISH -> "Monthly auto-payments via Swish" + + // todo MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION) + else -> "" }, color = HedvigTheme.colorScheme.textSecondary, ) } - - } } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt index c226ae1559..caeae878d6 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinDestination.kt @@ -43,8 +43,8 @@ internal fun SetupInvoicePayinDestination( SetupInvoicePayinScreen( uiState = uiState, globalSnackBarState = globalSnackBarState, - onConnect = { viewModel.emit(SetupInvoicePayoutEvent.Connect) }, - showedSnackBar = { viewModel.emit(SetupInvoicePayoutEvent.ShowedSnackBar) }, + onConnect = { viewModel.emit(SetupInvoicePayinEvent.Connect) }, + showedSnackBar = { viewModel.emit(SetupInvoicePayinEvent.ShowedSnackBar) }, navigateUp = navigateUp, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt index ce17db0dc4..17d91cbd26 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupinvoice/SetupInvoicePayinViewModel.kt @@ -9,9 +9,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel -import com.hedvig.android.feature.payoutaccount.data.SetupInvoicePayoutUseCase -import com.hedvig.android.feature.payoutaccount.navigation.SelectPayoutMethodKey import com.hedvig.android.feature.payin.account.data.SetupInvoicePayinUseCase +import com.hedvig.android.feature.payin.account.navigation.SelectPayinMethodKey import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -24,15 +23,15 @@ import dev.zacsweers.metro.Inject internal class SetupInvoicePayinViewModel( setupInvoicePayinUseCase: SetupInvoicePayinUseCase, backstack: Backstack, -) : MoleculeViewModel( - SetupInvoicePayinUiState(false, null, false), - SetupInvoicePayinPresenter(setupInvoicePayinUseCase, backstack), +) : MoleculeViewModel( + SetupInvoicePayinUiState(false, null, false), + SetupInvoicePayinPresenter(setupInvoicePayinUseCase, backstack), ) -internal sealed interface SetupInvoicePayoutEvent { - data object Connect : SetupInvoicePayoutEvent +internal sealed interface SetupInvoicePayinEvent { + data object Connect : SetupInvoicePayinEvent - data object ShowedSnackBar : SetupInvoicePayoutEvent + data object ShowedSnackBar : SetupInvoicePayinEvent } internal data class SetupInvoicePayinUiState( @@ -41,15 +40,12 @@ internal data class SetupInvoicePayinUiState( val showSuccessSnackBar: Boolean, ) -internal class SetupInvoicePayoutPresenter( - private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, - private val backstack: Backstack, -) : MoleculePresenter { internal class SetupInvoicePayinPresenter( private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, -) : MoleculePresenter { + private val backstack: Backstack, +) : MoleculePresenter { @Composable - override fun MoleculePresenterScope.present( + override fun MoleculePresenterScope.present( lastState: SetupInvoicePayinUiState, ): SetupInvoicePayinUiState { var isLoading by remember { mutableStateOf(false) } @@ -79,15 +75,15 @@ internal class SetupInvoicePayinPresenter( CollectEvents { event -> when (event) { - SetupInvoicePayoutEvent.Connect -> { + SetupInvoicePayinEvent.Connect -> { if (!isLoading) { shouldConnect = true connectIteration++ } } - SetupInvoicePayoutEvent.ShowedSnackBar -> { - backstack.popUpTo(inclusive = true) + SetupInvoicePayinEvent.ShowedSnackBar -> { + backstack.popUpTo(inclusive = true) } } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 1fa8c9484e..c4d276553f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -121,45 +121,46 @@ private fun SetupSwishPayoutScreen( ) } AnimatedVisibility(uiState.successUrl != null) { - if (uiState.successUrl != null) - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - EmptyState( - text = "Please give your approval in the Swish app", - description = null, - modifier = Modifier.fillMaxWidth(), - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SWISH, - ) - Spacer(Modifier.height(16.dp)) + if (uiState.successUrl != null) { Column( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - Surface( - color = Color.White, - shape = HedvigTheme.shapes.cornerMedium, - border = HedvigTheme.colorScheme.borderPrimary, - modifier = Modifier.padding(16.dp), + EmptyState( + text = "Please give your approval in the Swish app", + description = null, + modifier = Modifier.fillMaxWidth(), + iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SWISH, + ) + Spacer(Modifier.height(16.dp)) + Column( + horizontalAlignment = Alignment.CenterHorizontally, ) { - QRCode( - token = uiState.successUrl, - modifier = Modifier - .size(180.dp) - .padding(16.dp), - ) + Surface( + color = Color.White, + shape = HedvigTheme.shapes.cornerMedium, + border = HedvigTheme.colorScheme.borderPrimary, + modifier = Modifier.padding(16.dp), + ) { + QRCode( + token = uiState.successUrl, + modifier = Modifier + .size(180.dp) + .padding(16.dp), + ) + } } + Spacer(Modifier.height(16.dp)) + HedvigButton( + "Open Swish", + enabled = true, + onClick = { + openUrl(uiState.successUrl) + }, + buttonStyle = ButtonDefaults.ButtonStyle.PrimaryAlt, + buttonSize = ButtonDefaults.ButtonSize.Medium, + ) } - Spacer(Modifier.height(16.dp)) - HedvigButton( - "Open Swish", - enabled = true, - onClick = { - openUrl(uiState.successUrl) - }, - buttonStyle = ButtonDefaults.ButtonStyle.PrimaryAlt, - buttonSize = ButtonDefaults.ButtonSize.Medium, - ) } } Spacer(Modifier.weight(1f)) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt index 14e24ca1b0..6b0100f4b5 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -8,12 +8,17 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.ActivityRetainedScope +import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase import com.hedvig.android.feature.payin.account.data.SetupSwishResponse import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel +import dev.zacsweers.metro.Inject +@Inject +@HedvigViewModel(ActivityRetainedScope::class) internal class SetupSwishPayinViewModel( setupSwishPayoutUseCase: SetupSwishPayinUseCase, ) : MoleculeViewModel( diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt index 90cda4d988..0cc4ca226f 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt @@ -223,7 +223,7 @@ private fun MemberPaymentDetailsSuccessScreen( Spacer(Modifier.weight(1f)) Spacer(Modifier.height(16.dp)) HedvigButton( - text = stringResource(Res.string.MANAGE_BILLING_METHODS_BUTTON),//todo + text = stringResource(Res.string.MANAGE_BILLING_METHODS_BUTTON), // todo onClick = onChangeBankAccount, enabled = true, buttonStyle = ButtonDefaults.ButtonStyle.Secondary, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt index ba9b7cab5b..3dac4d88a6 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt @@ -28,7 +28,7 @@ internal class MemberPaymentDetailsViewModel( presenter = MemberPaymentDetailsPresenter( getMemberPaymentsDetailsUseCase, ), - sharingStarted = SharingStarted.WhileSubscribed(2.seconds), + sharingStarted = SharingStarted.WhileSubscribed(2.seconds), ) private class MemberPaymentDetailsPresenter( diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt index a947a55ab0..88cd9cb582 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/PayoutAccount.kt @@ -1,6 +1,5 @@ package com.hedvig.android.feature.payoutaccount.data - internal sealed interface PayoutAccount { val isPending: Boolean diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountDestination.kt index ab443beb93..81b79fa70e 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountDestination.kt @@ -16,6 +16,3 @@ internal data object EditBankAccountKey : HedvigNavKey @Serializable internal data object SetupSwishPayoutKey : HedvigNavKey - -@Serializable -internal data object SetupInvoicePayoutKey : HedvigNavKey diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt index 2f6947d539..097984bf95 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt @@ -9,8 +9,6 @@ import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOvervie import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewViewModel import com.hedvig.android.feature.payoutaccount.ui.selectmethod.SelectPayoutMethodDestination -import com.hedvig.android.feature.payoutaccount.ui.setupinvoice.SetupInvoicePayoutDestination -import com.hedvig.android.feature.payoutaccount.ui.setupinvoice.SetupInvoicePayoutViewModel import com.hedvig.android.feature.payoutaccount.ui.setupswish.SetupSwishPayoutDestination import com.hedvig.android.feature.payoutaccount.ui.setupswish.SetupSwishPayoutViewModel import com.hedvig.android.navigation.common.HedvigNavKey @@ -54,7 +52,6 @@ fun EntryProviderScope.payoutAccountEntries( }, onNordeaSelected = dropUnlessResumed { backstack.add(EditBankAccountKey) }, onSwishSelected = dropUnlessResumed { backstack.add(SetupSwishPayoutKey) }, - onInvoiceSelected = dropUnlessResumed { backstack.add(SetupInvoicePayoutKey) }, navigateUp = backstack::navigateUp, ) } @@ -76,13 +73,4 @@ fun EntryProviderScope.payoutAccountEntries( navigateUp = backstack::navigateUp, ) } - - entry { - val viewModel: SetupInvoicePayoutViewModel = metroViewModel() - SetupInvoicePayoutDestination( - viewModel = viewModel, - globalSnackBarState = globalSnackBarState, - navigateUp = backstack::navigateUp, - ) - } } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt index d3f2870caf..8146ff910b 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt @@ -35,7 +35,6 @@ import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOvervie import hedvig.resources.CHANGE_PAYOUT_METHOD_BUTTON_LABEL import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE import hedvig.resources.PAYMENTS_ACCOUNT -import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYOUT_MISSING_INFO import hedvig.resources.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE import hedvig.resources.PAYOUT_NO_PAYOUT_OPTIONS_TITLE @@ -47,7 +46,6 @@ import hedvig.resources.Res import hedvig.resources.swish import hedvig.resources.trustly import octopus.type.MemberPaymentProvider -import octopus.type.PaymentMethodInvoiceDelivery import org.jetbrains.compose.resources.stringResource @Composable diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 8515f65068..615aea8f45 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -67,7 +67,7 @@ internal fun SelectPayoutMethodDestination( MemberPaymentProvider.NORDEA -> { PayoutMethodRow( title = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), - subtitle = "Payout to a bank account", //todo: removed BANK_PAYOUT_METHOD_CARD_DESCRIPTION for demo + subtitle = "Payout to a bank account", // todo: removed BANK_PAYOUT_METHOD_CARD_DESCRIPTION for demo onClick = onNordeaSelected, provider = provider, ) @@ -107,23 +107,29 @@ private fun PayoutMethodRow( verticalAlignment = Alignment.CenterVertically, ) { when (provider) { - MemberPaymentProvider.TRUSTLY -> Icon( - HedvigIcons.Link, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.TRUSTLY -> { + Icon( + HedvigIcons.Link, + null, // todo + modifier = Modifier.size(32.dp), + ) + } - MemberPaymentProvider.SWISH -> Image( - HedvigIcons.Swish, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.SWISH -> { + Image( + HedvigIcons.Swish, + null, // todo + modifier = Modifier.size(32.dp), + ) + } - MemberPaymentProvider.NORDEA -> Icon( - HedvigIcons.Card, - null, //todo - modifier = Modifier.size(32.dp), - ) + MemberPaymentProvider.NORDEA -> { + Icon( + HedvigIcons.Card, + null, // todo + modifier = Modifier.size(32.dp), + ) + } else -> {} } diff --git a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt index eb2cbfa68d..6a54c07c5e 100644 --- a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt +++ b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderCards.kt @@ -348,7 +348,7 @@ private fun ReminderCardConnectPayment( modifier = modifier, priority = NotificationPriority.Attention, style = InfoCardStyle.Button( - buttonText = "Setup payment method", //todo + buttonText = "Setup payment method", // todo onButtonClick = navigateToConnectPayment, ), minLines = minLines, diff --git a/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/GetMemberQuickActionsUseCase.kt b/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/GetMemberQuickActionsUseCase.kt index 88a9674526..17c8930098 100644 --- a/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/GetMemberQuickActionsUseCase.kt +++ b/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/GetMemberQuickActionsUseCase.kt @@ -128,7 +128,7 @@ internal class GetMemberQuickActionsUseCaseImpl( QuickAction.StandaloneQuickLink( quickLinkDestination = QuickLinkDestination.OuterDestination.QuickLinkConnectPayment, titleRes = Res.string.HC_QUICK_ACTIONS_PAYMENTS_TITLE, - hintTextRes = Res.string.MANAGE_BILLING_METHODS_BUTTON, //todo!!! + hintTextRes = Res.string.MANAGE_BILLING_METHODS_BUTTON, // todo!!! ), ) } From cc4defc7112e044a757734cb0d71e3836e1250b1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 26 Aug 2026 16:00:30 +0200 Subject: [PATCH 32/75] remove logs --- .../account/ui/overview/PayinAccountOverviewDestination.kt | 7 +++---- .../account/ui/overview/PayinAccountOverviewViewModel.kt | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 3e30feaa45..78cf692488 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -30,7 +30,6 @@ import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced import com.hedvig.android.design.system.hedvig.HedvigNotificationCard -import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText @@ -47,7 +46,6 @@ import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.toDeliveryString -import com.hedvig.android.logger.logcat import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res @@ -142,7 +140,9 @@ private fun PayoutAccountContent( text = "You haven’t added a billing method yet. Add one to pay for your insurance.", // todo description = null, iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), ) } } else { @@ -354,7 +354,6 @@ internal fun formatSwishPhoneNumber(phoneNumber: String): String { } private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?, bankName: String?): String { - logcat { "Mariia: clearingNumber: $clearingNumber accountNumber: $accountNumber bankName: $bankName" } // return when { // clearingNumber != null && accountNumber != null && bankName != null -> "$bankName $clearingNumber-$accountNumber" // clearingNumber != null && bankName != null -> "$bankName $clearingNumber" diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index 060361cefb..a07e8835eb 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -13,7 +13,6 @@ import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase -import com.hedvig.android.logger.logcat import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -77,7 +76,6 @@ internal class PayinAccountOverviewPresenter( LaunchedEffect(providerToSetAsDefault) { val provider = providerToSetAsDefault if (provider != null) { - logcat { "Mariia: Starting LaunchedEffect(providerToSetAsDefault) with provider: $provider" } val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@LaunchedEffect uiState = currentState.copy(loadingDefaultProvider = provider) setAsDefaultUseCase.invoke(provider).fold( From 3f9f0cb5e75fc73ab43dcab69a5e888d68f3fd8d Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 26 Aug 2026 16:26:22 +0200 Subject: [PATCH 33/75] always show payment details --- .../android/feature/payments/ui/payments/PaymentsDestination.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt index 5d674040e5..259c488298 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt @@ -495,7 +495,7 @@ private fun PaymentsListItems( MemberType.STANDARD_MEMBER, MemberType.STANDARD_TO_QASA_MEMBER, - -> uiState.connectedPaymentInfo is ConnectedPaymentInfo.Active + -> true } if (showPaymentDetailsItem) { PaymentsListItem( From c1e9bb6695043ec9492b25ef600c5eec5c27c53a Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 26 Aug 2026 16:38:10 +0200 Subject: [PATCH 34/75] navigation to Payin module --- app/app/build.gradle.kts | 1 + .../app/navigation/HedvigEntryProvider.kt | 5 +++++ .../build.gradle.kts | 20 +++++++++++++++++++ .../account/navigation/PayinAccountKey.kt | 7 +++++++ .../feature-payin-account/build.gradle.kts | 1 + .../navigation/PayinAccountDestination.kt | 3 --- .../payments/navigation/PaymentsEntries.kt | 3 ++- .../member-quick-actions/build.gradle.kts | 2 +- .../memberquickactions/QuickLinkNavKey.kt | 4 ++-- 9 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 app/feature/feature-payin-account-navigation/build.gradle.kts create mode 100644 app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 6337c88dab..f22cef8a0e 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -213,6 +213,7 @@ dependencies { implementation(projects.featureRemoveAddons) implementation(projects.featurePayinAccount) + implementation(projects.featurePayinAccountNavigation) implementation(projects.featurePayoutAccount) implementation(projects.featurePayments) implementation(projects.featureProfile) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index a8b7ce7c26..82eca14676 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -50,6 +50,7 @@ import com.hedvig.android.feature.movingflow.SelectContractForMovingKey import com.hedvig.android.feature.movingflow.movingFlowEntries import com.hedvig.android.feature.onboarding.data.ResetOnboardingSeenUseCase import com.hedvig.android.feature.onboarding.navigation.onboardingEntries +import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey import com.hedvig.android.feature.payin.account.navigation.payinAccountEntries import com.hedvig.android.feature.payments.navigation.paymentsEntries import com.hedvig.android.feature.payoutaccount.navigation.PayoutAccountKey @@ -105,6 +106,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( ) { val shouldShowRequestPermissionRationale: (String) -> Boolean = androidAppHost::shouldShowPermissionRationale val navigateToConnectPayment: () -> Unit = { backstack.add(TrustlyKey) } + val navigateToPayinAccount: () -> Unit = { backstack.add(PayinAccountKey) } val navigateToPayoutAccount: () -> Unit = { backstack.add(PayoutAccountKey) } val navigateToInbox: () -> Unit = { backstack.add(InboxKey) } val navigateToNewConversation: () -> Unit = { backstack.add(ChatKey(Uuid.randomUUID().toString())) } @@ -160,6 +162,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( globalSnackBarState = globalSnackBarState, openUrl = openUrl, navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, navigateToNewConversation = navigateToNewConversation, ) @@ -460,12 +463,14 @@ private fun EntryProviderScope.addPaymentsEntries( globalSnackBarState: GlobalSnackBarState, openUrl: (String) -> Unit, navigateToConnectPayment: () -> Unit, + navigateToPayinAccount: () -> Unit, navigateToPayoutAccount: () -> Unit, navigateToNewConversation: () -> Unit, ) { paymentsEntries( backstack = backstack, navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, openConversation = navigateToNewConversation, ) diff --git a/app/feature/feature-payin-account-navigation/build.gradle.kts b/app/feature/feature-payin-account-navigation/build.gradle.kts new file mode 100644 index 0000000000..288520f5a2 --- /dev/null +++ b/app/feature/feature-payin-account-navigation/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("hedvig.multiplatform.library") + id("hedvig.multiplatform.library.android") + id("hedvig.gradle.plugin") +} + +hedvig { + serialization() + navKeys() +} + +kotlin { + sourceSets { + commonMain.dependencies { + implementation(libs.kotlinx.serialization.core) + implementation(projects.coreCommonPublic) + implementation(projects.navigationCommon) + } + } +} diff --git a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt new file mode 100644 index 0000000000..8b47fa2db5 --- /dev/null +++ b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt @@ -0,0 +1,7 @@ +package com.hedvig.android.feature.payin.account.navigation + +import com.hedvig.android.navigation.common.HedvigNavKey +import kotlinx.serialization.Serializable + +@Serializable +data object PayinAccountKey : HedvigNavKey diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts index 5509a2aac3..de1f67c237 100644 --- a/app/feature/feature-payin-account/build.gradle.kts +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { implementation(projects.coreCommonPublic) implementation(projects.coreResources) implementation(projects.designSystemHedvig) + implementation(projects.featurePayinAccountNavigation) implementation(projects.moleculePublic) implementation(projects.navigationCommon) implementation(projects.navigationCompose) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index eb416e7207..50956dfa1f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -3,9 +3,6 @@ package com.hedvig.android.feature.payin.account.navigation import com.hedvig.android.navigation.common.HedvigNavKey import kotlinx.serialization.Serializable -@Serializable -data object PayinAccountKey : HedvigNavKey - @Serializable internal data class SelectPayinMethodKey( val availableProviders: List, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt index 3d389d4d39..5bdb77f1fb 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt @@ -31,6 +31,7 @@ import dev.zacsweers.metrox.viewmodel.metroViewModel fun EntryProviderScope.paymentsEntries( backstack: Backstack, navigateToConnectPayment: () -> Unit, + navigateToPayinAccount: () -> Unit, navigateToPayoutAccount: () -> Unit, openConversation: () -> Unit, ) { @@ -129,7 +130,7 @@ fun EntryProviderScope.paymentsEntries( val viewModel: MemberPaymentDetailsViewModel = metroViewModel() MemberPaymentDetailsDestination( viewModel, - onChangeBankAccount = navigateToConnectPayment, + onChangeBankAccount = navigateToPayinAccount, navigateUp = backstack::navigateUp, ) } diff --git a/app/shared/member-quick-actions/build.gradle.kts b/app/shared/member-quick-actions/build.gradle.kts index 06f3445466..1f9861e279 100644 --- a/app/shared/member-quick-actions/build.gradle.kts +++ b/app/shared/member-quick-actions/build.gradle.kts @@ -40,9 +40,9 @@ kotlin { implementation(projects.uiEmergency) // -navigation modules for QuickLinkDestination.toNavKey() (Task 3) implementation(projects.featureChooseTierNavigation) - implementation(projects.featureConnectPaymentTrustlyNavigation) implementation(projects.featureEditCoinsuredNavigation) implementation(projects.featureMovingflowNavigation) + implementation(projects.featurePayinAccountNavigation) implementation(projects.featureTerminateInsuranceNavigation) implementation(projects.featureTravelCertificateNavigation) } diff --git a/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/QuickLinkNavKey.kt b/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/QuickLinkNavKey.kt index 12d5713301..7500eda0ae 100644 --- a/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/QuickLinkNavKey.kt +++ b/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/QuickLinkNavKey.kt @@ -2,12 +2,12 @@ package com.hedvig.android.memberquickactions import com.hedvig.android.data.coinsured.CoInsuredFlowType import com.hedvig.android.feature.change.tier.navigation.StartTierFlowChooseInsuranceKey -import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyKey import com.hedvig.android.feature.editcoinsured.navigation.CoInsuredAddInfoKey import com.hedvig.android.feature.editcoinsured.navigation.CoInsuredAddOrRemoveKey import com.hedvig.android.feature.editcoinsured.navigation.EditCoInsuredTriageKey import com.hedvig.android.feature.movingflow.MovingSource import com.hedvig.android.feature.movingflow.SelectContractForMovingKey +import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey import com.hedvig.android.feature.terminateinsurance.navigation.TerminateInsuranceKey import com.hedvig.android.feature.travelcertificate.navigation.TravelCertificateKey import com.hedvig.android.memberquickactions.QuickLinkDestination.OuterDestination.ChooseInsuranceForEditCoInsured @@ -48,7 +48,7 @@ fun QuickLinkDestination.OuterDestination.toNavKey(): HedvigNavKey = when (this) } QuickLinkConnectPayment -> { - TrustlyKey + PayinAccountKey } QuickLinkTermination -> { From d111bf4432d47fdd177d751b7b7c1f8154d6ee42 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 31 Aug 2026 15:41:02 +0200 Subject: [PATCH 35/75] connectPayment deeplink leads to payin overview now. scrollability --- .../trustly/navigation/TrustlyDeepLinks.kt | 2 +- .../feature-payin-account/build.gradle.kts | 1 + .../navigation/PayinAccountDeepLinks.kt | 19 +++++++++++++++++++ .../PayinAccountOverviewDestination.kt | 9 +++++++-- .../SelectPayinMethodDestination.kt | 5 ++++- .../SelectPayoutMethodDestination.kt | 3 ++- .../core/HedvigDeepLinkContainer.kt | 4 ++-- 7 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDeepLinks.kt diff --git a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/TrustlyDeepLinks.kt b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/TrustlyDeepLinks.kt index b224953b23..cbb1c6d30a 100644 --- a/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/TrustlyDeepLinks.kt +++ b/app/feature/feature-connect-payment-trustly/src/main/kotlin/com/hedvig/android/feature/connect/payment/trustly/navigation/TrustlyDeepLinks.kt @@ -16,5 +16,5 @@ internal class TrustlyDeepLinkMatcherProvider( private val container: HedvigDeepLinkContainer, ) : DeepLinkMatcherProvider { override fun matchers(): List> = - uriDeepLinkMatchers(container.connectPayment + container.directDebit, TrustlyKey.serializer()) + uriDeepLinkMatchers(container.directDebit, TrustlyKey.serializer()) } diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts index de1f67c237..e4ca94d73d 100644 --- a/app/feature/feature-payin-account/build.gradle.kts +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -29,5 +29,6 @@ dependencies { implementation(projects.moleculePublic) implementation(projects.navigationCommon) implementation(projects.navigationCompose) + implementation(projects.navigationCore) implementation(libs.zXing) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDeepLinks.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDeepLinks.kt new file mode 100644 index 0000000000..c0ec6f1b3b --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDeepLinks.kt @@ -0,0 +1,19 @@ +package com.hedvig.android.feature.payin.account.navigation + +import androidx.navigation3.runtime.deeplink.DeepLinkMatcher +import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.navigation.common.HedvigNavKey +import com.hedvig.android.navigation.compose.DeepLinkMatcherProvider +import com.hedvig.android.navigation.compose.uriDeepLinkMatchers +import com.hedvig.android.navigation.core.HedvigDeepLinkContainer +import dev.zacsweers.metro.ContributesIntoSet +import dev.zacsweers.metro.Inject + +@ContributesIntoSet(AppScope::class) +@Inject +internal class PayinAccountDeepLinkMatcherProvider( + private val container: HedvigDeepLinkContainer, +) : DeepLinkMatcherProvider { + override fun matchers(): List> = + uriDeepLinkMatchers(container.connectPayment, PayinAccountKey.serializer()) +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 78cf692488..91b5eb42e5 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -131,7 +133,8 @@ private fun PayoutAccountContent( setDefaultProviderError: ErrorMessage?, modifier: Modifier = Modifier, ) { - Column(modifier) { + Column(modifier + .verticalScroll(rememberScrollState())) { Spacer(Modifier.height(8.dp)) if (currentMethods.isEmpty()) { if (availablePayinMethods.isNotEmpty()) { @@ -224,8 +227,9 @@ private fun PayoutAccountContent( ) } } - Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Column { if (currentMethods.any { it.isPending }) { + Spacer(Modifier.height(16.dp)) HedvigNotificationCard( message = "You have just added or changed a billing method, it will appear here soon.", // todo priority = NotificationPriority.Info, @@ -235,6 +239,7 @@ private fun PayoutAccountContent( ) } if (availablePayinMethods.isNotEmpty()) { + Spacer(Modifier.height(16.dp)) HedvigButton( text = "Add a payment method", // todo! onClick = onConnectPayinMethodClicked, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index d98e20105b..fcc0d2fc62 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -48,7 +48,10 @@ internal fun SelectPayinMethodDestination( modifier = Modifier.fillMaxSize(), ) { Spacer(Modifier.height(8.dp)) - Column(Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Column( + Modifier.padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { for (provider in availableProviders) { PayinMethodRow( provider = provider, diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 615aea8f45..acf6566103 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon @@ -147,7 +148,7 @@ private fun PayoutMethodRow( } @Composable -@HedvigPreview +@HedvigShortMultiScreenPreview private fun PreviewSelectPayoutMethodScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { diff --git a/app/navigation/navigation-core/src/commonMain/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt b/app/navigation/navigation-core/src/commonMain/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt index dd648efb8b..d12fcdd887 100644 --- a/app/navigation/navigation-core/src/commonMain/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt +++ b/app/navigation/navigation-core/src/commonMain/kotlin/com/hedvig/android/navigation/core/HedvigDeepLinkContainer.kt @@ -43,9 +43,9 @@ interface HedvigDeepLinkContainer { val profile: List // The profile screen, which acts as a gateway to several app settings - // Screen where the member can connect their payment method to Hedvig to pay for insurance + // Payin account overview, where the member sees their current payin method and can change it val connectPayment: List - val directDebit: List // Same as connectPayment but to support an old link to it + val directDebit: List // Goes straight into the Trustly direct debit connection flow val eurobonus: List // The destination allowing to edit your current Eurobonus (SAS) number val payments: List // The payments screen, showing the payments history and the upcoming payment information val payout: List // Payout connection overview screen, can see existing options and change/add payout options From c0579e939302a8e2ac230a169767e1e154ab58b1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 31 Aug 2026 15:59:12 +0200 Subject: [PATCH 36/75] show member details even if default active is none --- .../data/GetMemberPaymentsDetailsUseCase.kt | 14 ++--- .../MemberPaymentDetailsDestination.kt | 51 ++++++++++++------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt index 253b43dc40..c397d709b8 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt @@ -47,26 +47,18 @@ internal class GetMemberPaymentsDetailsUseCaseImpl( val paymentMethods = result.currentMember.paymentMethods val payinMethod = paymentMethods.defaultPayinMethod ?: paymentMethods.payinMethods.find { it.isDefault && it.status == MemberPaymentMethodStatus.ACTIVE } - if (payinMethod == null) { - logcat(LogPriority.WARN) { "GetMemberPaymentsDetailsUseCase no active default payin method" } - raise(ErrorMessage()) - } - val paymentMethod = payinMethod.provider.toPaymentMethod() - if (paymentMethod == null) { - logcat(LogPriority.WARN) { "GetMemberPaymentsDetailsUseCase unknown provider: ${payinMethod.provider}" } - raise(ErrorMessage()) - } + val paymentMethod = payinMethod?.provider?.toPaymentMethod() MemberPaymentsDetails( paymentMethod = paymentMethod, chargingDayInTheMonth = paymentMethods.chargingDay, - account = payinMethod.details?.toPaymentAccount(), + account = payinMethod?.details?.toPaymentAccount(), ) } } } internal data class MemberPaymentsDetails( - val paymentMethod: PaymentMethod, + val paymentMethod: PaymentMethod?, val chargingDayInTheMonth: Int?, val account: PaymentAccount?, ) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt index 0cc4ca226f..d31fbf51fc 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt @@ -127,23 +127,33 @@ private fun MemberPaymentDetailsSuccessScreen( Column(modifier.padding(horizontal = 16.dp).verticalScroll(rememberScrollState())) { val explanationBottomSheetState = rememberHedvigBottomSheetState() ExplanationBottomSheet(explanationBottomSheetState) - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - HedvigText(stringResource(id = R.string.PAYMENTS_PAYMENT_METHOD)) - }, - endSlot = { - HedvigText( - text = paymentDetails.paymentMethod.label(), - textAlign = TextAlign.End, - modifier = Modifier.fillMaxWidth(), - color = HedvigTheme.colorScheme.textSecondary, - ) - }, - modifier = Modifier.padding(vertical = 16.dp), - spaceBetween = 8.dp, - ) - HorizontalDivider() - + val paymentMethod = paymentDetails.paymentMethod + if (paymentMethod != null) { + HorizontalItemsWithMaximumSpaceTaken( + startSlot = { + HedvigText(stringResource(id = R.string.PAYMENTS_PAYMENT_METHOD)) + }, + endSlot = { + HedvigText( + text = paymentMethod.label(), + textAlign = TextAlign.End, + modifier = Modifier.fillMaxWidth(), + color = HedvigTheme.colorScheme.textSecondary, + ) + }, + modifier = Modifier.padding(vertical = 16.dp), + spaceBetween = 8.dp, + ) + HorizontalDivider() + } else { + HedvigText( + "No active payment method", // todo!!!! + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + } val dayOfMonth = paymentDetails.chargingDayInTheMonth if (dayOfMonth != null) { HorizontalItemsWithMaximumSpaceTaken( @@ -347,6 +357,13 @@ private class MemberPaymentDetailsUiStatePreviewParameterProvider() : listOf( MemberPaymentDetailsUiState.Failure, MemberPaymentDetailsUiState.Loading, + MemberPaymentDetailsUiState.Success( + paymentDetails = MemberPaymentsDetails( + paymentMethod = null, + chargingDayInTheMonth = null, + account = null, + ), + ), MemberPaymentDetailsUiState.Success( paymentDetails = MemberPaymentsDetails( paymentMethod = PaymentMethod.TRUSTLY, From 495d925b6ba54b29871bd7619da338c98e0a19bf Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 31 Aug 2026 16:20:13 +0200 Subject: [PATCH 37/75] member reminders lead to payin overview --- .../android/app/navigation/HedvigEntryProvider.kt | 12 ++++++------ .../payin/account/data/GetPayinAccountUseCase.kt | 10 +++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index 1885ba4d72..5df8cbeede 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -139,7 +139,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( navigateToNewConversation = navigateToNewConversation, navigateToConversation = navigateToConversation, navigateToInbox = navigateToInbox, - navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, navigateToTravelCertificate = navigateToTravelCertificate, navigateToAddonPurchaseFlow = navigateToAddonPurchaseFlow, @@ -174,7 +174,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( languageService = languageService, externalNavigator = externalNavigator, openUrl = openUrl, - navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, navigateToNewConversation = navigateToNewConversation, onResetOnboardingForDebug = { @@ -248,7 +248,7 @@ private fun EntryProviderScope.addHomeEntries( navigateToNewConversation: () -> Unit, navigateToConversation: (String) -> Unit, navigateToInbox: () -> Unit, - navigateToConnectPayment: () -> Unit, + navigateToPayinAccount: () -> Unit, navigateToPayoutAccount: () -> Unit, navigateToTravelCertificate: () -> Unit, navigateToAddonPurchaseFlow: (List) -> Unit, @@ -272,7 +272,7 @@ private fun EntryProviderScope.addHomeEntries( onNavigateToInbox = navigateToInbox, onNavigateToNewConversation = navigateToNewConversation, navigateToClaimDetails = { claimId -> backstack.add(ClaimDetailsKey(claimId)) }, - navigateToConnectPayment = navigateToConnectPayment, + navigateToConnectPayment = navigateToPayinAccount, navigateToConnectPayout = navigateToPayoutAccount, navigateToContactInfo = { backstack.add(ContactInfoKey) }, navigateToMissingInfo = { contractId: String, type: CoInsuredFlowType -> @@ -497,7 +497,7 @@ private fun EntryProviderScope.addProfileEntries( languageService: LanguageService, externalNavigator: ExternalNavigator, openUrl: (String) -> Unit, - navigateToConnectPayment: () -> Unit, + navigateToPayinAccount: () -> Unit, navigateToPayoutAccount: () -> Unit, navigateToNewConversation: () -> Unit, onResetOnboardingForDebug: () -> Unit, @@ -515,7 +515,7 @@ private fun EntryProviderScope.addProfileEntries( globalSnackBarState = globalSnackBarState, backstack = backstack, hedvigBuildConstants = hedvigBuildConstants, - navigateToConnectPayment = navigateToConnectPayment, + navigateToConnectPayment = navigateToPayinAccount, navigateToConnectPayout = navigateToPayoutAccount, navigateToAddMissingInfo = { contractId: String, type: CoInsuredFlowType -> backstack.add(CoInsuredAddInfoKey(contractId, type)) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 654b2a8ace..bc72885d5c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -3,13 +3,12 @@ package com.hedvig.android.feature.payin.account.data import arrow.core.Either import arrow.core.raise.either import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.cache.normalized.FetchPolicy -import com.apollographql.apollo.cache.normalized.fetchPolicy +import com.apollographql.cache.normalized.FetchPolicy +import com.apollographql.cache.normalized.fetchPolicy import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope -import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import octopus.GetPayinMethodsQuery @@ -31,7 +30,6 @@ internal class GetPayinAccountUseCase( private val apolloClient: ApolloClient, ) { suspend fun invoke(): Either = either { - logcat { "Mariia: GetPayinAccountUseCase launching" } val result = apolloClient .query(GetPayinMethodsQuery()) .fetchPolicy(FetchPolicy.NetworkOnly) @@ -84,12 +82,10 @@ internal class GetPayinAccountUseCase( .filter { it.supportsPayin } .map { it.provider } - val finalResult = PayinAccountData( + PayinAccountData( currentMethods = currentMethods, availablePayinMethods = availablePayinMethods, ) - logcat { "Mariia: GetPayinAccountUseCase finalResult: $finalResult" } - finalResult } } From a39ea3d6b8b16b9fa158f2b95ef26a46cfaeb159 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 31 Aug 2026 16:26:32 +0200 Subject: [PATCH 38/75] onboarding step leads to payin overview --- app/feature/feature-onboarding/build.gradle.kts | 2 +- .../feature/onboarding/navigation/OnboardingNavigator.kt | 6 +++--- .../onboarding/ui/payment/OnboardingPaymentPresenterTest.kt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/feature/feature-onboarding/build.gradle.kts b/app/feature/feature-onboarding/build.gradle.kts index 1242e3ea4b..fe7e6f5b49 100644 --- a/app/feature/feature-onboarding/build.gradle.kts +++ b/app/feature/feature-onboarding/build.gradle.kts @@ -38,9 +38,9 @@ dependencies { implementation(projects.dataSettingsDatastorePublic) implementation(projects.designSystemHedvig) implementation(projects.dataCoinsured) - implementation(projects.featureConnectPaymentTrustlyNavigation) implementation(projects.featureEditCoinsuredNavigation) implementation(projects.featureFlags) + implementation(projects.featurePayinAccountNavigation) implementation(projects.foreverUi) implementation(projects.moleculePublic) implementation(projects.navigationCommon) diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt index 8bfb869449..a5a78c46a7 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt @@ -2,10 +2,10 @@ package com.hedvig.android.feature.onboarding.navigation import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.data.coinsured.CoInsuredFlowType -import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyKey import com.hedvig.android.feature.editcoinsured.navigation.CoInsuredAddInfoKey import com.hedvig.android.feature.onboarding.data.CompleteOnboardingUseCase import com.hedvig.android.feature.onboarding.data.OnboardingSessionStore +import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey import com.hedvig.android.logger.logcat import com.hedvig.android.navigation.compose.Backstack import com.hedvig.android.navigation.compose.add @@ -66,9 +66,9 @@ internal class OnboardingNavigator( backstack.add(CoInsuredAddInfoKey(contractId, type)) } - /** Pushes the Trustly connect-payment flow; it pops itself back here when done. */ + /** Pushes the payin account overview, where the member picks a payin method. */ fun openConnectPayment() { - backstack.add(TrustlyKey) + backstack.add(PayinAccountKey) } /** Pushes the shared Forever screen; the member leaves it with system back. */ diff --git a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt index 0a16a21729..e1970b8586 100644 --- a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt +++ b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt @@ -7,7 +7,6 @@ import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isInstanceOf import assertk.assertions.isTrue -import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyKey import com.hedvig.android.feature.onboarding.FakeOnboardingMemberIdProvider import com.hedvig.android.feature.onboarding.FakeOnboardingRepository import com.hedvig.android.feature.onboarding.data.CompleteOnboardingUseCase @@ -17,6 +16,7 @@ import com.hedvig.android.feature.onboarding.navigation.OnboardingStepId import com.hedvig.android.feature.onboarding.navigation.OnboardingStepKey import com.hedvig.android.feature.onboarding.testOnboardingData import com.hedvig.android.feature.onboarding.testSessionStore +import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey import com.hedvig.android.logger.TestLogcatLoggingRule import com.hedvig.android.molecule.test.test import com.hedvig.android.navigation.common.HedvigNavKey @@ -57,7 +57,7 @@ internal class OnboardingPaymentPresenterTest { } @Test - fun `connect payment pushes the trustly flow`() = runTest { + fun `connect payment pushes the payin account overview`() = runTest { val backstack = TestBackstack().apply { entries.add(OnboardingStepKey(OnboardingStepId.ConnectPayment)) } val repository = FakeOnboardingRepository() val sessionStore = testSessionStore(repository, FakeOnboardingMemberIdProvider()) @@ -71,7 +71,7 @@ internal class OnboardingPaymentPresenterTest { sendEvent(OnboardingPaymentEvent.ConnectPayment) awaitItem() runCurrent() - assertThat(backstack.entries.last()).isEqualTo(TrustlyKey) + assertThat(backstack.entries.last()).isEqualTo(PayinAccountKey) } } From 081c03942c338167a67c77a42437c314a7de39e7 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 31 Aug 2026 16:34:11 +0200 Subject: [PATCH 39/75] check isDefault --- .../src/main/graphql/OnboardingQuery.graphql | 1 + .../feature/onboarding/data/OnboardingRepository.kt | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql b/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql index f46fcd4c0e..93171b400a 100644 --- a/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql +++ b/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql @@ -36,6 +36,7 @@ query Onboarding { paymentMethods { payinMethods { status + isDefault } } crossSellV2(input: { userFlow: ONBOARDING, flowSource: null, experiments: [] }) { diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt index 630b2c4cc5..f0f6aa980c 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt @@ -10,6 +10,7 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.logger.logcat import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject import octopus.OnboardingQuery @@ -84,13 +85,14 @@ internal class OnboardingRepositoryImpl( currencyCode = referralInformation.monthlyDiscountPerReferral.currencyCode.rawValue, ) }, - payinStatus = member.paymentMethods.payinMethods.map { it.status.rawValue }.let { statuses -> + payinStatus = member.paymentMethods.payinMethods.let { methods -> + logcat { "Mariia: methods $methods " } when { - statuses.any { it == "ACTIVE" } -> OnboardingPayinStatus.Active + methods.any { it.status.rawValue == "ACTIVE" && it.isDefault } -> OnboardingPayinStatus.Active // A PENDING method counts as "connected enough" to skip the step (bank activation takes // days), but the step UI still shows it as pending rather than claiming it is connected. - statuses.any { it == "PENDING" } -> OnboardingPayinStatus.Pending + methods.any { it.status.rawValue == "PENDING" } -> OnboardingPayinStatus.Pending else -> OnboardingPayinStatus.NeedsSetup } From be8fbd3fed396c03fa1776b1257442ae62dcee60 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 2 Sep 2026 14:51:57 +0200 Subject: [PATCH 40/75] add log --- .../feature/payin/account/data/GetPayinAccountUseCase.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index bc72885d5c..d8c603ab0e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -9,6 +9,7 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import octopus.GetPayinMethodsQuery @@ -81,7 +82,7 @@ internal class GetPayinAccountUseCase( val availablePayinMethods = paymentMethods.availableMethods .filter { it.supportsPayin } .map { it.provider } - + logcat { "availablePayinMethods: $availablePayinMethods" } PayinAccountData( currentMethods = currentMethods, availablePayinMethods = availablePayinMethods, From b822ed7921f2ba5bca19e1f867588dac7ee107b0 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 2 Sep 2026 15:42:02 +0200 Subject: [PATCH 41/75] log --- .../feature/payin/account/data/GetPayinAccountUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index d8c603ab0e..18b726f7a3 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -78,7 +78,7 @@ internal class GetPayinAccountUseCase( } } } - + logcat { "availablePayinMethods: before filter ${paymentMethods.availableMethods}" } val availablePayinMethods = paymentMethods.availableMethods .filter { it.supportsPayin } .map { it.provider } From 486873cb4df24a60c0c1e978e864942bb059a2a4 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 7 Sep 2026 14:56:42 +0200 Subject: [PATCH 42/75] change icons --- .../design/system/hedvig/icon/Autogiro.kt | 1 - .../design/system/hedvig/icon/Trustly.kt | 72 +++++ .../system/hedvig/icon/colored/Swish.kt | 257 +----------------- .../SelectPayinMethodDestination.kt | 3 +- .../setupswish/SetupSwishPayinDestination.kt | 2 +- .../SelectPayoutMethodDestination.kt | 3 +- 6 files changed, 79 insertions(+), 259 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt index c46edc448f..393da43c24 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Autogiro.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.icon.colored.Swish val HedvigIcons.Autogiro: ImageVector get() { diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt new file mode 100644 index 0000000000..d2ae2f8215 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt @@ -0,0 +1,72 @@ +package com.hedvig.android.design.system.hedvig.icon + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.icon.colored.Swish + +val HedvigIcons.Trustly: ImageVector + get() { + if (_Vector2 != null) { + return _Vector2!! + } + _Vector2 = ImageVector.Builder( + name = "Vector2", + defaultWidth = 40.dp, + defaultHeight = 32.dp, + viewportWidth = 40f, + viewportHeight = 32f + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(0f, 14.018f) + horizontalLineTo(13.992f) + verticalLineTo(2.107f) + lineTo(25.164f, 14.018f) + lineTo(13.992f, 25.91f) + verticalLineTo(32f) + horizontalLineTo(27.553f) + verticalLineTo(14.018f) + horizontalLineTo(40f) + verticalLineTo(0f) + horizontalLineTo(0f) + verticalLineTo(14.018f) + close() + } + }.build() + + return _Vector2!! + } + +@Suppress("ObjectPropertyName") +private var _Vector2: ImageVector? = null + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + imageVector = HedvigIcons.Trustly, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((24.0).dp) + .height((24.0).dp), + ) + } + } +} diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt index 3c77566c1c..4a892f0ca3 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/Swish.kt @@ -12,13 +12,11 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathFillType -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.icon.CheckFilled import com.hedvig.android.design.system.hedvig.icon.HedvigIcons val HedvigIcons.Swish: ImageVector @@ -29,261 +27,10 @@ val HedvigIcons.Swish: ImageVector _Swish = ImageVector.Builder( name = "Swish", defaultWidth = 420.dp, - defaultHeight = 566.dp, + defaultHeight = 420.dp, viewportWidth = 420f, - viewportHeight = 566f, + viewportHeight = 420f, ).apply { - path(fill = SolidColor(Color(0xFF17191F))) { - moveTo(381.4f, 485.9f) - curveToRelative(0f, -2f, 0.4f, -3.9f, 1.1f, -5.7f) - curveToRelative(0.7f, -1.8f, 1.8f, -3.3f, 3f, -4.6f) - curveToRelative(1.3f, -1.3f, 2.8f, -2.4f, 4.5f, -3.1f) - curveToRelative(1.7f, -0.8f, 3.6f, -1.1f, 5.5f, -1.1f) - reflectiveCurveToRelative(3.9f, 0.4f, 5.6f, 1.1f) - curveToRelative(1.7f, 0.8f, 3.3f, 1.8f, 4.6f, 3.1f) - reflectiveCurveToRelative(2.3f, 2.9f, 3.1f, 4.6f) - curveToRelative(0.7f, 1.8f, 1.1f, 3.7f, 1.1f, 5.7f) - reflectiveCurveToRelative(-0.4f, 3.9f, -1.1f, 5.7f) - curveToRelative(-0.7f, 1.8f, -1.8f, 3.3f, -3.1f, 4.6f) - curveToRelative(-1.3f, 1.3f, -2.8f, 2.4f, -4.6f, 3.1f) - curveToRelative(-1.7f, 0.8f, -3.6f, 1.1f, -5.6f, 1.1f) - reflectiveCurveToRelative(-3.8f, -0.4f, -5.5f, -1.1f) - curveToRelative(-1.7f, -0.8f, -3.2f, -1.8f, -4.5f, -3.1f) - curveToRelative(-1.3f, -1.3f, -2.3f, -2.9f, -3f, -4.6f) - curveToRelative(-0.7f, -1.8f, -1.1f, -3.7f, -1.1f, -5.7f) - close() - moveTo(384.3f, 485.9f) - curveToRelative(0f, 1.7f, 0.3f, 3.2f, 0.9f, 4.7f) - curveToRelative(0.6f, 1.4f, 1.4f, 2.7f, 2.4f, 3.8f) - curveToRelative(1f, 1.1f, 2.2f, 1.9f, 3.6f, 2.5f) - curveToRelative(1.4f, 0.6f, 2.9f, 0.9f, 4.5f, 0.9f) - reflectiveCurveToRelative(3.1f, -0.3f, 4.5f, -0.9f) - curveToRelative(1.4f, -0.6f, 2.6f, -1.5f, 3.6f, -2.5f) - reflectiveCurveToRelative(1.8f, -2.3f, 2.4f, -3.8f) - curveToRelative(0.6f, -1.4f, 0.9f, -3f, 0.9f, -4.7f) - reflectiveCurveToRelative(-0.3f, -3.2f, -0.9f, -4.7f) - curveToRelative(-0.6f, -1.4f, -1.4f, -2.7f, -2.4f, -3.8f) - curveToRelative(-1f, -1.1f, -2.2f, -1.9f, -3.6f, -2.5f) - curveToRelative(-1.4f, -0.6f, -2.9f, -0.9f, -4.5f, -0.9f) - reflectiveCurveToRelative(-3.1f, 0.3f, -4.5f, 0.9f) - curveToRelative(-1.4f, 0.6f, -2.6f, 1.5f, -3.6f, 2.5f) - curveToRelative(-1f, 1.1f, -1.8f, 2.3f, -2.4f, 3.8f) - curveToRelative(-0.6f, 1.4f, -0.9f, 3f, -0.9f, 4.7f) - close() - moveTo(390.7f, 479.6f) - curveToRelative(0f, -0.9f, 0.4f, -1.3f, 1.3f, -1.3f) - horizontalLineToRelative(4.5f) - curveToRelative(1.4f, 0f, 2.6f, 0.4f, 3.4f, 1.2f) - curveToRelative(0.9f, 0.8f, 1.3f, 1.9f, 1.3f, 3.4f) - reflectiveCurveToRelative(0f, 1.1f, -0.3f, 1.6f) - curveToRelative(-0.2f, 0.5f, -0.4f, 0.8f, -0.7f, 1.2f) - curveToRelative(-0.3f, 0.3f, -0.6f, 0.6f, -0.9f, 0.8f) - reflectiveCurveToRelative(-0.6f, 0.4f, -1f, 0.4f) - horizontalLineToRelative(0f) - curveToRelative(0f, 0.1f, 0f, 0.2f, 0.1f, 0.2f) - curveToRelative(0f, 0f, 0.1f, 0.1f, 0.2f, 0.3f) - curveToRelative(0f, 0.1f, 0.1f, 0.3f, 0.2f, 0.4f) - lineToRelative(2.1f, 4f) - curveToRelative(0.2f, 0.5f, 0.3f, 0.8f, 0.2f, 1.1f) - curveToRelative(-0.1f, 0.3f, -0.4f, 0.4f, -0.9f, 0.4f) - horizontalLineToRelative(-0.5f) - curveToRelative(-0.7f, 0f, -1.3f, -0.3f, -1.6f, -1f) - lineToRelative(-2.3f, -4.9f) - horizontalLineToRelative(-2.5f) - verticalLineToRelative(4.6f) - curveToRelative(0f, 0.9f, -0.4f, 1.3f, -1.2f, 1.3f) - horizontalLineToRelative(-0.4f) - curveToRelative(-0.8f, 0f, -1.2f, -0.4f, -1.2f, -1.3f) - verticalLineToRelative(-12.5f) - close() - moveTo(396f, 485.4f) - curveToRelative(0.8f, 0f, 1.4f, -0.2f, 1.8f, -0.7f) - reflectiveCurveToRelative(0.6f, -1.1f, 0.6f, -1.9f) - reflectiveCurveToRelative(-0.2f, -1.4f, -0.6f, -1.8f) - curveToRelative(-0.4f, -0.4f, -1f, -0.6f, -1.8f, -0.6f) - horizontalLineToRelative(-2.4f) - verticalLineToRelative(5f) - horizontalLineToRelative(2.4f) - close() - moveTo(279.3f, 495f) - curveToRelative(4.5f, 0f, 8.4f, 0.6f, 11.5f, 1.7f) - curveToRelative(3.2f, 1.2f, 5.6f, 2.2f, 7.4f, 3.2f) - curveToRelative(1.5f, 0.8f, 2.4f, 1.9f, 2.7f, 3.2f) - curveToRelative(0.3f, 1.3f, 0f, 2.7f, -0.7f, 4.3f) - lineToRelative(-1.3f, 2.4f) - curveToRelative(-0.8f, 1.6f, -1.8f, 2.5f, -3.1f, 2.8f) - reflectiveCurveToRelative(-2.7f, 0f, -4.4f, -0.7f) - curveToRelative(-1.5f, -0.7f, -3.3f, -1.4f, -5.5f, -2.2f) - reflectiveCurveToRelative(-4.6f, -1.1f, -7.5f, -1.1f) - reflectiveCurveToRelative(-5.2f, 0.6f, -6.8f, 1.7f) - curveToRelative(-1.6f, 1.2f, -2.4f, 2.8f, -2.4f, 4.9f) - reflectiveCurveToRelative(0.8f, 3.4f, 2.5f, 4.5f) - curveToRelative(1.6f, 1.2f, 3.7f, 2.2f, 6.3f, 3.1f) - curveToRelative(2.5f, 0.9f, 5.2f, 1.8f, 8.1f, 2.9f) - curveToRelative(2.9f, 1f, 5.6f, 2.3f, 8.1f, 3.9f) - reflectiveCurveToRelative(4.6f, 3.6f, 6.3f, 6.1f) - curveToRelative(1.6f, 2.5f, 2.5f, 5.6f, 2.5f, 9.4f) - reflectiveCurveToRelative(-0.6f, 5.8f, -1.8f, 8.3f) - reflectiveCurveToRelative(-2.9f, 4.7f, -5.2f, 6.6f) - curveToRelative(-2.3f, 1.9f, -5f, 3.3f, -8.2f, 4.4f) - curveToRelative(-3.2f, 1.1f, -6.7f, 1.6f, -10.7f, 1.6f) - reflectiveCurveToRelative(-10.1f, -0.8f, -13.9f, -2.4f) - curveToRelative(-3.8f, -1.6f, -6.7f, -3.1f, -8.7f, -4.5f) - curveToRelative(-1.5f, -0.9f, -2.4f, -2f, -2.5f, -3.3f) - curveToRelative(-0.2f, -1.3f, 0.2f, -2.7f, 1.2f, -4.3f) - lineToRelative(1.6f, -2.4f) - curveToRelative(1f, -1.4f, 2.1f, -2.2f, 3.3f, -2.4f) - reflectiveCurveToRelative(2.6f, 0.2f, 4.3f, 1.1f) - curveToRelative(1.6f, 0.9f, 3.7f, 1.9f, 6.2f, 3f) - curveToRelative(2.5f, 1.1f, 5.5f, 1.7f, 9f, 1.7f) - reflectiveCurveToRelative(5.2f, -0.6f, 6.9f, -1.9f) - reflectiveCurveToRelative(2.5f, -2.9f, 2.5f, -5.1f) - reflectiveCurveToRelative(-0.8f, -3.3f, -2.5f, -4.5f) - curveToRelative(-1.6f, -1.1f, -3.7f, -2.1f, -6.3f, -3.1f) - curveToRelative(-2.5f, -0.9f, -5.2f, -1.9f, -8.1f, -3f) - curveToRelative(-2.9f, -1.1f, -5.6f, -2.4f, -8.1f, -4f) - curveToRelative(-2.5f, -1.6f, -4.6f, -3.6f, -6.3f, -6.1f) - curveToRelative(-1.6f, -2.5f, -2.5f, -5.7f, -2.5f, -9.6f) - reflectiveCurveToRelative(0.7f, -6.2f, 2.1f, -8.8f) - curveToRelative(1.4f, -2.6f, 3.2f, -4.7f, 5.6f, -6.4f) - reflectiveCurveToRelative(5.1f, -3f, 8.3f, -3.9f) - curveToRelative(3.2f, -0.9f, 6.5f, -1.3f, 10.1f, -1.3f) - close() - moveTo(81.3f, 495f) - curveToRelative(4.5f, 0f, 8.4f, 0.6f, 11.5f, 1.7f) - reflectiveCurveToRelative(5.6f, 2.2f, 7.4f, 3.2f) - curveToRelative(1.5f, 0.8f, 2.4f, 1.9f, 2.7f, 3.2f) - curveToRelative(0.3f, 1.3f, 0f, 2.7f, -0.7f, 4.3f) - lineToRelative(-1.3f, 2.4f) - curveToRelative(-0.8f, 1.6f, -1.8f, 2.5f, -3.1f, 2.8f) - reflectiveCurveToRelative(-2.7f, 0f, -4.4f, -0.7f) - curveToRelative(-1.5f, -0.7f, -3.3f, -1.4f, -5.5f, -2.2f) - curveToRelative(-2.1f, -0.8f, -4.6f, -1.1f, -7.5f, -1.1f) - reflectiveCurveToRelative(-5.2f, 0.6f, -6.8f, 1.7f) - curveToRelative(-1.6f, 1.2f, -2.4f, 2.8f, -2.4f, 4.9f) - reflectiveCurveToRelative(0.8f, 3.4f, 2.5f, 4.5f) - curveToRelative(1.6f, 1.2f, 3.7f, 2.2f, 6.3f, 3.1f) - curveToRelative(2.5f, 0.9f, 5.2f, 1.8f, 8.1f, 2.9f) - curveToRelative(2.9f, 1f, 5.6f, 2.3f, 8.1f, 3.9f) - curveToRelative(2.5f, 1.6f, 4.6f, 3.6f, 6.3f, 6.1f) - curveToRelative(1.6f, 2.5f, 2.5f, 5.6f, 2.5f, 9.4f) - reflectiveCurveToRelative(-0.6f, 5.8f, -1.8f, 8.3f) - reflectiveCurveToRelative(-2.9f, 4.7f, -5.2f, 6.6f) - curveToRelative(-2.3f, 1.9f, -5f, 3.3f, -8.2f, 4.4f) - curveToRelative(-3.2f, 1.1f, -6.7f, 1.6f, -10.7f, 1.6f) - reflectiveCurveToRelative(-10.1f, -0.8f, -13.9f, -2.4f) - curveToRelative(-3.8f, -1.6f, -6.7f, -3.1f, -8.7f, -4.5f) - curveToRelative(-1.5f, -0.9f, -2.4f, -2f, -2.5f, -3.3f) - curveToRelative(-0.2f, -1.3f, 0.2f, -2.7f, 1.2f, -4.3f) - lineToRelative(1.6f, -2.4f) - curveToRelative(1f, -1.4f, 2.1f, -2.2f, 3.3f, -2.4f) - curveToRelative(1.2f, -0.2f, 2.6f, 0.2f, 4.3f, 1.1f) - curveToRelative(1.6f, 0.9f, 3.7f, 1.9f, 6.2f, 3f) - curveToRelative(2.5f, 1.1f, 5.5f, 1.7f, 9f, 1.7f) - reflectiveCurveToRelative(5.2f, -0.6f, 6.9f, -1.9f) - curveToRelative(1.7f, -1.2f, 2.5f, -2.9f, 2.5f, -5.1f) - reflectiveCurveToRelative(-0.8f, -3.3f, -2.5f, -4.5f) - curveToRelative(-1.6f, -1.1f, -3.7f, -2.1f, -6.3f, -3.1f) - curveToRelative(-2.5f, -0.9f, -5.2f, -1.9f, -8.1f, -3f) - curveToRelative(-2.9f, -1.1f, -5.6f, -2.4f, -8.1f, -4f) - curveToRelative(-2.5f, -1.6f, -4.6f, -3.6f, -6.3f, -6.1f) - reflectiveCurveToRelative(-2.5f, -5.7f, -2.5f, -9.6f) - reflectiveCurveToRelative(0.7f, -6.2f, 2.1f, -8.8f) - curveToRelative(1.4f, -2.6f, 3.2f, -4.7f, 5.6f, -6.4f) - reflectiveCurveToRelative(5.1f, -3f, 8.3f, -3.9f) - curveToRelative(3.2f, -0.9f, 6.5f, -1.3f, 10.1f, -1.3f) - close() - moveTo(324.1f, 470f) - curveToRelative(3.7f, 0f, 5.6f, 1.9f, 5.6f, 5.6f) - verticalLineToRelative(27.4f) - curveToRelative(0f, 0.9f, 0f, 1.7f, 0f, 2.3f) - curveToRelative(0f, 0.7f, -0.1f, 1.3f, -0.2f, 1.8f) - curveToRelative(0f, 0.6f, -0.1f, 1.2f, -0.1f, 1.6f) - horizontalLineToRelative(0.3f) - curveToRelative(0.8f, -1.6f, 1.9f, -3.2f, 3.4f, -4.9f) - curveToRelative(1.5f, -1.6f, 3.2f, -3.1f, 5.2f, -4.5f) - curveToRelative(2f, -1.3f, 4.3f, -2.4f, 6.8f, -3.2f) - curveToRelative(2.5f, -0.8f, 5.3f, -1.2f, 8.2f, -1.2f) - curveToRelative(7.5f, 0f, 13.4f, 2f, 17.5f, 6.1f) - curveToRelative(4.1f, 4.1f, 6.2f, 10.6f, 6.2f, 19.7f) - verticalLineToRelative(38f) - curveToRelative(0f, 3.7f, -1.9f, 5.6f, -5.6f, 5.6f) - horizontalLineToRelative(-5.7f) - curveToRelative(-3.7f, 0f, -5.6f, -1.9f, -5.6f, -5.6f) - verticalLineToRelative(-34.6f) - curveToRelative(0f, -4.2f, -0.7f, -7.5f, -2.1f, -10f) - reflectiveCurveToRelative(-4.3f, -3.8f, -8.5f, -3.8f) - reflectiveCurveToRelative(-5.6f, 0.6f, -8.1f, 1.7f) - curveToRelative(-2.4f, 1.2f, -4.5f, 2.7f, -6.2f, 4.7f) - curveToRelative(-1.7f, 2f, -3f, 4.4f, -3.9f, 7.1f) - curveToRelative(-0.9f, 2.7f, -1.4f, 5.7f, -1.4f, 8.9f) - verticalLineToRelative(25.9f) - curveToRelative(0f, 3.7f, -1.9f, 5.6f, -5.6f, 5.6f) - horizontalLineToRelative(-5.7f) - curveToRelative(-3.7f, 0f, -5.6f, -1.9f, -5.6f, -5.6f) - verticalLineToRelative(-83.2f) - curveToRelative(0f, -3.7f, 1.9f, -5.6f, 5.6f, -5.6f) - horizontalLineToRelative(5.7f) - close() - moveTo(235.7f, 496.6f) - curveToRelative(3.6f, 0f, 5.5f, 1.9f, 5.5f, 5.6f) - verticalLineToRelative(56.6f) - curveToRelative(0f, 3.7f, -1.8f, 5.6f, -5.5f, 5.6f) - horizontalLineToRelative(-5.9f) - curveToRelative(-3.6f, 0f, -5.5f, -1.9f, -5.5f, -5.6f) - verticalLineToRelative(-56.6f) - curveToRelative(0f, -3.7f, 1.8f, -5.6f, 5.5f, -5.6f) - horizontalLineToRelative(5.9f) - close() - moveTo(122.7f, 496.6f) - curveToRelative(3.4f, 0f, 5.4f, 1.6f, 6f, 4.9f) - lineToRelative(10.9f, 39.6f) - curveToRelative(0.2f, 1f, 0.3f, 1.9f, 0.5f, 2.7f) - curveToRelative(0.1f, 0.8f, 0.3f, 1.6f, 0.5f, 2.3f) - curveToRelative(0.2f, 0.8f, 0.3f, 1.6f, 0.4f, 2.3f) - horizontalLineToRelative(0.3f) - curveToRelative(0f, -0.7f, 0.2f, -1.5f, 0.4f, -2.3f) - curveToRelative(0.2f, -0.7f, 0.3f, -1.5f, 0.5f, -2.3f) - curveToRelative(0.1f, -0.8f, 0.3f, -1.7f, 0.6f, -2.7f) - lineToRelative(11.5f, -39.6f) - curveToRelative(0.6f, -3.2f, 2.7f, -4.8f, 6.1f, -4.8f) - horizontalLineToRelative(5.1f) - curveToRelative(3.3f, 0f, 5.3f, 1.6f, 6.1f, 4.8f) - lineToRelative(11.3f, 39.6f) - curveToRelative(0.3f, 1f, 0.5f, 1.9f, 0.6f, 2.7f) - curveToRelative(0.1f, 0.8f, 0.3f, 1.6f, 0.5f, 2.3f) - curveToRelative(0.2f, 0.8f, 0.3f, 1.6f, 0.4f, 2.3f) - horizontalLineToRelative(0.3f) - curveToRelative(0f, -0.7f, 0.2f, -1.5f, 0.4f, -2.3f) - curveToRelative(0.2f, -0.7f, 0.3f, -1.5f, 0.5f, -2.3f) - curveToRelative(0.1f, -0.8f, 0.3f, -1.7f, 0.6f, -2.7f) - lineToRelative(10.8f, -39.6f) - curveToRelative(0.8f, -3.3f, 2.8f, -4.9f, 6.1f, -4.9f) - horizontalLineToRelative(6.1f) - curveToRelative(2f, 0f, 3.5f, 0.6f, 4.3f, 1.7f) - curveToRelative(0.8f, 1.2f, 0.9f, 2.7f, 0.4f, 4.5f) - lineToRelative(-17.4f, 56.9f) - curveToRelative(-0.9f, 3.1f, -3f, 4.7f, -6.3f, 4.7f) - horizontalLineToRelative(-8.9f) - curveToRelative(-3.4f, 0f, -5.5f, -1.6f, -6.3f, -4.8f) - lineToRelative(-10.3f, -33.9f) - curveToRelative(-0.3f, -0.9f, -0.5f, -1.8f, -0.7f, -2.7f) - reflectiveCurveToRelative(-0.4f, -1.7f, -0.5f, -2.4f) - curveToRelative(-0.2f, -0.8f, -0.3f, -1.6f, -0.4f, -2.3f) - horizontalLineToRelative(-0.3f) - curveToRelative(-0.2f, 0.7f, -0.4f, 1.5f, -0.5f, 2.3f) - curveToRelative(-0.2f, 0.7f, -0.4f, 1.5f, -0.5f, 2.4f) - curveToRelative(-0.2f, 0.9f, -0.4f, 1.8f, -0.7f, 2.7f) - lineToRelative(-10.3f, 33.9f) - curveToRelative(-0.8f, 3.2f, -2.8f, 4.8f, -6.1f, 4.8f) - horizontalLineToRelative(-9.2f) - curveToRelative(-3.2f, 0f, -5.2f, -1.6f, -6.1f, -4.7f) - lineToRelative(-17.6f, -56.9f) - curveToRelative(-0.5f, -1.9f, -0.4f, -3.4f, 0.5f, -4.5f) - curveToRelative(0.8f, -1.2f, 2.2f, -1.7f, 4.2f, -1.7f) - horizontalLineToRelative(6.4f) - close() - } path( fill = Brush.linearGradient( colorStops = arrayOf( diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index fcc0d2fc62..5a6a1df49d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -25,6 +25,7 @@ import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.icon.Autogiro import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import hedvig.resources.PAYMENTS_INVOICE @@ -100,7 +101,7 @@ private fun PayinMethodRow( when (provider) { MemberPaymentProvider.TRUSTLY -> { Icon( - HedvigIcons.Autogiro, + HedvigIcons.Trustly, null, // todo modifier = Modifier.size(32.dp), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index c4d276553f..e5d8efcd7a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -127,7 +127,7 @@ private fun SetupSwishPayoutScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { EmptyState( - text = "Please give your approval in the Swish app", + text = "Please give your approval in the Swish app", // todo description = null, modifier = Modifier.fillMaxWidth(), iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SWISH, diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index acf6566103..1e590569aa 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -27,6 +27,7 @@ import com.hedvig.android.design.system.hedvig.icon.BankAccount import com.hedvig.android.design.system.hedvig.icon.Card import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Link +import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Swish import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE @@ -110,7 +111,7 @@ private fun PayoutMethodRow( when (provider) { MemberPaymentProvider.TRUSTLY -> { Icon( - HedvigIcons.Link, + HedvigIcons.Trustly, null, // todo modifier = Modifier.size(32.dp), ) From ac0aec6c7250ea34e07f71b8192255f7bfe5adfa Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 7 Sep 2026 16:44:25 +0200 Subject: [PATCH 43/75] add new copy --- .../androidMain/res/values-sv-rSE/strings.xml | 26 +++++++++++-- .../src/androidMain/res/values/strings.xml | 26 +++++++++++-- .../values-sv-rSE/strings.xml | 26 +++++++++++-- .../composeResources/values/strings.xml | 26 +++++++++++-- .../PayinAccountOverviewDestination.kt | 37 +++++++++---------- .../SelectPayinMethodDestination.kt | 23 ++++++------ 6 files changed, 117 insertions(+), 47 deletions(-) diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 30d5b508b0..bfd63085bc 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -152,7 +152,7 @@ Ange byggår Byggår Du + %1$d - Det nya beloppet börjar gälla när din kvalificeringstid är slut. Fram till dess gäller ditt nuvarande belopp. + Hämtar försäkringsbelopp... Ändra utbetalningskonto Ändra skyddsnivå Chatta med en specialist @@ -512,11 +512,14 @@ Hedvig Läs mer Upptäck våra försäkringar - Se pris Hur kan vi hjälpa dig? Hej %1$s - Ändra adress + Jag ska flytta Ändra försäkring + Bjud in en vän + Reseintyg + Kommande betalning + Ändra mitt skydd Dina prisförslag Lägg till medförsäkrad Lägg till medägare @@ -662,7 +665,6 @@ Kunde inte spara, försök igen Så vi kan nå dig om det behövs Telefonnummer - Se pris Alltid mörkt läge Alltid ljust läge Välj hur appen ska se ut @@ -724,7 +726,23 @@ Delsumma Kommande betalning Giltig till %1$s + Lägg till betalningsmetod + Välj primär metod + Lägg till en betalningsmetod för att hålla din försäkring aktiv + Koppla betalning Betalningshistorik + Betalningsmetoder + Betalningsmetod + Kopplat + Direktbetalning via faktura + Betalningar och utbetalningar via Swish + Anslut din bank via Trustly + Bekräfta primär betalningsmetod + Primär + Ändra ditt föredragna betalsätt + Primär betalningsmetod + Är du säker på att du vill ta bort betalningsmetoden? + Ta bort betalningsmetod Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index 8da17f8faf..6cee9c8d0a 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -152,7 +152,7 @@ Please enter year of construction Year of construction You + %1$d - Your new amount takes effect once your qualification period ends. Until then, your current amount applies. + Fetching insurance amount... Change payout account Change coverage level Talk to a human @@ -512,11 +512,14 @@ Hedvig Read more Discover our insurances - See price How can we help? Hi %1$s - Change address + I\'m moving Edit insurance + Invite a friend + Travel certificate + Upcoming payment + Change my coverage Your quotes Add co-insured Add co-owner @@ -662,7 +665,6 @@ Could not save, please try again So we can reach you if we need to Phone number - See price Always dark Always light Choose how the app looks @@ -724,7 +726,23 @@ Subtotal Upcoming payment Valid until %1$s + Add payment method + Choose primary method + Add a payment method to keep your insurance active + Connect payment Payment history + Payment methods + Payment method + Connected + Direct payment via Invoice + Payments and payouts via Swish + Connect your bank via Trustly + Confirm primary payment method + Primary + Change your preferred way of payment + Primary payment method + Are you sure you want to remove payment method? + Remove payment method Add payout method Direct payment via Invoice Instant payout with Swish diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 1e29d4dd52..e9d658d8c3 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -151,7 +151,7 @@ Ange byggår Byggår Du + %1$d - Det nya beloppet börjar gälla när din kvalificeringstid är slut. Fram till dess gäller ditt nuvarande belopp. + Hämtar försäkringsbelopp... Ändra utbetalningskonto Ändra skyddsnivå Chatta med en specialist @@ -511,11 +511,14 @@ Hedvig Läs mer Upptäck våra försäkringar - Se pris Hur kan vi hjälpa dig? Hej %1$s - Ändra adress + Jag ska flytta Ändra försäkring + Bjud in en vän + Reseintyg + Kommande betalning + Ändra mitt skydd Dina prisförslag Lägg till medförsäkrad Lägg till medägare @@ -661,7 +664,6 @@ Kunde inte spara, försök igen Så vi kan nå dig om det behövs Telefonnummer - Se pris Alltid mörkt läge Alltid ljust läge Välj hur appen ska se ut @@ -723,7 +725,23 @@ Delsumma Kommande betalning Giltig till %1$s + Lägg till betalningsmetod + Välj primär metod + Lägg till en betalningsmetod för att hålla din försäkring aktiv + Koppla betalning Betalningshistorik + Betalningsmetoder + Betalningsmetod + Kopplat + Direktbetalning via faktura + Betalningar och utbetalningar via Swish + Anslut din bank via Trustly + Bekräfta primär betalningsmetod + Primär + Ändra ditt föredragna betalsätt + Primär betalningsmetod + Är du säker på att du vill ta bort betalningsmetoden? + Ta bort betalningsmetod Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index c798247652..e53ecae56e 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -151,7 +151,7 @@ Please enter year of construction Year of construction You + %1$d - Your new amount takes effect once your qualification period ends. Until then, your current amount applies. + Fetching insurance amount... Change payout account Change coverage level Talk to a human @@ -511,11 +511,14 @@ Hedvig Read more Discover our insurances - See price How can we help? Hi %1$s - Change address + I'm moving Edit insurance + Invite a friend + Travel certificate + Upcoming payment + Change my coverage Your quotes Add co-insured Add co-owner @@ -661,7 +664,6 @@ Could not save, please try again So we can reach you if we need to Phone number - See price Always dark Always light Choose how the app looks @@ -723,7 +725,23 @@ Subtotal Upcoming payment Valid until %1$s + Add payment method + Choose primary method + Add a payment method to keep your insurance active + Connect payment Payment history + Payment methods + Payment method + Connected + Direct payment via Invoice + Payments and payouts via Swish + Connect your bank via Trustly + Confirm primary payment method + Primary + Change your preferred way of payment + Primary payment method + Are you sure you want to remove payment method? + Remove payment method Add payout method Direct payment via Invoice Instant payout with Swish diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 91b5eb42e5..cd8fd9306a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -49,6 +49,8 @@ import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.toDeliveryString import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYMENT_ADD_METHOD_BUTTON +import hedvig.resources.PAYMENT_METHODS_TITLE import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res import hedvig.resources.something_went_wrong @@ -84,7 +86,7 @@ private fun PayinAccountOverviewScreen( setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, ) { HedvigScaffold( - topAppBarText = "Payment account", // todo! + topAppBarText = stringResource(Res.string.PAYMENT_METHODS_TITLE), navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -133,8 +135,10 @@ private fun PayoutAccountContent( setDefaultProviderError: ErrorMessage?, modifier: Modifier = Modifier, ) { - Column(modifier - .verticalScroll(rememberScrollState())) { + Column( + modifier + .verticalScroll(rememberScrollState()), + ) { Spacer(Modifier.height(8.dp)) if (currentMethods.isEmpty()) { if (availablePayinMethods.isNotEmpty()) { @@ -149,11 +153,6 @@ private fun PayoutAccountContent( ) } } else { - HedvigText( - "Connected payment methods", // todo - modifier = Modifier.padding(horizontal = 16.dp), - ) - Spacer(Modifier.height(16.dp)) Column( verticalArrangement = Arrangement.spacedBy(8.dp), ) { @@ -228,20 +227,20 @@ private fun PayoutAccountContent( } } Column { - if (currentMethods.any { it.isPending }) { - Spacer(Modifier.height(16.dp)) - HedvigNotificationCard( - message = "You have just added or changed a billing method, it will appear here soon.", // todo - priority = NotificationPriority.Info, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - } +// if (currentMethods.any { it.isPending }) { +// Spacer(Modifier.height(16.dp)) +// HedvigNotificationCard( +// message = "You have just added or changed a billing method, it will appear here soon.", // todo +// priority = NotificationPriority.Info, +// modifier = Modifier +// .fillMaxWidth() +// .padding(horizontal = 16.dp), +// ) +// } if (availablePayinMethods.isNotEmpty()) { Spacer(Modifier.height(16.dp)) HedvigButton( - text = "Add a payment method", // todo! + text = stringResource(Res.string.PAYMENT_ADD_METHOD_BUTTON), onClick = onConnectPayinMethodClicked, enabled = true, modifier = Modifier diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 5a6a1df49d..02bde380b0 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -28,8 +28,12 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL import hedvig.resources.PAYMENTS_INVOICE -import hedvig.resources.PAYOUT_METHOD_INVOICE_DESCRIPTION +import hedvig.resources.PAYMENT_CONNECT_TITLE +import hedvig.resources.PAYMENT_OPTION_INVOICE_SUBTITLE +import hedvig.resources.PAYMENT_OPTION_SWISH_SUBTITLE +import hedvig.resources.PAYMENT_OPTION_TRUSTLY_SUBTITLE import hedvig.resources.Res import hedvig.resources.swish import octopus.type.MemberPaymentProvider @@ -44,7 +48,7 @@ internal fun SelectPayinMethodDestination( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = "Add or change payment method", // todo + topAppBarText = stringResource(Res.string.PAYMENT_CONNECT_TITLE), navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -130,9 +134,9 @@ private fun PayinMethodRow( Column(Modifier.padding(vertical = 12.dp)) { HedvigText( text = when (provider) { - MemberPaymentProvider.TRUSTLY -> "Direct debit" + MemberPaymentProvider.TRUSTLY -> stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL) - // todo + // todo: check MemberPaymentProvider.SWISH -> stringResource(Res.string.swish) MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENTS_INVOICE) @@ -142,14 +146,9 @@ private fun PayinMethodRow( ) HedvigText( text = when (provider) { - MemberPaymentProvider.TRUSTLY -> "Connect your bank via Trustly" - - // todo - MemberPaymentProvider.SWISH -> "Monthly auto-payments via Swish" - - // todo - MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION) - + MemberPaymentProvider.TRUSTLY -> stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE) + MemberPaymentProvider.SWISH -> stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE) + MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE) else -> "" }, color = HedvigTheme.colorScheme.textSecondary, From a13d48c2a6f97531f92efa31a946a270187c74df Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 8 Sep 2026 16:48:43 +0200 Subject: [PATCH 44/75] changes to selection screen --- .../design/system/hedvig/icon/Trustly.kt | 63 +++--- .../account/navigation/PayinAccountEntries.kt | 10 +- .../SelectPayinMethodDestination.kt | 214 +++++++++--------- .../SelectPayinMethodViewModel.kt | 54 +++++ 4 files changed, 200 insertions(+), 141 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt index d2ae2f8215..c65d7e7e7f 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/Trustly.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -15,42 +16,42 @@ import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.design.system.hedvig.LocalContentColor +/** + * A single monochrome path filled with the content color, so the mark stays legible in both themes + * even where it is drawn without a tint. + */ val HedvigIcons.Trustly: ImageVector + @Composable get() { - if (_Vector2 != null) { - return _Vector2!! - } - _Vector2 = ImageVector.Builder( - name = "Vector2", - defaultWidth = 40.dp, - defaultHeight = 32.dp, - viewportWidth = 40f, - viewportHeight = 32f - ).apply { - path(fill = SolidColor(Color.Black)) { - moveTo(0f, 14.018f) - horizontalLineTo(13.992f) - verticalLineTo(2.107f) - lineTo(25.164f, 14.018f) - lineTo(13.992f, 25.91f) - verticalLineTo(32f) - horizontalLineTo(27.553f) - verticalLineTo(14.018f) - horizontalLineTo(40f) - verticalLineTo(0f) - horizontalLineTo(0f) - verticalLineTo(14.018f) - close() - } - }.build() - - return _Vector2!! + val color = LocalContentColor.current + return remember(color) { trustlyImageVector(color) } } -@Suppress("ObjectPropertyName") -private var _Vector2: ImageVector? = null +private fun trustlyImageVector(color: Color): ImageVector = ImageVector.Builder( + name = "Trustly", + defaultWidth = 40.dp, + defaultHeight = 32.dp, + viewportWidth = 40f, + viewportHeight = 32f, +).apply { + path(fill = SolidColor(color)) { + moveTo(0f, 14.018f) + horizontalLineTo(13.992f) + verticalLineTo(2.107f) + lineTo(25.164f, 14.018f) + lineTo(13.992f, 25.91f) + verticalLineTo(32f) + horizontalLineTo(27.553f) + verticalLineTo(14.018f) + horizontalLineTo(40f) + verticalLineTo(0f) + horizontalLineTo(0f) + verticalLineTo(14.018f) + close() + } +}.build() @Preview @Composable diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 5e91522973..83eb70e2c9 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -7,6 +7,8 @@ import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverview import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodDestination +import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodViewModel +import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodViewModelFactory import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinDestination @@ -15,8 +17,8 @@ import com.hedvig.android.navigation.common.HedvigNavKey import com.hedvig.android.navigation.compose.Backstack import com.hedvig.android.navigation.compose.add import com.hedvig.android.navigation.compose.popUpTo +import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel import dev.zacsweers.metrox.viewmodel.metroViewModel -import octopus.type.MemberPaymentProvider fun EntryProviderScope.payinAccountEntries( backstack: Backstack, @@ -41,8 +43,12 @@ fun EntryProviderScope.payinAccountEntries( } entry { key -> + val viewModel: SelectPayinMethodViewModel = + assistedMetroViewModel { + create(key.availableProviders) + } SelectPayinMethodDestination( - availableProviders = key.availableProviders.map { MemberPaymentProvider.safeValueOf(it) }, + viewModel = viewModel, onTrustlySelected = dropUnlessResumed { backstack.popUpTo(inclusive = true) navigateToConnectPayment() diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt index 02bde380b0..c6dc1b610a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -1,86 +1,57 @@ package com.hedvig.android.feature.payin.account.ui.selectmethod -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.hedvig.android.design.system.hedvig.HedvigCard -import com.hedvig.android.design.system.hedvig.HedvigPreview +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview -import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.IconResource +import com.hedvig.android.design.system.hedvig.RadioGroup +import com.hedvig.android.design.system.hedvig.RadioOption +import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.icon.Autogiro +import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYMENT_CONNECT_SUBTITLE import hedvig.resources.PAYMENT_CONNECT_TITLE import hedvig.resources.PAYMENT_OPTION_INVOICE_SUBTITLE import hedvig.resources.PAYMENT_OPTION_SWISH_SUBTITLE import hedvig.resources.PAYMENT_OPTION_TRUSTLY_SUBTITLE import hedvig.resources.Res +import hedvig.resources.general_cancel_button import hedvig.resources.swish import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @Composable internal fun SelectPayinMethodDestination( - availableProviders: List, + viewModel: SelectPayinMethodViewModel, onTrustlySelected: () -> Unit, onSwishSelected: () -> Unit, onInvoiceSelected: () -> Unit, navigateUp: () -> Unit, ) { - HedvigScaffold( - topAppBarText = stringResource(Res.string.PAYMENT_CONNECT_TITLE), - navigateUp = navigateUp, - modifier = Modifier.fillMaxSize(), - ) { - Spacer(Modifier.height(8.dp)) - Column( - Modifier.padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - for (provider in availableProviders) { - PayinMethodRow( - provider = provider, - onTrustlySelected = onTrustlySelected, - onSwishSelected = onSwishSelected, - onInvoiceSelected = onInvoiceSelected, - ) - } - } - Spacer(Modifier.height(16.dp)) - } -} - -@Composable -private fun PayinMethodRow( - provider: MemberPaymentProvider, - onTrustlySelected: () -> Unit, - onSwishSelected: () -> Unit, - onInvoiceSelected: () -> Unit, - modifier: Modifier = Modifier, -) { - HedvigCard( - onClick = { - when (provider) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + SelectPayinMethodScreen( + uiState = uiState, + onProviderSelected = { viewModel.emit(SelectPayinMethodEvent.SelectProvider(it)) }, + onSubmitSelected = { + when (uiState.selectedProvider) { MemberPaymentProvider.TRUSTLY -> { onTrustlySelected() } @@ -96,65 +67,90 @@ private fun PayinMethodRow( else -> {} } }, - modifier = modifier.fillMaxWidth(), + navigateUp = navigateUp, + ) +} + +@Composable +private fun SelectPayinMethodScreen( + uiState: SelectPayinMethodUiState, + onProviderSelected: (MemberPaymentProvider) -> Unit, + onSubmitSelected: () -> Unit, + navigateUp: () -> Unit, +) { + HedvigScaffold( + topAppBarText = null, + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), ) { - Row( + Spacer(Modifier.height(8.dp)) + FlowHeading( + title = stringResource(Res.string.PAYMENT_CONNECT_TITLE), + description = stringResource(Res.string.PAYMENT_CONNECT_SUBTITLE), + baseStyle = HedvigTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - Icon( - HedvigIcons.Trustly, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - MemberPaymentProvider.SWISH -> { - Image( - HedvigIcons.Swish, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - MemberPaymentProvider.INVOICE -> { - Image( - HedvigIcons.Kivra, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - else -> {} - } + ) + Spacer(Modifier.weight(1f)) + Spacer(Modifier.height(8.dp)) + RadioGroup( + options = uiState.availableProviders.mapNotNull { it.toRadioOption() }, + selectedOption = uiState.selectedProvider?.let { RadioOptionId(it.rawValue) }, + onRadioOptionSelected = { onProviderSelected(MemberPaymentProvider.safeValueOf(it.id)) }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + HedvigButton( + onClick = onSubmitSelected, + enabled = uiState.selectedProvider != null, + text = stringResource(Res.string.PAYMENT_CONNECT_TITLE), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + onClick = navigateUp, + enabled = true, + text = stringResource(Res.string.general_cancel_button), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + } +} - Spacer(Modifier.width(16.dp)) - Column(Modifier.padding(vertical = 12.dp)) { - HedvigText( - text = when (provider) { - MemberPaymentProvider.TRUSTLY -> stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL) +/** + * Null for providers this screen has no option for, so a value the backend adds later is left out + * of the group. + */ +@Composable +private fun MemberPaymentProvider.toRadioOption(): RadioOption? { + val id = RadioOptionId(rawValue) + return when (this) { + MemberPaymentProvider.TRUSTLY -> RadioOption( + id = id, + text = stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL), + label = stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE), + iconResource = IconResource.Vector(HedvigIcons.Trustly), + ) - // todo: check - MemberPaymentProvider.SWISH -> stringResource(Res.string.swish) + MemberPaymentProvider.SWISH -> RadioOption( + id = id, + // todo: check + text = stringResource(Res.string.swish), + label = stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE), + iconResource = IconResource.Vector(HedvigIcons.Swish), + ) - MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENTS_INVOICE) + MemberPaymentProvider.INVOICE -> RadioOption( + id = id, + text = stringResource(Res.string.PAYMENTS_INVOICE), + label = stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE), + iconResource = IconResource.Vector(HedvigIcons.Kivra), + ) - else -> "" - }, - ) - HedvigText( - text = when (provider) { - MemberPaymentProvider.TRUSTLY -> stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE) - MemberPaymentProvider.SWISH -> stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE) - MemberPaymentProvider.INVOICE -> stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE) - else -> "" - }, - color = HedvigTheme.colorScheme.textSecondary, - ) - } - } + else -> null } } @@ -163,15 +159,17 @@ private fun PayinMethodRow( private fun PreviewSelectPayinMethodScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - SelectPayinMethodDestination( - availableProviders = listOf( - MemberPaymentProvider.SWISH, - MemberPaymentProvider.INVOICE, - MemberPaymentProvider.TRUSTLY, + SelectPayinMethodScreen( + uiState = SelectPayinMethodUiState( + availableProviders = listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.INVOICE, + MemberPaymentProvider.TRUSTLY, + ), + selectedProvider = MemberPaymentProvider.TRUSTLY, ), - onTrustlySelected = {}, - onSwishSelected = {}, - onInvoiceSelected = {}, + onProviderSelected = {}, + onSubmitSelected = {}, navigateUp = {}, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt new file mode 100644 index 0000000000..3955578224 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt @@ -0,0 +1,54 @@ +package com.hedvig.android.feature.payin.account.ui.selectmethod + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.di.ActivityRetainedScope +import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import octopus.type.MemberPaymentProvider + +@AssistedInject +@HedvigViewModel(ActivityRetainedScope::class) +internal class SelectPayinMethodViewModel( + @Assisted availableProviders: List, +) : MoleculeViewModel( + initialState = SelectPayinMethodUiState( + availableProviders = availableProviders.map(MemberPaymentProvider::safeValueOf), + selectedProvider = null, + ), + presenter = SelectPayinMethodPresenter(), + ) + +internal sealed interface SelectPayinMethodEvent { + data class SelectProvider(val provider: MemberPaymentProvider) : SelectPayinMethodEvent +} + +internal data class SelectPayinMethodUiState( + val availableProviders: List, + val selectedProvider: MemberPaymentProvider?, +) + +internal class SelectPayinMethodPresenter : + MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: SelectPayinMethodUiState, + ): SelectPayinMethodUiState { + var selectedProvider by remember { mutableStateOf(lastState.selectedProvider) } + + CollectEvents { event -> + when (event) { + is SelectPayinMethodEvent.SelectProvider -> selectedProvider = event.provider + } + } + + return lastState.copy(selectedProvider = selectedProvider) + } +} From 5b66255e372ac66cafa3e0df1592c5cd955e8bc0 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 8 Sep 2026 20:08:02 +0200 Subject: [PATCH 45/75] changes to payment methods --- .../feature-payin-account/build.gradle.kts | 3 + .../account/data/GetPayinAccountUseCase.kt | 43 ++- .../navigation/PayinAccountDestination.kt | 6 + .../account/navigation/PayinAccountEntries.kt | 19 ++ .../account/ui/components/PayinMethodRow.kt | 154 +++++++++ .../PayinAccountOverviewDestination.kt | 282 ++++------------ .../overview/PayinAccountOverviewViewModel.kt | 42 +-- .../SelectPrimaryPayinMethodDestination.kt | 304 ++++++++++++++++++ .../SelectPrimaryPayinMethodViewModel.kt | 99 ++++++ .../setupswish/SetupSwishPayinDestination.kt | 2 +- ...tPrimaryPayinMethodKeySerializationTest.kt | 39 +++ 11 files changed, 715 insertions(+), 278 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt create mode 100644 app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts index e4ca94d73d..ebac7f62fa 100644 --- a/app/feature/feature-payin-account/build.gradle.kts +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -31,4 +31,7 @@ dependencies { implementation(projects.navigationCompose) implementation(projects.navigationCore) implementation(libs.zXing) + testImplementation(libs.assertK) + testImplementation(libs.kotlinx.serialization.json) + testImplementation(libs.junit) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 18b726f7a3..ea9bfab06f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -12,6 +12,7 @@ import com.hedvig.android.core.common.di.AppScope import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn +import kotlinx.serialization.Serializable import octopus.GetPayinMethodsQuery import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodBankAccountDetails import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodInvoiceDetails @@ -66,7 +67,7 @@ internal class GetPayinAccountUseCase( MemberPaymentProvider.INVOICE -> { val invoiceDetails = method.details?.asPaymentMethodInvoiceDetails() PayinAccount.Invoice( - delivery = invoiceDetails?.delivery, + delivery = invoiceDetails?.delivery.toInvoiceDelivery(), email = invoiceDetails?.email, isPending = isPending, isDefault = isDefault, @@ -111,10 +112,13 @@ private fun parseBankAccountDetails( ) } +/** Serializable so that a chosen set of methods can be carried in a nav key across process death. */ +@Serializable internal sealed interface PayinAccount { val isPending: Boolean val isDefault: Boolean + @Serializable data class Trustly( val clearingNumber: String?, val accountNumber: String?, @@ -123,30 +127,51 @@ internal sealed interface PayinAccount { override val isDefault: Boolean, ) : PayinAccount + @Serializable data class SwishPayin( val phoneNumber: String?, override val isPending: Boolean, override val isDefault: Boolean, ) : PayinAccount + @Serializable data class Invoice( - val delivery: PaymentMethodInvoiceDelivery?, + val delivery: InvoiceDelivery?, val email: String?, override val isPending: Boolean, override val isDefault: Boolean, ) : PayinAccount } -fun PaymentMethodInvoiceDelivery?.toDeliveryString(): String? { +internal val PayinAccount.provider: MemberPaymentProvider + get() = when (this) { + is PayinAccount.Trustly -> MemberPaymentProvider.TRUSTLY + is PayinAccount.SwishPayin -> MemberPaymentProvider.SWISH + is PayinAccount.Invoice -> MemberPaymentProvider.INVOICE + } + +@Serializable +internal enum class InvoiceDelivery { + Kivra, + Mail, +} + +private fun PaymentMethodInvoiceDelivery?.toInvoiceDelivery(): InvoiceDelivery? { return when (this) { - PaymentMethodInvoiceDelivery.KIVRA -> "Kivra" + PaymentMethodInvoiceDelivery.KIVRA -> InvoiceDelivery.Kivra + PaymentMethodInvoiceDelivery.MAIL -> InvoiceDelivery.Mail + else -> null + } +} - // todo - PaymentMethodInvoiceDelivery.MAIL -> "Email" +internal fun InvoiceDelivery?.toDeliveryString(): String? { + return when (this) { + // TODO: Add "Kivra" / "Kivra" to Lokalise + InvoiceDelivery.Kivra -> "Kivra" - // todo - PaymentMethodInvoiceDelivery.UNKNOWN__ -> "" + // TODO: Add "Email" / "E-post" to Lokalise + InvoiceDelivery.Mail -> "Email" - else -> null + null -> null } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 50956dfa1f..64b6ebc78d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -1,5 +1,6 @@ package com.hedvig.android.feature.payin.account.navigation +import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.navigation.common.HedvigNavKey import kotlinx.serialization.Serializable @@ -13,3 +14,8 @@ internal data object SetupSwishPayinKey : HedvigNavKey @Serializable internal data object SetupInvoicePayinKey : HedvigNavKey + +@Serializable +internal data class SelectPrimaryPayinMethodKey( + val currentMethods: List, +) : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 83eb70e2c9..fad675609a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -6,6 +6,9 @@ import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewDestination import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel +import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodDestination +import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodViewModel +import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodViewModelFactory import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodDestination import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodViewModel import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodViewModelFactory @@ -38,7 +41,23 @@ fun EntryProviderScope.payinAccountEntries( ), ) }, + onChoosePrimaryMethodClicked = dropUnlessResumed { + val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content + backstack.add(SelectPrimaryPayinMethodKey(currentMethods = content?.currentMethods ?: emptyList())) + }, + navigateUp = backstack::navigateUp, + ) + } + + entry { key -> + val viewModel: SelectPrimaryPayinMethodViewModel = + assistedMetroViewModel { + create(key.currentMethods) + } + SelectPrimaryPayinMethodDestination( + viewModel = viewModel, navigateUp = backstack::navigateUp, + navigateBack = backstack::popBackstack, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt new file mode 100644 index 0000000000..200a44a436 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -0,0 +1,154 @@ +package com.hedvig.android.feature.payin.account.ui.components + +import androidx.compose.foundation.Image +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.Spacer +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.toDeliveryString +import hedvig.resources.PAYMENTS_BANK_LABEL +import hedvig.resources.PAYMENT_PRIMARY_LABEL +import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL +import hedvig.resources.Res +import hedvig.resources.swish +import org.jetbrains.compose.resources.stringResource + +/** + * The method's brand mark on the tile it is drawn on: white behind the full-colour marks, black + * behind Trustly's monochrome one, which picks up the tile's content colour. + */ +@Composable +private fun PayinMethodPillow(method: PayinAccount, modifier: Modifier = Modifier) { + val onDarkTile = method is PayinAccount.Trustly + Surface( + shape = HedvigTheme.shapes.cornerSmall, + color = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + modifier = modifier.size(40.dp), + ) { + Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { + when (method) { + is PayinAccount.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(28.dp)) + is PayinAccount.SwishPayin -> Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(28.dp)) + is PayinAccount.Invoice -> Image(HedvigIcons.Kivra, EmptyContentDescription, Modifier.size(28.dp)) + } + } + } +} + +@Composable +internal fun payinMethodTitle(method: PayinAccount): String = when (method) { + is PayinAccount.Trustly -> stringResource(Res.string.PAYMENTS_BANK_LABEL) + is PayinAccount.SwishPayin -> stringResource(Res.string.swish) + is PayinAccount.Invoice -> method.delivery.toDeliveryString().orEmpty() +} + +@Composable +internal fun payinMethodSubtitle(method: PayinAccount): String? { + val pendingLabel = stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + return when (method) { + is PayinAccount.Trustly -> { + method.maskedAccount() ?: pendingLabel.takeIf { method.isPending } + } + + is PayinAccount.SwishPayin -> { + val phoneNumber = method.phoneNumber + if (phoneNumber.isNullOrBlank()) pendingLabel.takeIf { method.isPending } else formatSwishPhoneNumber(phoneNumber) + } + + is PayinAccount.Invoice -> { + null + } + } +} + +/** + * The trailing edge of a method row. Sized so a row without one keeps the same height as its + * neighbours. + */ +@Composable +internal fun PayinMethodRow( + method: PayinAccount, + modifier: Modifier = Modifier, + endSlot: @Composable (() -> Unit)? = null, +) { + Row( + modifier = modifier + .heightIn(min = 64.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + PayinMethodPillow(method) + Spacer(Modifier.width(2.dp)) + Column( + Modifier + .weight(1f) + .padding(vertical = 8.dp), + ) { + HedvigText(text = payinMethodTitle(method)) + val subtitle = payinMethodSubtitle(method) + if (subtitle != null) { + HedvigText( + text = subtitle, + style = HedvigTheme.typography.finePrint, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + } + endSlot?.invoke() + } +} + +/** Static counterpart to the button the design draws it with: the primary method is not a choice made here. */ +@Composable +internal fun PrimaryMethodLabel(modifier: Modifier = Modifier) { + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = HedvigTheme.colorScheme.buttonSecondaryAltResting, + modifier = modifier, + ) { + HedvigText( + text = stringResource(Res.string.PAYMENT_PRIMARY_LABEL), + style = HedvigTheme.typography.label, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 7.dp), + ) + } +} + +private fun PayinAccount.Trustly.maskedAccount(): String? { + val bank = bankName ?: return null + val lastFour = accountNumber?.takeLast(4)?.takeIf { it.isNotBlank() } ?: return bank + return "$bank ···· $lastFour" +} + +internal fun formatSwishPhoneNumber(phoneNumber: String): String { + val digits = phoneNumber.take(15) + val sb = StringBuilder() + for (i in digits.indices) { + sb.append(digits[i]) + if (i in setOf(2, 5, 7)) { + sb.append("-") + } + } + return sb.toString() +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index cd8fd9306a..509681989e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -1,16 +1,13 @@ package com.hedvig.android.feature.payin.account.ui.overview -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -22,58 +19,47 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.EmptyState import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.HedvigButton -import com.hedvig.android.design.system.hedvig.HedvigButtonGhostWithBorder import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced -import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview -import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.HighlightLabel -import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults -import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.icon.Autogiro +import com.hedvig.android.design.system.hedvig.icon.ChevronRight import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra -import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.payin.account.data.InvoiceDelivery import com.hedvig.android.feature.payin.account.data.PayinAccount -import com.hedvig.android.feature.payin.account.data.toDeliveryString -import hedvig.resources.PAYMENTS_INVOICE +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow +import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel import hedvig.resources.PAYMENT_ADD_METHOD_BUTTON +import hedvig.resources.PAYMENT_CHOOSE_PRIMARY_BUTTON import hedvig.resources.PAYMENT_METHODS_TITLE -import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res -import hedvig.resources.something_went_wrong -import hedvig.resources.swish import octopus.type.MemberPaymentProvider -import octopus.type.PaymentMethodInvoiceDelivery import org.jetbrains.compose.resources.stringResource @Composable internal fun PayinAccountOverviewDestination( viewModel: PayinAccountOverviewViewModel, onConnectPayoutMethodClicked: () -> Unit, + onChoosePrimaryMethodClicked: () -> Unit, navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() PayinAccountOverviewScreen( uiState = uiState, onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, + onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, navigateUp = navigateUp, - setAsDefaultPayinMethod = { - viewModel.emit(PayinAccountOverviewEvent.SetDefaultMethod(it)) - }, ) } @@ -81,9 +67,9 @@ internal fun PayinAccountOverviewDestination( private fun PayinAccountOverviewScreen( uiState: PayinAccountOverviewUiState, onConnectPayoutMethodClicked: () -> Unit, + onChoosePrimaryMethodClicked: () -> Unit, onRetry: () -> Unit, navigateUp: () -> Unit, - setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, ) { HedvigScaffold( topAppBarText = stringResource(Res.string.PAYMENT_METHODS_TITLE), @@ -115,10 +101,8 @@ private fun PayinAccountOverviewScreen( currentMethods = uiState.currentMethods, availablePayinMethods = uiState.availablePayinMethods, onConnectPayinMethodClicked = onConnectPayoutMethodClicked, + onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, modifier = Modifier.weight(1f), - setAsDefaultPayinMethod = setAsDefaultPayinMethod, - loadingDefaultProvider = uiState.loadingDefaultProvider, - setDefaultProviderError = uiState.setDefaultProviderError, ) } } @@ -130,9 +114,7 @@ private fun PayoutAccountContent( currentMethods: List, availablePayinMethods: List, onConnectPayinMethodClicked: () -> Unit, - setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, - loadingDefaultProvider: MemberPaymentProvider?, - setDefaultProviderError: ErrorMessage?, + onChoosePrimaryMethodClicked: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -144,7 +126,10 @@ private fun PayoutAccountContent( if (availablePayinMethods.isNotEmpty()) { Spacer(Modifier.weight(1f)) EmptyState( - text = "You haven’t added a billing method yet. Add one to pay for your insurance.", // todo + // TODO: Add "You haven't added a billing method yet. Add one to pay for your insurance." / + // "Du har inte lagt till någon betalningsmetod än. Lägg till en för att betala för din försäkring." + // to Lokalise + text = "You haven’t added a billing method yet. Add one to pay for your insurance.", description = null, iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, modifier = Modifier @@ -154,221 +139,64 @@ private fun PayoutAccountContent( } } else { Column( + Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - currentMethods.forEach { method -> - when (method) { - is PayinAccount.SwishPayin -> { - val phoneNumber = method.phoneNumber.orEmpty() - CurrentPayinMethodRow( - label = stringResource(Res.string.swish), - text = if (method.isPending && phoneNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) - } else { - formatSwishPhoneNumber(phoneNumber) - }, - isDefault = method.isDefault, - onClick = setAsDefaultPayinMethod, - modifier = Modifier.padding(horizontal = 16.dp), - loadingDefaultProvider = loadingDefaultProvider, - isPending = method.isPending, - provider = MemberPaymentProvider.SWISH, - ) - } - - is PayinAccount.Trustly -> { - val accountNumber = formatBankAccountNumber(method.clearingNumber, method.accountNumber, method.bankName) - CurrentPayinMethodRow( - label = "Direct debit", // todo - text = if (method.isPending && accountNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) - } else { - accountNumber - }, - isDefault = method.isDefault, - onClick = setAsDefaultPayinMethod, - modifier = Modifier.padding(horizontal = 16.dp), - loadingDefaultProvider = loadingDefaultProvider, - isPending = method.isPending, - provider = MemberPaymentProvider.TRUSTLY, - ) - } - - is PayinAccount.Invoice -> { - CurrentPayinMethodRow( - stringResource(Res.string.PAYMENTS_INVOICE), - method.delivery?.toDeliveryString() ?: "", - isDefault = method.isDefault, - onClick = setAsDefaultPayinMethod, - modifier = Modifier.padding(horizontal = 16.dp), - loadingDefaultProvider = loadingDefaultProvider, - isPending = method.isPending, - provider = MemberPaymentProvider.INVOICE, - ) - } - } + for (method in currentMethods) { + CurrentPayinMethodRow(method = method) } } } - Column( - Modifier - .weight(1f) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - if (setDefaultProviderError != null) { - EmptyState( - text = stringResource(Res.string.something_went_wrong), - description = setDefaultProviderError.message, - modifier = Modifier.padding(horizontal = 16.dp), - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.ERROR, - ) - } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.height(16.dp)) + if (availablePayinMethods.isNotEmpty()) { + HedvigButton( + text = stringResource(Res.string.PAYMENT_ADD_METHOD_BUTTON), + onClick = onConnectPayinMethodClicked, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) } - Column { -// if (currentMethods.any { it.isPending }) { -// Spacer(Modifier.height(16.dp)) -// HedvigNotificationCard( -// message = "You have just added or changed a billing method, it will appear here soon.", // todo -// priority = NotificationPriority.Info, -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 16.dp), -// ) -// } - if (availablePayinMethods.isNotEmpty()) { - Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(Res.string.PAYMENT_ADD_METHOD_BUTTON), - onClick = onConnectPayinMethodClicked, - enabled = true, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - } + if (currentMethods.size > 1) { + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.PAYMENT_CHOOSE_PRIMARY_BUTTON), + onClick = onChoosePrimaryMethodClicked, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) } Spacer(Modifier.height(16.dp)) } } @Composable -private fun CurrentPayinMethodRow( - label: String, - text: String, - isDefault: Boolean, - isPending: Boolean, - loadingDefaultProvider: MemberPaymentProvider?, - onClick: (MemberPaymentProvider) -> Unit, - provider: MemberPaymentProvider, - modifier: Modifier = Modifier, -) { +private fun CurrentPayinMethodRow(method: PayinAccount, modifier: Modifier = Modifier) { HedvigCard( + shape = HedvigTheme.shapes.cornerLarge, modifier = modifier.fillMaxWidth(), ) { - HorizontalItemsWithMaximumSpaceTaken( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), - startSlot = { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - Icon( - HedvigIcons.Autogiro, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - MemberPaymentProvider.SWISH -> { - Image( - HedvigIcons.Swish, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - MemberPaymentProvider.INVOICE -> { - Image( - HedvigIcons.Kivra, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - else -> {} - } - - Spacer(Modifier.width(16.dp)) - Column { - HedvigText(text = label) - HedvigText( - text = text, - color = HedvigTheme.colorScheme.textSecondary, - ) - } - } - }, + PayinMethodRow( + method = method, + modifier = Modifier.fillMaxWidth(), endSlot = { - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - if (isDefault) { - HighlightLabel( - labelText = "Default", // todo - size = HighlightLabelDefaults.HighLightSize.Small, - color = HighlightLabelDefaults.HighlightColor.Green( - HighlightLabelDefaults.HighlightShade.LIGHT, - ), - ) - } else { - if (!isPending) { - HedvigButtonGhostWithBorder( - size = ButtonDefaults.ButtonSize.Small, - text = "Set as default", // todo - onClick = { - onClick(provider) - }, - isLoading = loadingDefaultProvider == provider, - ) - } - } + if (method.isDefault) { + PrimaryMethodLabel() } + Icon( + imageVector = HedvigIcons.ChevronRight, + contentDescription = EmptyContentDescription, + modifier = Modifier.size(24.dp), + ) }, - spaceBetween = 6.dp, ) } } -internal fun formatSwishPhoneNumber(phoneNumber: String): String { - val digits = phoneNumber.take(15) - val sb = StringBuilder() - for (i in digits.indices) { - sb.append(digits[i]) - if (i in setOf(2, 5, 7)) { - sb.append("-") - } - } - return sb.toString() -} - -private fun formatBankAccountNumber(clearingNumber: String?, accountNumber: String?, bankName: String?): String { -// return when { -// clearingNumber != null && accountNumber != null && bankName != null -> "$bankName $clearingNumber-$accountNumber" -// clearingNumber != null && bankName != null -> "$bankName $clearingNumber" -// else -> clearingNumber.orEmpty() -// } - return when { - bankName != null -> bankName - else -> clearingNumber.orEmpty() - } -} - @Composable @HedvigShortMultiScreenPreview private fun PreviewPayinAccountOverviewScreen( @@ -379,9 +207,9 @@ private fun PreviewPayinAccountOverviewScreen( PayinAccountOverviewScreen( uiState = uiState, onConnectPayoutMethodClicked = {}, + onChoosePrimaryMethodClicked = {}, onRetry = {}, navigateUp = {}, - {}, ) } } @@ -435,7 +263,6 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr ), ), availablePayinMethods = listOf(MemberPaymentProvider.SWISH), - loadingDefaultProvider = MemberPaymentProvider.SWISH, ), PayinAccountOverviewUiState.Content( currentMethods = listOf( @@ -452,7 +279,7 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr isDefault = false, ), PayinAccount.Invoice( - delivery = PaymentMethodInvoiceDelivery.KIVRA, + delivery = InvoiceDelivery.Kivra, isPending = false, isDefault = false, email = "", @@ -476,7 +303,6 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr ), ), availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), - setDefaultProviderError = ErrorMessage("Not possible to set this method as default"), ), ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index a07e8835eb..952a32fdcc 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -7,12 +7,10 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase import com.hedvig.android.feature.payin.account.data.PayinAccount -import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -23,16 +21,13 @@ import octopus.type.MemberPaymentProvider @HedvigViewModel(ActivityRetainedScope::class) internal class PayinAccountOverviewViewModel( getPayinAccountUseCase: GetPayinAccountUseCase, - setAsDefaultUseCase: SetAsDefaultUseCase, ) : MoleculeViewModel( PayinAccountOverviewUiState.Loading, - PayinAccountOverviewPresenter(getPayinAccountUseCase, setAsDefaultUseCase), + PayinAccountOverviewPresenter(getPayinAccountUseCase), ) internal sealed interface PayinAccountOverviewEvent { data object Retry : PayinAccountOverviewEvent - - data class SetDefaultMethod(val provider: MemberPaymentProvider) : PayinAccountOverviewEvent } internal sealed interface PayinAccountOverviewUiState { @@ -43,21 +38,17 @@ internal sealed interface PayinAccountOverviewUiState { data class Content( val currentMethods: List, val availablePayinMethods: List, - val loadingDefaultProvider: MemberPaymentProvider? = null, - val setDefaultProviderError: ErrorMessage? = null, ) : PayinAccountOverviewUiState } internal class PayinAccountOverviewPresenter( private val getPayinAccountUseCase: GetPayinAccountUseCase, - private val setAsDefaultUseCase: SetAsDefaultUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( lastState: PayinAccountOverviewUiState, ): PayinAccountOverviewUiState { var loadIteration by remember { mutableIntStateOf(0) } - var providerToSetAsDefault by remember { mutableStateOf(null) } var uiState by remember { mutableStateOf(lastState) } LaunchedEffect(loadIteration) { @@ -73,38 +64,9 @@ internal class PayinAccountOverviewPresenter( ) } - LaunchedEffect(providerToSetAsDefault) { - val provider = providerToSetAsDefault - if (provider != null) { - val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@LaunchedEffect - uiState = currentState.copy(loadingDefaultProvider = provider) - setAsDefaultUseCase.invoke(provider).fold( - ifLeft = { - providerToSetAsDefault = null - uiState = currentState.copy(setDefaultProviderError = it) - }, - ifRight = { data -> - providerToSetAsDefault = null - uiState = PayinAccountOverviewUiState.Content( - currentMethods = data.currentMethods, - availablePayinMethods = data.availablePayinMethods, - ) - }, - ) - } - } - CollectEvents { event -> when (event) { - PayinAccountOverviewEvent.Retry -> { - loadIteration++ - } - - is PayinAccountOverviewEvent.SetDefaultMethod -> { - val currentState = uiState as? PayinAccountOverviewUiState.Content ?: return@CollectEvents - uiState = currentState.copy(setDefaultProviderError = null) - providerToSetAsDefault = event.provider - } + PayinAccountOverviewEvent.Retry -> loadIteration++ } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt new file mode 100644 index 0000000000..f5c91041aa --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -0,0 +1,304 @@ +package com.hedvig.android.feature.payin.account.ui.primary + +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigNotificationCard +import com.hedvig.android.design.system.hedvig.HedvigPreview +import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.IconResource +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.RadioGroup +import com.hedvig.android.design.system.hedvig.RadioOption +import com.hedvig.android.design.system.hedvig.RadioOptionId +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.HelipadFilled +import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.provider +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow +import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel +import com.hedvig.android.feature.payin.account.ui.components.payinMethodSubtitle +import com.hedvig.android.feature.payin.account.ui.components.payinMethodTitle +import hedvig.resources.PAYMENT_PRIMARY_CONFIRM_TITLE +import hedvig.resources.PAYMENT_PRIMARY_SUBTITLE +import hedvig.resources.PAYMENT_PRIMARY_TITLE +import hedvig.resources.Res +import hedvig.resources.general_cancel_button +import hedvig.resources.general_continue_button +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun SelectPrimaryPayinMethodDestination( + viewModel: SelectPrimaryPayinMethodViewModel, + navigateUp: () -> Unit, + navigateBack: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + LaunchedEffect(uiState.hasSetPrimaryMethod) { + if (uiState.hasSetPrimaryMethod) { + navigateBack() + } + } + SelectPrimaryPayinMethodScreen( + uiState = uiState, + onMethodSelected = { viewModel.emit(SelectPrimaryPayinMethodEvent.SelectMethod(it)) }, + onConfirm = { viewModel.emit(SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod) }, + navigateUp = navigateUp, + navigateBack = navigateBack, + ) +} + +@Composable +private fun SelectPrimaryPayinMethodScreen( + uiState: SelectPrimaryPayinMethodUiState, + onMethodSelected: (PayinAccount) -> Unit, + onConfirm: () -> Unit, + navigateUp: () -> Unit, + navigateBack: () -> Unit, +) { + val confirmationSheetState = rememberHedvigBottomSheetState() + HedvigScaffold( + topAppBarText = null, + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + Spacer(Modifier.height(8.dp)) + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + HedvigText( + text = stringResource(Res.string.PAYMENT_PRIMARY_TITLE), + textAlign = TextAlign.Center, + ) + HedvigText( + text = stringResource(Res.string.PAYMENT_PRIMARY_SUBTITLE), + color = colorScheme.textSecondary, + textAlign = TextAlign.Center, + ) + } + Spacer(Modifier.height(48.dp)) + PayinMethodHandoverIllustration( + method = uiState.selectedMethod, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + Spacer(Modifier.weight(1f)) + Spacer(Modifier.height(48.dp)) + RadioGroup( + options = uiState.methods.map { it.toRadioOption() }, + selectedOption = uiState.selectedMethod?.let { RadioOptionId(it.provider.rawValue) }, + onRadioOptionSelected = { id -> + val method = uiState.methods.firstOrNull { it.provider.rawValue == id.id } + if (method != null) onMethodSelected(method) + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + if (uiState.errorMessage != null) { + Spacer(Modifier.height(8.dp)) + HedvigNotificationCard( + message = uiState.errorMessage, + priority = NotificationPriority.Error, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_continue_button), + onClick = { + val method = uiState.selectedMethod + if (method != null) confirmationSheetState.show(method) + }, + enabled = uiState.selectedMethod != null, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_cancel_button), + onClick = navigateBack, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + } + ConfirmPrimaryPayinMethodBottomSheet( + sheetState = confirmationSheetState, + isConfirming = uiState.isConfirming, + onConfirm = onConfirm, + ) +} + +@Composable +private fun ConfirmPrimaryPayinMethodBottomSheet( + sheetState: HedvigBottomSheetState, + isConfirming: Boolean, + onConfirm: () -> Unit, +) { + HedvigBottomSheet(sheetState) { method -> + HedvigText( + text = stringResource(Res.string.PAYMENT_PRIMARY_CONFIRM_TITLE), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(24.dp)) + HedvigNotificationCard( + // TODO: Add "Your next payment will be drawn from {method}. Claims payouts will also be sent to this + // account." / "Din nästa betalning dras från {method}. Skadeutbetalningar skickas också till detta konto." + // to Lokalise + message = "Your next payment will be drawn from ${payinMethodTitle(method)}. " + + "Claims payouts will also be sent to this account.", + priority = NotificationPriority.Info, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) + Surface( + shape = HedvigTheme.shapes.cornerLarge, + color = colorScheme.surfacePrimary, + modifier = Modifier.fillMaxWidth(), + ) { + PayinMethodRow( + method = method, + modifier = Modifier.fillMaxWidth(), + endSlot = { PrimaryMethodLabel() }, + ) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_continue_button), + onClick = onConfirm, + enabled = !isConfirming, + isLoading = isConfirming, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_cancel_button), + onClick = { sheetState.dismiss() }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) + } +} + +/** The chosen method handing over to Hedvig: the method's mark, a run of dots, then the Hedvig mark. */ +@Composable +private fun PayinMethodHandoverIllustration(method: PayinAccount?, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.fillWhite, + contentColor = colorScheme.fillBlack, + border = colorScheme.borderSecondary, + modifier = Modifier.size(74.dp), + ) { + Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { + when (method) { + is PayinAccount.Trustly -> { + Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) + } + + is PayinAccount.SwishPayin -> { + Icon(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(39.dp)) + } + + is PayinAccount.Invoice -> { + Icon(HedvigIcons.Kivra, EmptyContentDescription, Modifier.size(39.dp)) + } + + null -> {} + } + } + } + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + repeat(3) { index -> + Surface( + shape = HedvigTheme.shapes.cornerFull, + color = if (index == 0) colorScheme.fillPrimary else colorScheme.borderSecondary, + modifier = Modifier.size(6.dp), + ) {} + } + } + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.fillBlack, + contentColor = colorScheme.fillWhite, + modifier = Modifier.size(74.dp), + ) { + Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { + Icon(HedvigIcons.HelipadFilled, EmptyContentDescription, Modifier.size(39.dp)) + } + } + } +} + +@Composable +private fun PayinAccount.toRadioOption(): RadioOption = RadioOption( + id = RadioOptionId(provider.rawValue), + text = payinMethodTitle(this), + label = payinMethodSubtitle(this), + iconResource = when (this) { + is PayinAccount.Trustly -> IconResource.Vector(HedvigIcons.Trustly) + is PayinAccount.SwishPayin -> IconResource.Vector(HedvigIcons.Swish) + is PayinAccount.Invoice -> IconResource.Vector(HedvigIcons.Kivra) + }, +) + +@Composable +@HedvigPreview +private fun PreviewSelectPrimaryPayinMethodScreen() { + HedvigTheme { + Surface(color = colorScheme.backgroundPrimary) { + val methods = listOf( + PayinAccount.Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), + PayinAccount.SwishPayin("0709901232", isPending = false, isDefault = false), + ) + SelectPrimaryPayinMethodScreen( + uiState = SelectPrimaryPayinMethodUiState(methods = methods, selectedMethod = methods[1]), + onMethodSelected = {}, + onConfirm = {}, + navigateUp = {}, + navigateBack = {}, + ) + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt new file mode 100644 index 0000000000..a7e2918598 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt @@ -0,0 +1,99 @@ +package com.hedvig.android.feature.payin.account.ui.primary + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.di.ActivityRetainedScope +import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase +import com.hedvig.android.feature.payin.account.data.provider +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject + +@AssistedInject +@HedvigViewModel(ActivityRetainedScope::class) +internal class SelectPrimaryPayinMethodViewModel( + @Assisted currentMethods: List, + setAsDefaultUseCase: SetAsDefaultUseCase, +) : MoleculeViewModel( + initialState = SelectPrimaryPayinMethodUiState( + methods = currentMethods, + selectedMethod = currentMethods.firstOrNull { it.isDefault } ?: currentMethods.firstOrNull(), + ), + presenter = SelectPrimaryPayinMethodPresenter(setAsDefaultUseCase), + ) + +internal sealed interface SelectPrimaryPayinMethodEvent { + data class SelectMethod(val method: PayinAccount) : SelectPrimaryPayinMethodEvent + + data object ConfirmSelectedMethod : SelectPrimaryPayinMethodEvent +} + +internal data class SelectPrimaryPayinMethodUiState( + val methods: List, + val selectedMethod: PayinAccount?, + val isConfirming: Boolean = false, + val errorMessage: String? = null, + val hasSetPrimaryMethod: Boolean = false, +) + +internal class SelectPrimaryPayinMethodPresenter( + private val setAsDefaultUseCase: SetAsDefaultUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: SelectPrimaryPayinMethodUiState, + ): SelectPrimaryPayinMethodUiState { + var selectedMethod by remember { mutableStateOf(lastState.selectedMethod) } + var isConfirming by remember { mutableStateOf(false) } + var errorMessage by remember { mutableStateOf(null) } + var hasSetPrimaryMethod by remember { mutableStateOf(false) } + var methodToConfirm by remember { mutableStateOf(null) } + + LaunchedEffect(methodToConfirm) { + val method = methodToConfirm ?: return@LaunchedEffect + isConfirming = true + errorMessage = null + setAsDefaultUseCase.invoke(method.provider).fold( + ifLeft = { + isConfirming = false + methodToConfirm = null + errorMessage = it.message + }, + ifRight = { + isConfirming = false + methodToConfirm = null + hasSetPrimaryMethod = true + }, + ) + } + + CollectEvents { event -> + when (event) { + is SelectPrimaryPayinMethodEvent.SelectMethod -> { + selectedMethod = event.method + } + + SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod -> { + if (!isConfirming) { + methodToConfirm = selectedMethod + } + } + } + } + + return lastState.copy( + selectedMethod = selectedMethod, + isConfirming = isConfirming, + errorMessage = errorMessage, + hasSetPrimaryMethod = hasSetPrimaryMethod, + ) + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index e5d8efcd7a..23396486ac 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -51,7 +51,7 @@ import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.feature.payin.account.ui.overview.formatSwishPhoneNumber +import com.hedvig.android.feature.payin.account.ui.components.formatSwishPhoneNumber import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL import hedvig.resources.Res diff --git a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt new file mode 100644 index 0000000000..ca766677b0 --- /dev/null +++ b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt @@ -0,0 +1,39 @@ +package com.hedvig.android.feature.payin.account.navigation + +import assertk.assertThat +import assertk.assertions.isEqualTo +import com.hedvig.android.feature.payin.account.data.InvoiceDelivery +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.navigation.common.HedvigNavKey +import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass +import org.junit.Test + +internal class SelectPrimaryPayinMethodKeySerializationTest { + private val json = Json { + serializersModule = SerializersModule { + polymorphic(HedvigNavKey::class) { + subclass(SelectPrimaryPayinMethodKey::class) + } + } + } + + @Test + fun `key carrying every payin account type survives a serialization round trip`() { + val key = SelectPrimaryPayinMethodKey( + currentMethods = listOf( + PayinAccount.Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), + PayinAccount.SwishPayin("0709901232", isPending = true, isDefault = false), + PayinAccount.Invoice(InvoiceDelivery.Kivra, "a@b.com", isPending = false, isDefault = false), + PayinAccount.Invoice(null, null, isPending = false, isDefault = false), + ), + ) + + val encoded = json.encodeToString(PolymorphicSerializer(HedvigNavKey::class), key) + + assertThat(json.decodeFromString(PolymorphicSerializer(HedvigNavKey::class), encoded)).isEqualTo(key) + } +} From 6815930c7a91ca5013d862125ffc08b77c266492 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 8 Sep 2026 23:12:20 +0200 Subject: [PATCH 46/75] changes to payments navigation --- .../navigation/PayinAccountDestination.kt | 5 + .../account/navigation/PayinAccountEntries.kt | 33 ++ .../account/ui/components/PayinMethodRow.kt | 5 + .../PayinMethodDetailsDestination.kt | 188 +++++++++ .../PayinAccountOverviewDestination.kt | 11 +- ...tPrimaryPayinMethodKeySerializationTest.kt | 18 + .../graphql/QueryMemberPaymentDetails.graphql | 13 - .../data/GetMemberPaymentsDetailsUseCase.kt | 86 ---- .../feature/payments/data/PaymentMethod.kt | 18 - .../navigation/PaymentsDestination.kt | 3 - .../payments/navigation/PaymentsEntries.kt | 15 +- .../MemberPaymentDetailsDestination.kt | 389 ------------------ .../MemberPaymentDetailsViewModel.kt | 79 ---- .../ui/payments/PaymentsDestination.kt | 24 +- 14 files changed, 271 insertions(+), 616 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt delete mode 100644 app/feature/feature-payments/src/main/graphql/QueryMemberPaymentDetails.graphql delete mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt delete mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentMethod.kt delete mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt delete mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 64b6ebc78d..9ee6de09b4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -19,3 +19,8 @@ internal data object SetupInvoicePayinKey : HedvigNavKey internal data class SelectPrimaryPayinMethodKey( val currentMethods: List, ) : HedvigNavKey + +@Serializable +internal data class PayinMethodDetailsKey( + val method: PayinAccount, +) : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index fad675609a..1f7007f8bd 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -2,7 +2,10 @@ package com.hedvig.android.feature.payin.account.navigation import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope +import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.design.system.hedvig.GlobalSnackBarState +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsDestination import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewDestination import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel @@ -41,6 +44,9 @@ fun EntryProviderScope.payinAccountEntries( ), ) }, + onPayinMethodClicked = dropUnlessResumed { method: PayinAccount -> + backstack.add(PayinMethodDetailsKey(method)) + }, onChoosePrimaryMethodClicked = dropUnlessResumed { val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content backstack.add(SelectPrimaryPayinMethodKey(currentMethods = content?.currentMethods ?: emptyList())) @@ -49,6 +55,33 @@ fun EntryProviderScope.payinAccountEntries( ) } + entry { key -> + val method = key.method + PayinMethodDetailsDestination( + method = method, + navigateUp = backstack::navigateUp, + onChangeMethod = dropUnlessResumed { + when (method) { + is PayinAccount.Trustly -> { + backstack.popUpTo(inclusive = true) + navigateToConnectPayment() + } + + is PayinAccount.SwishPayin -> { + backstack.add(SetupSwishPayinKey) + } + + is PayinAccount.Invoice -> { + backstack.add(SetupInvoicePayinKey) + } + } + }, + onRemoveMethod = { + // TODO: call the remove-payin-method API once the backend exposes one. + }, + ) + } + entry { key -> val viewModel: SelectPrimaryPayinMethodViewModel = assistedMetroViewModel { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index 200a44a436..eb0ac64329 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -141,6 +141,11 @@ private fun PayinAccount.Trustly.maskedAccount(): String? { return "$bank ···· $lastFour" } +internal fun PayinAccount.Trustly.maskedAccountNumber(): String? { + val lastFour = accountNumber?.takeLast(4)?.takeIf { it.isNotBlank() } ?: return null + return "**** $lastFour" +} + internal fun formatSwishPhoneNumber(phoneNumber: String): String { val digits = phoneNumber.take(15) val sb = StringBuilder() diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt new file mode 100644 index 0000000000..e85e5e8075 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -0,0 +1,188 @@ +package com.hedvig.android.feature.payin.account.ui.methoddetails + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigRedTextButton +import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HorizontalDivider +import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.feature.payin.account.data.InvoiceDelivery +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.toDeliveryString +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow +import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel +import com.hedvig.android.feature.payin.account.ui.components.maskedAccountNumber +import hedvig.resources.GENERAL_REMOVE +import hedvig.resources.PAYMENTS_ACCOUNT +import hedvig.resources.PAYMENTS_AUTOGIRO_LABEL +import hedvig.resources.PAYMENTS_BANK_LABEL +import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYMENTS_PAYMENT_METHOD +import hedvig.resources.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT +import hedvig.resources.Res +import hedvig.resources.swish +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun PayinMethodDetailsDestination( + method: PayinAccount, + navigateUp: () -> Unit, + onChangeMethod: () -> Unit, + onRemoveMethod: () -> Unit, +) { + HedvigScaffold( + topAppBarText = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + HedvigCard( + shape = HedvigTheme.shapes.cornerLarge, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + PayinMethodRow( + method = method, + modifier = Modifier.fillMaxWidth(), + endSlot = { if (method.isDefault) PrimaryMethodLabel() }, + ) + } + Spacer(Modifier.height(8.dp)) + DetailRow( + label = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + value = when (method) { + is PayinAccount.Trustly -> stringResource(Res.string.PAYMENTS_AUTOGIRO_LABEL) + is PayinAccount.SwishPayin -> stringResource(Res.string.swish) + is PayinAccount.Invoice -> stringResource(Res.string.PAYMENTS_INVOICE) + }, + ) + // The charging day comes from MemberPaymentMethods.chargingDay, which the payin methods query does not + // request yet. Restore this row, and the info-button explanation sheet that belongs to it, once it does. + // DetailRow( + // label = stringResource(Res.string.PAYMENTS_PAYMENT_DUE), + // value = stringResource(Res.string.PAYMENTS_DUE_DESCRIPTION, chargingDay.format()), + // ) + when (method) { + is PayinAccount.SwishPayin -> { + DetailRow( + // TODO: Add "Number" / "Nummer" to Lokalise + label = "Number", + value = method.phoneNumber.orEmpty(), + ) + } + + is PayinAccount.Trustly -> { + DetailRow( + label = stringResource(Res.string.PAYMENTS_ACCOUNT), + value = method.maskedAccountNumber().orEmpty(), + ) + DetailRow( + label = stringResource(Res.string.PAYMENTS_BANK_LABEL), + value = method.bankName.orEmpty(), + ) + // The mandate holder comes from MemberPaymentInformation.chargeMethod.mandate, which nothing in this + // feature queries yet. + // DetailRow( + // label = stringResource(Res.string.PAYMENTS_MANDATE), + // value = mandate, + // ) + } + + is PayinAccount.Invoice -> { + DetailRow( + label = stringResource(Res.string.PAYMENTS_ACCOUNT), + value = method.email ?: method.delivery.toDeliveryString().orEmpty(), + ) + } + } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.height(16.dp)) + val changeButtonText = when (method) { + // TODO: Add "Change number" / "Ändra nummer" to Lokalise + is PayinAccount.SwishPayin -> "Change number" + + is PayinAccount.Trustly -> stringResource(Res.string.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT) + + is PayinAccount.Invoice -> null + } + if (changeButtonText != null) { + HedvigButton( + text = changeButtonText, + onClick = onChangeMethod, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + } + HedvigRedTextButton( + text = stringResource(Res.string.GENERAL_REMOVE), + onClick = onRemoveMethod, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + } +} + +@Composable +private fun DetailRow(label: String, value: String) { + HorizontalItemsWithMaximumSpaceTaken( + startSlot = { HedvigText(label) }, + endSlot = { + HedvigText( + text = value, + textAlign = TextAlign.End, + modifier = Modifier.fillMaxWidth(), + color = HedvigTheme.colorScheme.textSecondary, + ) + }, + spaceBetween = 8.dp, + modifier = Modifier.padding(horizontal = 18.dp, vertical = 16.dp), + ) + HorizontalDivider(Modifier.padding(horizontal = 18.dp)) +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewPayinMethodDetailsDestination( + @PreviewParameter(PayinMethodPreviewProvider::class) method: PayinAccount, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + PayinMethodDetailsDestination( + method = method, + navigateUp = {}, + onChangeMethod = {}, + onRemoveMethod = {}, + ) + } + } +} + +private class PayinMethodPreviewProvider : CollectionPreviewParameterProvider( + listOf( + PayinAccount.SwishPayin("0709901232", isPending = false, isDefault = true), + PayinAccount.Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = false), + PayinAccount.Invoice(InvoiceDelivery.Kivra, null, isPending = false, isDefault = false), + ), +) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 509681989e..f2d764f0ec 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -51,6 +51,7 @@ internal fun PayinAccountOverviewDestination( viewModel: PayinAccountOverviewViewModel, onConnectPayoutMethodClicked: () -> Unit, onChoosePrimaryMethodClicked: () -> Unit, + onPayinMethodClicked: (PayinAccount) -> Unit, navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -58,6 +59,7 @@ internal fun PayinAccountOverviewDestination( uiState = uiState, onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, + onPayinMethodClicked = onPayinMethodClicked, onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, navigateUp = navigateUp, ) @@ -68,6 +70,7 @@ private fun PayinAccountOverviewScreen( uiState: PayinAccountOverviewUiState, onConnectPayoutMethodClicked: () -> Unit, onChoosePrimaryMethodClicked: () -> Unit, + onPayinMethodClicked: (PayinAccount) -> Unit, onRetry: () -> Unit, navigateUp: () -> Unit, ) { @@ -102,6 +105,7 @@ private fun PayinAccountOverviewScreen( availablePayinMethods = uiState.availablePayinMethods, onConnectPayinMethodClicked = onConnectPayoutMethodClicked, onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, + onPayinMethodClicked = onPayinMethodClicked, modifier = Modifier.weight(1f), ) } @@ -115,6 +119,7 @@ private fun PayoutAccountContent( availablePayinMethods: List, onConnectPayinMethodClicked: () -> Unit, onChoosePrimaryMethodClicked: () -> Unit, + onPayinMethodClicked: (PayinAccount) -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -143,7 +148,7 @@ private fun PayoutAccountContent( verticalArrangement = Arrangement.spacedBy(8.dp), ) { for (method in currentMethods) { - CurrentPayinMethodRow(method = method) + CurrentPayinMethodRow(method = method, onClick = { onPayinMethodClicked(method) }) } } } @@ -175,8 +180,9 @@ private fun PayoutAccountContent( } @Composable -private fun CurrentPayinMethodRow(method: PayinAccount, modifier: Modifier = Modifier) { +private fun CurrentPayinMethodRow(method: PayinAccount, onClick: () -> Unit, modifier: Modifier = Modifier) { HedvigCard( + onClick = onClick, shape = HedvigTheme.shapes.cornerLarge, modifier = modifier.fillMaxWidth(), ) { @@ -208,6 +214,7 @@ private fun PreviewPayinAccountOverviewScreen( uiState = uiState, onConnectPayoutMethodClicked = {}, onChoosePrimaryMethodClicked = {}, + onPayinMethodClicked = {}, onRetry = {}, navigateUp = {}, ) diff --git a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt index ca766677b0..5534d7caf7 100644 --- a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt +++ b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt @@ -17,6 +17,7 @@ internal class SelectPrimaryPayinMethodKeySerializationTest { serializersModule = SerializersModule { polymorphic(HedvigNavKey::class) { subclass(SelectPrimaryPayinMethodKey::class) + subclass(PayinMethodDetailsKey::class) } } } @@ -36,4 +37,21 @@ internal class SelectPrimaryPayinMethodKeySerializationTest { assertThat(json.decodeFromString(PolymorphicSerializer(HedvigNavKey::class), encoded)).isEqualTo(key) } + + @Test + fun `payin method details key survives a serialization round trip for every account type`() { + val methods = listOf( + PayinAccount.Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), + PayinAccount.SwishPayin("0709901232", isPending = true, isDefault = false), + PayinAccount.Invoice(InvoiceDelivery.Mail, "a@b.com", isPending = false, isDefault = false), + ) + + for (method in methods) { + val key = PayinMethodDetailsKey(method) + + val encoded = json.encodeToString(PolymorphicSerializer(HedvigNavKey::class), key) + + assertThat(json.decodeFromString(PolymorphicSerializer(HedvigNavKey::class), encoded)).isEqualTo(key) + } + } } diff --git a/app/feature/feature-payments/src/main/graphql/QueryMemberPaymentDetails.graphql b/app/feature/feature-payments/src/main/graphql/QueryMemberPaymentDetails.graphql deleted file mode 100644 index c4cd0d824f..0000000000 --- a/app/feature/feature-payments/src/main/graphql/QueryMemberPaymentDetails.graphql +++ /dev/null @@ -1,13 +0,0 @@ -query MemberPaymentDetails { - currentMember { - paymentMethods { - payinMethods { - ...MemberPaymentMethodFragment - } - defaultPayinMethod { - ...MemberPaymentMethodFragment - } - chargingDay - } - } -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt deleted file mode 100644 index e769af072c..0000000000 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetMemberPaymentsDetailsUseCase.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.hedvig.android.feature.payments.data - -import arrow.core.Either -import arrow.core.raise.context.bind -import arrow.core.raise.context.either -import arrow.core.raise.context.raise -import com.apollographql.apollo.ApolloClient -import com.apollographql.cache.normalized.FetchPolicy -import com.apollographql.cache.normalized.fetchPolicy -import com.hedvig.android.apollo.ErrorMessage -import com.hedvig.android.apollo.safeExecute -import com.hedvig.android.core.common.ErrorMessage -import com.hedvig.android.core.common.di.AppScope -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn -import octopus.MemberPaymentDetailsQuery -import octopus.fragment.MemberPaymentMethodFragment -import octopus.fragment.MemberPaymentMethodFragment.PaymentMethodBankAccountDetailsDetails -import octopus.fragment.MemberPaymentMethodFragment.PaymentMethodInvoiceDetailsDetails -import octopus.fragment.MemberPaymentMethodFragment.PaymentMethodSwishDetailsDetails -import octopus.type.MemberPaymentMethodStatus -import octopus.type.MemberPaymentProvider -import octopus.type.PaymentMethodInvoiceDelivery - -internal interface GetMemberPaymentsDetailsUseCase { - suspend fun invoke(): Either -} - -@ContributesBinding(AppScope::class) -@SingleIn(AppScope::class) -@Inject -internal class GetMemberPaymentsDetailsUseCaseImpl( - private val apolloClient: ApolloClient, -) : GetMemberPaymentsDetailsUseCase { - override suspend fun invoke(): Either { - return either { - val result = apolloClient.query(MemberPaymentDetailsQuery()) - .fetchPolicy(FetchPolicy.NetworkOnly) - .safeExecute(::ErrorMessage) - .onLeft { - logcat(LogPriority.WARN) { "GetMemberPaymentsDetailsUseCase returned error: $it" } - } - .bind() - val paymentMethods = result.currentMember.paymentMethods - val payinMethod = paymentMethods.defaultPayinMethod - ?: paymentMethods.payinMethods.find { it.isDefault && it.status == MemberPaymentMethodStatus.ACTIVE } - val paymentMethod = payinMethod?.provider?.toPaymentMethod() - MemberPaymentsDetails( - paymentMethod = paymentMethod, - chargingDayInTheMonth = paymentMethods.chargingDay, - account = payinMethod?.details?.toPaymentAccount(), - ) - } - } -} - -internal data class MemberPaymentsDetails( - val paymentMethod: PaymentMethod?, - val chargingDayInTheMonth: Int?, - val account: PaymentAccount?, -) - -private fun MemberPaymentProvider.toPaymentMethod(): PaymentMethod? = when (this) { - MemberPaymentProvider.TRUSTLY -> PaymentMethod.TRUSTLY - MemberPaymentProvider.SWISH -> PaymentMethod.SWISH - MemberPaymentProvider.NORDEA -> PaymentMethod.NORDEA - MemberPaymentProvider.INVOICE -> PaymentMethod.INVOICE - MemberPaymentProvider.UNKNOWN__ -> null -} - -private fun MemberPaymentMethodFragment.Details.toPaymentAccount(): PaymentAccount? = when (this) { - is PaymentMethodInvoiceDetailsDetails -> when (delivery) { - PaymentMethodInvoiceDelivery.KIVRA -> PaymentAccount.Kivra - PaymentMethodInvoiceDelivery.MAIL -> email?.let(PaymentAccount::Email) - PaymentMethodInvoiceDelivery.UNKNOWN__ -> null - } - - is PaymentMethodSwishDetailsDetails -> PaymentAccount.PhoneNumber(phoneNumber) - - is PaymentMethodBankAccountDetailsDetails -> PaymentAccount.BankAccount(account, bank) - - else -> null -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentMethod.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentMethod.kt deleted file mode 100644 index 781843df04..0000000000 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentMethod.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.hedvig.android.feature.payments.data - -internal enum class PaymentMethod { - TRUSTLY, - SWISH, - NORDEA, - INVOICE, -} - -internal sealed interface PaymentAccount { - data object Kivra : PaymentAccount - - data class Email(val email: String) : PaymentAccount - - data class PhoneNumber(val phoneNumber: String) : PaymentAccount - - data class BankAccount(val account: String, val bank: String) : PaymentAccount -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsDestination.kt index b438e28e9d..f627b5a8ed 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsDestination.kt @@ -9,9 +9,6 @@ internal data object PaymentHistoryKey : HedvigNavKey @Serializable internal data object DiscountsKey : HedvigNavKey -@Serializable -internal data object MemberPaymentDetailsKey : HedvigNavKey - @Serializable internal data object ManualChargeKey : HedvigNavKey diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt index 50b2cf6b7b..a8a0396c17 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt @@ -15,8 +15,6 @@ import com.hedvig.android.feature.payments.ui.history.PaymentHistoryViewModel import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeDestination import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeSuccessDestination import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeViewModel -import com.hedvig.android.feature.payments.ui.memberpaymentdetails.MemberPaymentDetailsDestination -import com.hedvig.android.feature.payments.ui.memberpaymentdetails.MemberPaymentDetailsViewModel import com.hedvig.android.feature.payments.ui.payments.PaymentsDestination import com.hedvig.android.feature.payments.ui.payments.PaymentsViewModel import com.hedvig.android.navigation.common.HedvigNavKey @@ -49,9 +47,7 @@ fun EntryProviderScope.paymentsEntries( onPaymentClicked = dropUnlessResumed { id: String? -> backstack.add(PaymentDetailsKey(id)) }, - onMemberPaymentDetailsClicked = dropUnlessResumed { - backstack.add(MemberPaymentDetailsKey) - }, + onPaymentMethodsClicked = dropUnlessResumed { navigateToPayinAccount() }, onOpenManualCharge = { backstack.add(ManualChargeKey) }, @@ -119,13 +115,4 @@ fun EntryProviderScope.paymentsEntries( }, ) } - - entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { - val viewModel: MemberPaymentDetailsViewModel = metroViewModel() - MemberPaymentDetailsDestination( - viewModel, - onChangeBankAccount = navigateToPayinAccount, - navigateUp = backstack::navigateUp, - ) - } } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt deleted file mode 100644 index d31fbf51fc..0000000000 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsDestination.kt +++ /dev/null @@ -1,389 +0,0 @@ -package com.hedvig.android.feature.payments.ui.memberpaymentdetails - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsBottomHeight -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.hedvig.android.design.system.hedvig.ButtonDefaults -import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large -import com.hedvig.android.design.system.hedvig.HedvigBottomSheet -import com.hedvig.android.design.system.hedvig.HedvigButton -import com.hedvig.android.design.system.hedvig.HedvigErrorSection -import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress -import com.hedvig.android.design.system.hedvig.HedvigNotificationCard -import com.hedvig.android.design.system.hedvig.HedvigScaffold -import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview -import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTextButton -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.HorizontalDivider -import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken -import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.IconButton -import com.hedvig.android.design.system.hedvig.NotificationDefaults -import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.datepicker.getLocale -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.InfoFilled -import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.feature.payments.data.MemberPaymentsDetails -import com.hedvig.android.feature.payments.data.PaymentAccount -import com.hedvig.android.feature.payments.data.PaymentMethod -import com.hedvig.android.placeholder.PlaceholderHighlight -import com.hedvig.android.placeholder.shimmer -import hedvig.resources.DASHBOARD_OPEN_CHAT -import hedvig.resources.KIVRA_NOTIFICATION_BOX_TEXT -import hedvig.resources.MANAGE_BILLING_METHODS_BUTTON -import hedvig.resources.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON -import hedvig.resources.R -import hedvig.resources.Res -import hedvig.resources.swish -import kotlinx.datetime.LocalDate -import org.jetbrains.compose.resources.stringResource - -@Composable -internal fun MemberPaymentDetailsDestination( - viewModel: MemberPaymentDetailsViewModel, - navigateUp: () -> Unit, - onChangeBankAccount: () -> Unit, -) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - MemberPaymentDetailsScreen( - uiState = uiState, - retry = { - viewModel.emit(MemberPaymentDetailsEvent.Retry) - }, - navigateUp = navigateUp, - onChangeBankAccount = onChangeBankAccount, - ) -} - -@Composable -private fun MemberPaymentDetailsScreen( - uiState: MemberPaymentDetailsUiState, - retry: () -> Unit, - navigateUp: () -> Unit, - onChangeBankAccount: () -> Unit, -) { - HedvigScaffold( - topAppBarText = stringResource(R.string.PAYMENTS_PAYMENT_DETAILS_INFO_TITLE), - navigateUp = navigateUp, - ) { - when (uiState) { - MemberPaymentDetailsUiState.Failure -> { - HedvigErrorSection( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) - .weight(1f), - onButtonClick = retry, - buttonText = stringResource(R.string.GENERAL_RETRY), - ) - } - - MemberPaymentDetailsUiState.Loading -> { - HedvigFullScreenCenterAlignedProgress(Modifier.weight(1f)) - } - - is MemberPaymentDetailsUiState.Success -> { - MemberPaymentDetailsSuccessScreen( - uiState, - onChangeBankAccount, - modifier = Modifier.weight(1f), - ) - } - } - } -} - -@Composable -private fun MemberPaymentDetailsSuccessScreen( - uiState: MemberPaymentDetailsUiState.Success, - onChangeBankAccount: () -> Unit, - modifier: Modifier = Modifier, -) { - val paymentDetails = uiState.paymentDetails - Column(modifier.padding(horizontal = 16.dp).verticalScroll(rememberScrollState())) { - val explanationBottomSheetState = rememberHedvigBottomSheetState() - ExplanationBottomSheet(explanationBottomSheetState) - val paymentMethod = paymentDetails.paymentMethod - if (paymentMethod != null) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - HedvigText(stringResource(id = R.string.PAYMENTS_PAYMENT_METHOD)) - }, - endSlot = { - HedvigText( - text = paymentMethod.label(), - textAlign = TextAlign.End, - modifier = Modifier.fillMaxWidth(), - color = HedvigTheme.colorScheme.textSecondary, - ) - }, - modifier = Modifier.padding(vertical = 16.dp), - spaceBetween = 8.dp, - ) - HorizontalDivider() - } else { - HedvigText( - "No active payment method", // todo!!!! - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) - } - val dayOfMonth = paymentDetails.chargingDayInTheMonth - if (dayOfMonth != null) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - HedvigText(stringResource(id = R.string.PAYMENTS_PAYMENT_DUE)) - }, - endSlot = { - val dayOfMonthFormatted = dayOfMonth.format() - Row(horizontalArrangement = Arrangement.End) { - HedvigText( - text = stringResource( - R.string.PAYMENTS_DUE_DESCRIPTION, - dayOfMonthFormatted, - ), - textAlign = TextAlign.End, - modifier = Modifier.weight(1f, false), - color = HedvigTheme.colorScheme.textSecondary, - ) - val explanation = paymentDetails.explanationData(dayOfMonthFormatted) - if (explanation != null) { - Spacer(Modifier.width(8.dp)) - IconButton( - onClick = { explanationBottomSheetState.show(explanation) }, - modifier = Modifier.size(24.dp), - ) { - Icon( - imageVector = HedvigIcons.InfoFilled, - contentDescription = stringResource(R.string.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION), - modifier = Modifier.size(24.dp), - tint = HedvigTheme.colorScheme.fillSecondaryTransparent, - ) - } - } - } - }, - modifier = Modifier.padding(vertical = 16.dp), - spaceBetween = 8.dp, - ) - HorizontalDivider() - } - val account = paymentDetails.account - if (account is PaymentAccount.BankAccount) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { HedvigText(stringResource(id = R.string.PAYMENTS_BANK_LABEL)) }, - endSlot = { - HedvigText( - text = account.bank, - textAlign = TextAlign.End, - modifier = Modifier.fillMaxWidth(), - color = HedvigTheme.colorScheme.textSecondary, - ) - }, - spaceBetween = 8.dp, - modifier = Modifier.padding(vertical = 16.dp), - ) - HorizontalDivider() - } - val accountLabel = account?.label() - if (accountLabel != null) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - HedvigText(stringResource(id = R.string.PAYMENTS_ACCOUNT)) - }, - endSlot = { - HedvigText( - text = accountLabel, - textAlign = TextAlign.End, - modifier = Modifier.fillMaxWidth(), - color = HedvigTheme.colorScheme.textSecondary, - ) - }, - modifier = Modifier.padding(vertical = 16.dp), - spaceBetween = 8.dp, - ) - HorizontalDivider() - } - Spacer(Modifier.weight(1f)) - Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(Res.string.MANAGE_BILLING_METHODS_BUTTON), // todo - onClick = onChangeBankAccount, - enabled = true, - buttonStyle = ButtonDefaults.ButtonStyle.Secondary, - modifier = Modifier - .fillMaxWidth() - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) - - Spacer(Modifier.height(16.dp)) - } -} - -@Composable -private fun PaymentMethod.label(): String = when (this) { - PaymentMethod.TRUSTLY -> stringResource(R.string.PAYMENTS_AUTOGIRO_LABEL) - PaymentMethod.NORDEA -> stringResource(R.string.BANK_PAYOUT_METHOD_CARD_TITLE) - PaymentMethod.INVOICE -> stringResource(R.string.PAYMENTS_INVOICE) - PaymentMethod.SWISH -> stringResource(Res.string.swish) -} - -@Composable -private fun PaymentAccount.label(): String = when (this) { - PaymentAccount.Kivra -> "Kivra" - is PaymentAccount.Email -> email - is PaymentAccount.PhoneNumber -> phoneNumber - is PaymentAccount.BankAccount -> account -} - -private fun MemberPaymentsDetails.explanationData(dayOfMonthFormatted: String): PaymentExplanationData? = when { - paymentMethod == PaymentMethod.TRUSTLY -> PaymentExplanationData.Trustly(dayOfMonthFormatted) - paymentMethod == PaymentMethod.INVOICE && account == PaymentAccount.Kivra -> PaymentExplanationData.Kivra - else -> null -} - -private sealed interface PaymentExplanationData { - data object Kivra : PaymentExplanationData - - data class Trustly(val dueDate: String) : PaymentExplanationData -} - -@Composable -private fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { - HedvigBottomSheet(sheetState) { data -> - val text = when (data) { - PaymentExplanationData.Kivra -> stringResource(R.string.KIVRA_PAYMENT_INFO) - is PaymentExplanationData.Trustly -> stringResource(R.string.PAYMENTS_PAYMENT_DUE_INFO, data.dueDate) - } - HedvigText( - text = text, - modifier = Modifier - .fillMaxWidth(), - ) - Spacer(Modifier.height(32.dp)) - HedvigTextButton( - text = stringResource(id = R.string.general_close_button), - buttonSize = Large, - onClick = { sheetState.dismiss() }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(8.dp)) - Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) - } -} - -@Composable -private fun Int.format(): String { - val day = this - val lastDigit = day % 10 - - val locale = getLocale() - - val suffix = when (locale.language) { - "en" -> when (lastDigit) { - 1 -> "st" - 2 -> "nd" - 3 -> "rd" - else -> "th" - } - - "sv" -> when (day) { - 11, 12 -> ":e" - - else -> when (lastDigit) { - 1, 2 -> ":a" - else -> ":e" - } - } - - else -> when (lastDigit) { - 1 -> "st" - 2 -> "nd" - 3 -> "rd" - else -> "th" - } - } - - return "$day$suffix" -} - -@Composable -@HedvigShortMultiScreenPreview -internal fun MemberPaymentDetailsScreenPreview( - @PreviewParameter( - MemberPaymentDetailsUiStatePreviewParameterProvider::class, - ) uiState: MemberPaymentDetailsUiState, -) { - HedvigTheme { - Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - MemberPaymentDetailsScreen( - uiState, - {}, - {}, - {}, - ) - } - } -} - -private class MemberPaymentDetailsUiStatePreviewParameterProvider() : - CollectionPreviewParameterProvider( - listOf( - MemberPaymentDetailsUiState.Failure, - MemberPaymentDetailsUiState.Loading, - MemberPaymentDetailsUiState.Success( - paymentDetails = MemberPaymentsDetails( - paymentMethod = null, - chargingDayInTheMonth = null, - account = null, - ), - ), - MemberPaymentDetailsUiState.Success( - paymentDetails = MemberPaymentsDetails( - paymentMethod = PaymentMethod.TRUSTLY, - chargingDayInTheMonth = 28, - account = PaymentAccount.BankAccount(account = "12345-67890", bank = "Nordea"), - ), - ), - MemberPaymentDetailsUiState.Success( - paymentDetails = MemberPaymentsDetails( - paymentMethod = PaymentMethod.INVOICE, - chargingDayInTheMonth = 28, - account = PaymentAccount.Kivra, - ), - ), - MemberPaymentDetailsUiState.Success( - paymentDetails = MemberPaymentsDetails( - paymentMethod = PaymentMethod.SWISH, - chargingDayInTheMonth = 28, - account = PaymentAccount.PhoneNumber("0701234567"), - ), - ), - ), - ) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt deleted file mode 100644 index 3dac4d88a6..0000000000 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/memberpaymentdetails/MemberPaymentDetailsViewModel.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.hedvig.android.feature.payments.ui.memberpaymentdetails - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import com.hedvig.android.core.common.di.ActivityRetainedScope -import com.hedvig.android.core.common.di.HedvigViewModel -import com.hedvig.android.feature.payments.data.GetMemberPaymentsDetailsUseCase -import com.hedvig.android.feature.payments.data.MemberPaymentsDetails -import com.hedvig.android.molecule.public.MoleculePresenter -import com.hedvig.android.molecule.public.MoleculePresenterScope -import com.hedvig.android.molecule.public.MoleculeViewModel -import dev.zacsweers.metro.Inject -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.WhileSubscribed - -@Inject -@HedvigViewModel(ActivityRetainedScope::class) -internal class MemberPaymentDetailsViewModel( - getMemberPaymentsDetailsUseCase: GetMemberPaymentsDetailsUseCase, -) : MoleculeViewModel( - initialState = MemberPaymentDetailsUiState.Loading, - presenter = MemberPaymentDetailsPresenter( - getMemberPaymentsDetailsUseCase, - ), - sharingStarted = SharingStarted.WhileSubscribed(2.seconds), - ) - -private class MemberPaymentDetailsPresenter( - private val getMemberPaymentsDetailsUseCase: GetMemberPaymentsDetailsUseCase, -) : MoleculePresenter { - @Composable - override fun MoleculePresenterScope.present( - lastState: MemberPaymentDetailsUiState, - ): MemberPaymentDetailsUiState { - var dataLoadIteration by remember { mutableIntStateOf(0) } - var currentState by remember { - mutableStateOf(lastState) - } - - CollectEvents { - when (it) { - MemberPaymentDetailsEvent.Retry -> dataLoadIteration++ - } - } - - LaunchedEffect(dataLoadIteration) { - currentState = MemberPaymentDetailsUiState.Loading - getMemberPaymentsDetailsUseCase.invoke().fold( - ifLeft = { - currentState = MemberPaymentDetailsUiState.Failure - }, - ifRight = { - currentState = MemberPaymentDetailsUiState.Success(it) - }, - ) - } - return currentState - } -} - -internal sealed interface MemberPaymentDetailsUiState { - data object Loading : MemberPaymentDetailsUiState - - data object Failure : MemberPaymentDetailsUiState - - data class Success( - val paymentDetails: MemberPaymentsDetails, - ) : MemberPaymentDetailsUiState -} - -internal sealed interface MemberPaymentDetailsEvent { - data object Retry : MemberPaymentDetailsEvent -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt index 259c488298..d827568488 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt @@ -100,7 +100,6 @@ import hedvig.resources.PAYMENTS_DISCOUNTS_SECTION_TITLE import hedvig.resources.PAYMENTS_IN_PROGRESS import hedvig.resources.PAYMENTS_MISSED_PAYMENT import hedvig.resources.PAYMENTS_NO_PAYMENTS_IN_PROGRESS -import hedvig.resources.PAYMENTS_PAYMENT_DETAILS_INFO_TITLE import hedvig.resources.PAYMENTS_PAYMENT_HISTORY_BUTTON_LABEL import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_AMOUNT_DUE import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_BODY @@ -108,6 +107,7 @@ import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_BUTTON import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_TITLE import hedvig.resources.PAYMENTS_PROCESSING_PAYMENT import hedvig.resources.PAYMENTS_UPCOMING_PAYMENT +import hedvig.resources.PAYMENT_METHODS_TITLE import hedvig.resources.PAYOUT_ADD_PAYOUT_METHOD import hedvig.resources.PAYOUT_MISSING_INFO import hedvig.resources.PAYOUT_PAGE_HEADING @@ -131,7 +131,7 @@ internal fun PaymentsDestination( onDiscountClicked: () -> Unit, onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, - onMemberPaymentDetailsClicked: () -> Unit, + onPaymentMethodsClicked: () -> Unit, onChangeBankAccount: () -> Unit, onOpenManualCharge: () -> Unit, ) { @@ -144,7 +144,7 @@ internal fun PaymentsDestination( onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, onRetry = { viewModel.emit(Retry) }, - onPaymentDetailsClicked = onMemberPaymentDetailsClicked, + onPaymentMethodsClicked = onPaymentMethodsClicked, onOpenManualCharge = onOpenManualCharge, ) } @@ -157,7 +157,7 @@ private fun PaymentsScreen( onDiscountClicked: () -> Unit, onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, - onPaymentDetailsClicked: () -> Unit, + onPaymentMethodsClicked: () -> Unit, onOpenManualCharge: () -> Unit, onRetry: () -> Unit, ) { @@ -220,7 +220,7 @@ private fun PaymentsScreen( onDiscountClicked = onDiscountClicked, onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, - onPaymentDetailsClicked = onPaymentDetailsClicked, + onPaymentMethodsClicked = onPaymentMethodsClicked, onOpenManualCharge = onOpenManualCharge, ) Spacer(Modifier.height(16.dp)) @@ -246,7 +246,7 @@ private fun PaymentsContent( onDiscountClicked: () -> Unit, onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, - onPaymentDetailsClicked: () -> Unit, + onPaymentMethodsClicked: () -> Unit, onOpenManualCharge: () -> Unit, modifier: Modifier = Modifier, ) { @@ -356,7 +356,7 @@ private fun PaymentsContent( onDiscountClicked = onDiscountClicked, onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, - onPaymentDetailsClicked = onPaymentDetailsClicked, + onPaymentMethodsClicked = onPaymentMethodsClicked, showPayoutButton = (uiState as? Content)?.showPayoutButton == true, ) } @@ -429,7 +429,7 @@ private fun PaymentsListItems( onDiscountClicked: () -> Unit, onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, - onPaymentDetailsClicked: () -> Unit, + onPaymentMethodsClicked: () -> Unit, showPayoutButton: Boolean, ) { val listItemsSideSpacingModifier = Modifier @@ -490,16 +490,16 @@ private fun PaymentsListItems( HorizontalDivider(listItemsSideSpacingModifier) } - val showPaymentDetailsItem = when (uiState.memberType) { + val showPaymentMethodsItem = when (uiState.memberType) { MemberType.QASA_ONLY_MEMBER -> false MemberType.STANDARD_MEMBER, MemberType.STANDARD_TO_QASA_MEMBER, -> true } - if (showPaymentDetailsItem) { + if (showPaymentMethodsItem) { PaymentsListItem( - text = stringResource(Res.string.PAYMENTS_PAYMENT_DETAILS_INFO_TITLE), + text = stringResource(Res.string.PAYMENT_METHODS_TITLE), icon = { Icon( imageVector = HedvigIcons.Card, @@ -508,7 +508,7 @@ private fun PaymentsListItems( ) }, modifier = Modifier - .clickable(onClick = onPaymentDetailsClicked) + .clickable(onClick = onPaymentMethodsClicked) .then(listItemsSideSpacingModifier) .padding(vertical = 16.dp) .fillMaxWidth(), From b91cea09b1876a490e0dff207329921b16a339cd Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 8 Sep 2026 23:49:36 +0200 Subject: [PATCH 47/75] changes to payments navigation part 2 + mock! --- .../app/navigation/HedvigEntryProvider.kt | 2 + .../account/navigation/PayinAccountKey.kt | 16 +++ .../account/data/GetPayinAccountUseCase.kt | 21 ++- .../navigation/PayinAccountDestination.kt | 5 - .../account/navigation/PayinAccountEntries.kt | 14 +- .../PayinMethodDetailsDestination.kt | 52 ++++++- .../PayinMethodDetailsViewModel.kt | 79 +++++++++++ ...tPrimaryPayinMethodKeySerializationTest.kt | 12 +- app/feature/feature-payments/build.gradle.kts | 1 + .../android/feature/payments/PreviewData.kt | 3 + .../feature/payments/data/PaymentOverview.kt | 1 + .../payments/data/PrimaryPayinMethod.kt | 70 ++++++++++ .../payments/navigation/PaymentsEntries.kt | 5 + .../data/GetUpcomingPaymentUseCase.kt | 7 + .../ui/payments/PaymentsDestination.kt | 132 ++++++++++++++++-- .../payments/ui/payments/PaymentsPresenter.kt | 3 + 16 files changed, 391 insertions(+), 32 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt create mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PrimaryPayinMethod.kt diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index 215d78ee49..a5d05ff248 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -53,6 +53,7 @@ import com.hedvig.android.feature.movingflow.movingFlowEntries import com.hedvig.android.feature.onboarding.data.ResetOnboardingSeenUseCase import com.hedvig.android.feature.onboarding.navigation.onboardingEntries import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey +import com.hedvig.android.feature.payin.account.navigation.PayinMethodDetailsKey import com.hedvig.android.feature.payin.account.navigation.payinAccountEntries import com.hedvig.android.feature.payments.navigation.paymentsEntries import com.hedvig.android.feature.payoutaccount.navigation.PayoutAccountKey @@ -473,6 +474,7 @@ private fun EntryProviderScope.addPaymentsEntries( backstack = backstack, navigateToConnectPayment = navigateToConnectPayment, navigateToPayinAccount = navigateToPayinAccount, + navigateToPayinMethodDetails = { method -> backstack.add(PayinMethodDetailsKey(method)) }, navigateToPayoutAccount = navigateToPayoutAccount, openConversation = navigateToNewConversation, ) diff --git a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt index 8b47fa2db5..fe262c6f60 100644 --- a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt +++ b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt @@ -5,3 +5,19 @@ import kotlinx.serialization.Serializable @Serializable data object PayinAccountKey : HedvigNavKey + +/** + * Names one of the member's connected payin methods. A method is identified by its provider because a member can + * have at most one connected method per provider. + */ +@Serializable +enum class PayinMethodId { + Trustly, + Swish, + Invoice, +} + +@Serializable +data class PayinMethodDetailsKey( + val method: PayinMethodId, +) : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index ea9bfab06f..a1e634dc8c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -9,6 +9,7 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn @@ -79,11 +80,14 @@ internal class GetPayinAccountUseCase( } } } - logcat { "availablePayinMethods: before filter ${paymentMethods.availableMethods}" } - val availablePayinMethods = paymentMethods.availableMethods - .filter { it.supportsPayin } - .map { it.provider } - logcat { "availablePayinMethods: $availablePayinMethods" } + logcat { "availablePayinMethods: backend returned ${paymentMethods.availableMethods}" } + // TODO: the backend does not list Swish or Trustly as available payin methods yet, so the connect flow is + // driven off a mock. Restore the commented-out filter below and delete `availablePayinMethods` once it does. + // val availablePayinMethods = paymentMethods.availableMethods + // .filter { it.supportsPayin } + // .map { it.provider } + val availablePayinMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY) + logcat { "availablePayinMethods: using mocked $availablePayinMethods" } PayinAccountData( currentMethods = currentMethods, availablePayinMethods = availablePayinMethods, @@ -143,6 +147,13 @@ internal sealed interface PayinAccount { ) : PayinAccount } +internal val PayinAccount.id: PayinMethodId + get() = when (this) { + is PayinAccount.Trustly -> PayinMethodId.Trustly + is PayinAccount.SwishPayin -> PayinMethodId.Swish + is PayinAccount.Invoice -> PayinMethodId.Invoice + } + internal val PayinAccount.provider: MemberPaymentProvider get() = when (this) { is PayinAccount.Trustly -> MemberPaymentProvider.TRUSTLY diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 9ee6de09b4..64b6ebc78d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -19,8 +19,3 @@ internal data object SetupInvoicePayinKey : HedvigNavKey internal data class SelectPrimaryPayinMethodKey( val currentMethods: List, ) : HedvigNavKey - -@Serializable -internal data class PayinMethodDetailsKey( - val method: PayinAccount, -) : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 1f7007f8bd..2397738fdd 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -5,7 +5,10 @@ import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.id import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsDestination +import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsViewModel +import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsViewModelFactory import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewDestination import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewUiState import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverviewViewModel @@ -45,7 +48,7 @@ fun EntryProviderScope.payinAccountEntries( ) }, onPayinMethodClicked = dropUnlessResumed { method: PayinAccount -> - backstack.add(PayinMethodDetailsKey(method)) + backstack.add(PayinMethodDetailsKey(method.id)) }, onChoosePrimaryMethodClicked = dropUnlessResumed { val content = viewModel.uiState.value as? PayinAccountOverviewUiState.Content @@ -56,11 +59,14 @@ fun EntryProviderScope.payinAccountEntries( } entry { key -> - val method = key.method + val viewModel: PayinMethodDetailsViewModel = + assistedMetroViewModel { + create(key.method) + } PayinMethodDetailsDestination( - method = method, + viewModel = viewModel, navigateUp = backstack::navigateUp, - onChangeMethod = dropUnlessResumed { + onChangeMethod = dropUnlessResumed { method: PayinAccount -> when (method) { is PayinAccount.Trustly -> { backstack.popUpTo(inclusive = true) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt index e85e5e8075..fb83b06d3f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -6,14 +6,18 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigErrorSection +import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress import com.hedvig.android.design.system.hedvig.HedvigRedTextButton import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview @@ -41,6 +45,52 @@ import org.jetbrains.compose.resources.stringResource @Composable internal fun PayinMethodDetailsDestination( + viewModel: PayinMethodDetailsViewModel, + navigateUp: () -> Unit, + onChangeMethod: (PayinAccount) -> Unit, + onRemoveMethod: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + when (val state = uiState) { + PayinMethodDetailsUiState.Loading -> { + HedvigScaffold( + topAppBarText = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + HedvigFullScreenCenterAlignedProgress(Modifier.weight(1f)) + } + } + + PayinMethodDetailsUiState.Error -> { + HedvigScaffold( + topAppBarText = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + HedvigErrorSection( + onButtonClick = { viewModel.emit(PayinMethodDetailsEvent.Retry) }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .weight(1f), + ) + } + } + + is PayinMethodDetailsUiState.Content -> { + PayinMethodDetailsScreen( + method = state.method, + navigateUp = navigateUp, + onChangeMethod = { onChangeMethod(state.method) }, + onRemoveMethod = onRemoveMethod, + ) + } + } +} + +@Composable +private fun PayinMethodDetailsScreen( method: PayinAccount, navigateUp: () -> Unit, onChangeMethod: () -> Unit, @@ -169,7 +219,7 @@ private fun PreviewPayinMethodDetailsDestination( ) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - PayinMethodDetailsDestination( + PayinMethodDetailsScreen( method = method, navigateUp = {}, onChangeMethod = {}, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt new file mode 100644 index 0000000000..6ef994e560 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -0,0 +1,79 @@ +package com.hedvig.android.feature.payin.account.ui.methoddetails + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.di.ActivityRetainedScope +import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.id +import com.hedvig.android.feature.payin.account.navigation.PayinMethodId +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject + +@AssistedInject +@HedvigViewModel(ActivityRetainedScope::class) +internal class PayinMethodDetailsViewModel( + @Assisted methodId: PayinMethodId, + getPayinAccountUseCase: GetPayinAccountUseCase, +) : MoleculeViewModel( + initialState = PayinMethodDetailsUiState.Loading, + presenter = PayinMethodDetailsPresenter(methodId, getPayinAccountUseCase), + ) + +internal sealed interface PayinMethodDetailsEvent { + data object Retry : PayinMethodDetailsEvent +} + +internal sealed interface PayinMethodDetailsUiState { + data object Loading : PayinMethodDetailsUiState + + data object Error : PayinMethodDetailsUiState + + data class Content(val method: PayinAccount) : PayinMethodDetailsUiState +} + +internal class PayinMethodDetailsPresenter( + private val methodId: PayinMethodId, + private val getPayinAccountUseCase: GetPayinAccountUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: PayinMethodDetailsUiState, + ): PayinMethodDetailsUiState { + var loadIteration by remember { mutableIntStateOf(0) } + var uiState by remember { mutableStateOf(lastState) } + + LaunchedEffect(loadIteration) { + uiState = PayinMethodDetailsUiState.Loading + getPayinAccountUseCase.invoke().fold( + ifLeft = { uiState = PayinMethodDetailsUiState.Error }, + ifRight = { data -> + val method = data.currentMethods.firstOrNull { it.id == methodId } + // The method can be gone if it was disconnected on another device between listing and opening it. + uiState = if (method == null) { + PayinMethodDetailsUiState.Error + } else { + PayinMethodDetailsUiState.Content(method) + } + }, + ) + } + + CollectEvents { event -> + when (event) { + PayinMethodDetailsEvent.Retry -> loadIteration++ + } + } + + return uiState + } +} diff --git a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt index 5534d7caf7..8297a95604 100644 --- a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt +++ b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt @@ -39,15 +39,9 @@ internal class SelectPrimaryPayinMethodKeySerializationTest { } @Test - fun `payin method details key survives a serialization round trip for every account type`() { - val methods = listOf( - PayinAccount.Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), - PayinAccount.SwishPayin("0709901232", isPending = true, isDefault = false), - PayinAccount.Invoice(InvoiceDelivery.Mail, "a@b.com", isPending = false, isDefault = false), - ) - - for (method in methods) { - val key = PayinMethodDetailsKey(method) + fun `payin method details key survives a serialization round trip for every method id`() { + for (methodId in PayinMethodId.entries) { + val key = PayinMethodDetailsKey(methodId) val encoded = json.encodeToString(PolymorphicSerializer(HedvigNavKey::class), key) diff --git a/app/feature/feature-payments/build.gradle.kts b/app/feature/feature-payments/build.gradle.kts index 38e4fb97f0..d60c30b54b 100644 --- a/app/feature/feature-payments/build.gradle.kts +++ b/app/feature/feature-payments/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.designSystemHedvig) implementation(projects.featureFlags) implementation(projects.featureForeverNavigation) + implementation(projects.featurePayinAccountNavigation) implementation(projects.featurePaymentsNavigation) implementation(projects.languageCore) implementation(projects.languageData) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt index da7403e007..63a503a565 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt @@ -3,6 +3,7 @@ package com.hedvig.android.feature.payments import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.data.paying.member.MemberType +import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.feature.payments.data.Discount import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.MemberCharge @@ -11,6 +12,7 @@ import com.hedvig.android.feature.payments.data.MemberPaymentChargeMethod import com.hedvig.android.feature.payments.data.PaymentConnection import com.hedvig.android.feature.payments.data.PaymentOverview import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge +import com.hedvig.android.feature.payments.data.PrimaryPayinMethod import kotlinx.datetime.LocalDate internal val periodsPreviewData = listOf( @@ -280,6 +282,7 @@ internal val paymentOverViewPreviewData: PaymentOverview memberChargeShortInfo = memberChargeShortInfo, ongoingCharges = listOf(OngoingCharge("id", LocalDate.fromEpochDays(401), UiMoney(200.0, UiCurrencyCode.SEK))), paymentConnection = PaymentConnection.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), isManualChargeAllowed = ManualChargeToPrompt(UiMoney(200.0, UiCurrencyCode.SEK)), memberType = MemberType.STANDARD_MEMBER, ) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt index 3952ee0e73..5485cd6799 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt @@ -8,6 +8,7 @@ data class PaymentOverview( val memberChargeShortInfo: MemberChargeShortInfo?, val ongoingCharges: List, val paymentConnection: PaymentConnection, + val primaryPayinMethod: PrimaryPayinMethod?, val isManualChargeAllowed: ManualChargeToPrompt?, val memberType: MemberType, ) { diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PrimaryPayinMethod.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PrimaryPayinMethod.kt new file mode 100644 index 0000000000..ce0e4d95c2 --- /dev/null +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PrimaryPayinMethod.kt @@ -0,0 +1,70 @@ +package com.hedvig.android.feature.payments.data + +import com.hedvig.android.feature.payin.account.navigation.PayinMethodId +import octopus.fragment.MemberPaymentMethodFragment +import octopus.fragment.MemberPaymentMethodFragment.PaymentMethodBankAccountDetailsDetails +import octopus.fragment.MemberPaymentMethodFragment.PaymentMethodInvoiceDetailsDetails +import octopus.fragment.MemberPaymentMethodFragment.PaymentMethodSwishDetailsDetails +import octopus.type.MemberPaymentProvider +import octopus.type.PaymentMethodInvoiceDelivery + +/** + * The method the member is currently charged with, summarised for the payments overview. + * + * [descriptor] is the method's account in the member's own terms — the Swish number, the masked bank account, or + * the invoice delivery channel. Invoice has nothing else worth showing, so the row uses it as the title there and + * as the subtitle for the other providers. + */ +data class PrimaryPayinMethod( + val id: PayinMethodId, + val descriptor: String?, +) + +fun MemberPaymentMethodFragment.toPrimaryPayinMethod(): PrimaryPayinMethod? { + val id = when (provider) { + MemberPaymentProvider.TRUSTLY -> PayinMethodId.Trustly + MemberPaymentProvider.SWISH -> PayinMethodId.Swish + MemberPaymentProvider.INVOICE -> PayinMethodId.Invoice + MemberPaymentProvider.NORDEA, MemberPaymentProvider.UNKNOWN__ -> return null + } + return PrimaryPayinMethod(id = id, descriptor = details?.descriptor()) +} + +private fun MemberPaymentMethodFragment.Details.descriptor(): String? = when (this) { + is PaymentMethodSwishDetailsDetails -> { + formatSwishPhoneNumber(phoneNumber) + } + + is PaymentMethodBankAccountDetailsDetails -> { + val lastFour = account.substringAfter('-', "").takeLast(4).takeIf { it.isNotBlank() } + if (lastFour == null) bank else "$bank ···· $lastFour" + } + + is PaymentMethodInvoiceDetailsDetails -> { + when (delivery) { + // TODO: Add "Kivra" / "Kivra" to Lokalise + PaymentMethodInvoiceDelivery.KIVRA -> "Kivra" + + // TODO: Add "Email" / "E-post" to Lokalise + PaymentMethodInvoiceDelivery.MAIL -> "Email" + + PaymentMethodInvoiceDelivery.UNKNOWN__ -> null + } + } + + else -> { + null + } +} + +private fun formatSwishPhoneNumber(phoneNumber: String): String { + val digits = phoneNumber.take(15) + val sb = StringBuilder() + for (i in digits.indices) { + sb.append(digits[i]) + if (i in setOf(2, 5, 7)) { + sb.append("-") + } + } + return sb.toString() +} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt index a8a0396c17..889b73812e 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.feature.forever.navigation.InviteFriendsKey +import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.feature.payments.ui.details.PaymentDetailExplanationContent import com.hedvig.android.feature.payments.ui.details.PaymentDetailsDestination import com.hedvig.android.feature.payments.ui.details.PaymentDetailsViewModel @@ -29,6 +30,7 @@ fun EntryProviderScope.paymentsEntries( backstack: Backstack, navigateToConnectPayment: () -> Unit, navigateToPayinAccount: () -> Unit, + navigateToPayinMethodDetails: (PayinMethodId) -> Unit, navigateToPayoutAccount: () -> Unit, openConversation: () -> Unit, ) { @@ -48,6 +50,9 @@ fun EntryProviderScope.paymentsEntries( backstack.add(PaymentDetailsKey(id)) }, onPaymentMethodsClicked = dropUnlessResumed { navigateToPayinAccount() }, + onPrimaryPayinMethodClicked = dropUnlessResumed { method: PayinMethodId -> + navigateToPayinMethodDetails(method) + }, onOpenManualCharge = { backstack.add(ManualChargeKey) }, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt index b93d061726..34fdc3a2b8 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt @@ -20,6 +20,7 @@ import com.hedvig.android.feature.payments.data.PaymentConnection import com.hedvig.android.feature.payments.data.PaymentOverview import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge import com.hedvig.android.feature.payments.data.toFailedCharge +import com.hedvig.android.feature.payments.data.toPrimaryPayinMethod import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import kotlin.time.Clock @@ -28,6 +29,7 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import octopus.UpcomingPaymentQuery import octopus.fragment.MemberChargeFragment +import octopus.fragment.MemberPaymentMethodFragment import octopus.type.MemberChargeStatus import octopus.type.MemberPaymentMethodStatus @@ -72,6 +74,10 @@ internal data class GetUpcomingPaymentUseCaseImpl( val id = it.id ?: return@mapNotNull null OngoingCharge(id, it.date, UiMoney.fromMoneyFragment(it.net)) }, + primaryPayinMethod = result.currentMember.paymentMethods.let { paymentMethods -> + paymentMethods.defaultPayinMethod as MemberPaymentMethodFragment? + ?: paymentMethods.payinMethods.find { it.isDefault } + }?.toPrimaryPayinMethod(), paymentConnection = run { val paymentMethods = result.currentMember.paymentMethods val payinMethod = paymentMethods.defaultPayinMethod @@ -162,6 +168,7 @@ internal class GetUpcomingPaymentUseCaseDemo( ), emptyList(), PaymentConnection.Unknown, + primaryPayinMethod = null, isManualChargeAllowed = null, memberType = MemberType.STANDARD_MEMBER, ).right() diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt index d827568488..f544b3a516 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt @@ -3,6 +3,7 @@ package com.hedvig.android.feature.payments.ui.payments import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.expandVertically +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -16,6 +17,7 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing @@ -72,13 +74,18 @@ import com.hedvig.android.design.system.hedvig.icon.ChevronRight import com.hedvig.android.design.system.hedvig.icon.Clock import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.PaymentOutline +import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.WarningFilled +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.placeholder.hedvigPlaceholder import com.hedvig.android.design.system.hedvig.placeholder.shimmer import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter import com.hedvig.android.design.system.hedvig.rememberHedvigMonthDateTimeFormatter +import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge +import com.hedvig.android.feature.payments.data.PrimaryPayinMethod import com.hedvig.android.feature.payments.ui.payments.PaymentsEvent.Retry import com.hedvig.android.feature.payments.ui.payments.PaymentsUiState.Content import com.hedvig.android.feature.payments.ui.payments.PaymentsUiState.Content.ConnectedPaymentInfo @@ -96,11 +103,14 @@ import com.hedvig.android.pullrefresh.PullRefreshIndicator import com.hedvig.android.pullrefresh.pullRefresh import com.hedvig.android.pullrefresh.rememberPullRefreshState import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE +import hedvig.resources.PAYMENTS_BANK_LABEL import hedvig.resources.PAYMENTS_DISCOUNTS_SECTION_TITLE +import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYMENTS_IN_PROGRESS import hedvig.resources.PAYMENTS_MISSED_PAYMENT import hedvig.resources.PAYMENTS_NO_PAYMENTS_IN_PROGRESS import hedvig.resources.PAYMENTS_PAYMENT_HISTORY_BUTTON_LABEL +import hedvig.resources.PAYMENTS_PAYMENT_METHOD import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_AMOUNT_DUE import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_BODY import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_BUTTON @@ -117,6 +127,7 @@ import hedvig.resources.Res import hedvig.resources.TAB_PAYMENTS_TITLE import hedvig.resources.info_card_missing_payment_body import hedvig.resources.info_card_missing_payment_missing_payments_body +import hedvig.resources.swish import kotlin.time.Clock.System import kotlin.time.Duration.Companion.days import kotlinx.datetime.LocalDate @@ -132,6 +143,7 @@ internal fun PaymentsDestination( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, + onPrimaryPayinMethodClicked: (PayinMethodId) -> Unit, onChangeBankAccount: () -> Unit, onOpenManualCharge: () -> Unit, ) { @@ -145,6 +157,7 @@ internal fun PaymentsDestination( onPayoutAccountClicked = onPayoutAccountClicked, onRetry = { viewModel.emit(Retry) }, onPaymentMethodsClicked = onPaymentMethodsClicked, + onPrimaryPayinMethodClicked = onPrimaryPayinMethodClicked, onOpenManualCharge = onOpenManualCharge, ) } @@ -158,6 +171,7 @@ private fun PaymentsScreen( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, + onPrimaryPayinMethodClicked: (PayinMethodId) -> Unit, onOpenManualCharge: () -> Unit, onRetry: () -> Unit, ) { @@ -221,6 +235,7 @@ private fun PaymentsScreen( onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, onPaymentMethodsClicked = onPaymentMethodsClicked, + onPrimaryPayinMethodClicked = onPrimaryPayinMethodClicked, onOpenManualCharge = onOpenManualCharge, ) Spacer(Modifier.height(16.dp)) @@ -247,6 +262,7 @@ private fun PaymentsContent( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, + onPrimaryPayinMethodClicked: (PayinMethodId) -> Unit, onOpenManualCharge: () -> Unit, modifier: Modifier = Modifier, ) { @@ -351,6 +367,15 @@ private fun PaymentsContent( } } + val primaryPayinMethod = (uiState as? Content)?.primaryPayinMethod + if (primaryPayinMethod != null) { + PrimaryPayinMethodSection( + method = primaryPayinMethod, + onClick = { onPrimaryPayinMethodClicked(primaryPayinMethod.id) }, + ) + Spacer(Modifier.height(8.dp)) + } + PaymentsListItems( uiState, onDiscountClicked = onDiscountClicked, @@ -423,6 +448,81 @@ private fun UpcomingPaymentInfoCard(upcomingPaymentInfo: UpcomingPaymentInfo?, m } } +@Composable +private fun PrimaryPayinMethodSection(method: PrimaryPayinMethod, onClick: () -> Unit) { + val sideSpacing = Modifier + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) + Column(Modifier.fillMaxWidth()) { + HedvigText( + text = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + modifier = sideSpacing.padding(vertical = 8.dp), + ) + HedvigCard( + onClick = onClick, + shape = HedvigTheme.shapes.cornerLarge, + modifier = sideSpacing.fillMaxWidth(), + ) { + Row( + modifier = Modifier + .heightIn(min = 64.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + PayinMethodPillow(method.id) + Spacer(Modifier.width(2.dp)) + Column( + Modifier + .weight(1f) + .padding(vertical = 8.dp), + ) { + HedvigText( + text = when (method.id) { + PayinMethodId.Trustly -> stringResource(Res.string.PAYMENTS_BANK_LABEL) + PayinMethodId.Swish -> stringResource(Res.string.swish) + PayinMethodId.Invoice -> method.descriptor ?: stringResource(Res.string.PAYMENTS_INVOICE) + }, + ) + val subtitle = method.descriptor.takeIf { method.id != PayinMethodId.Invoice } + if (subtitle != null) { + HedvigText( + text = subtitle, + style = HedvigTheme.typography.finePrint, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + } + Icon( + imageVector = HedvigIcons.ChevronRight, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + } + } + } +} + +/** Mirrors the tile the payin-account feature draws; the two features cannot share a composable. */ +@Composable +private fun PayinMethodPillow(id: PayinMethodId) { + val onDarkTile = id == PayinMethodId.Trustly + Surface( + shape = HedvigTheme.shapes.cornerSmall, + color = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + modifier = Modifier.size(40.dp), + ) { + Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { + when (id) { + PayinMethodId.Trustly -> Icon(HedvigIcons.Trustly, null, Modifier.size(28.dp)) + PayinMethodId.Swish -> Image(HedvigIcons.Swish, null, Modifier.size(28.dp)) + PayinMethodId.Invoice -> Image(HedvigIcons.Kivra, null, Modifier.size(28.dp)) + } + } + } +} + @Composable private fun PaymentsListItems( uiState: PaymentsUiState, @@ -789,14 +889,15 @@ private fun PreviewPaymentScreen( Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { PaymentsScreen( uiState = uiState, - { _ -> }, - {}, - {}, - {}, - {}, - {}, - {}, - {}, + onUpcomingPaymentClicked = {}, + onChangeBankAccount = {}, + onDiscountClicked = {}, + onPaymentHistoryClicked = {}, + onPayoutAccountClicked = {}, + onPaymentMethodsClicked = {}, + onPrimaryPayinMethodClicked = {}, + onOpenManualCharge = {}, + onRetry = {}, ) } } @@ -813,6 +914,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = listOf(OngoingCharge("id", LocalDate.fromEpochDays(401), UiMoney(200.0, SEK))), connectedPaymentInfo = ConnectedPaymentInfo.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = true, memberType = MemberType.STANDARD_MEMBER, ), @@ -828,6 +930,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -845,6 +948,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( null, ), + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), @@ -860,6 +964,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = InProgress, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -881,6 +986,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ), ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -896,6 +1002,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Pending, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -917,6 +1024,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( null, ), + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -934,6 +1042,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( null, ), + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -955,6 +1064,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( dueDateToConnect = System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -976,6 +1086,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), @@ -987,6 +1098,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), @@ -998,6 +1110,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), @@ -1009,6 +1122,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.QASA_ONLY_MEMBER, ), @@ -1020,6 +1134,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = false, memberType = MemberType.QASA_ONLY_MEMBER, ), @@ -1035,6 +1150,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< upcomingPaymentInfo = UpcomingPaymentInfo.NoInfo, ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, + primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), showPayoutButton = true, memberType = MemberType.STANDARD_MEMBER, ), diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt index c37975999b..c6f5031fc6 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt @@ -20,6 +20,7 @@ import com.hedvig.android.feature.payments.data.PaymentConnection.Pending import com.hedvig.android.feature.payments.data.PaymentConnection.Unknown import com.hedvig.android.feature.payments.data.PaymentOverview import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge +import com.hedvig.android.feature.payments.data.PrimaryPayinMethod import com.hedvig.android.feature.payments.overview.data.GetShouldShowPayoutUseCase import com.hedvig.android.feature.payments.overview.data.GetUpcomingPaymentUseCase import com.hedvig.android.feature.payments.ui.payments.PaymentsUiState.Content.ConnectedPaymentInfo @@ -83,6 +84,7 @@ internal class PaymentsPresenter( }, ongoingCharges = paymentOverview.ongoingCharges, connectedPaymentInfo = paymentOverview.paymentConnection.toConnectedPaymentInfo(), + primaryPayinMethod = paymentOverview.primaryPayinMethod, showPayoutButton = shouldShowPayout, memberType = paymentOverview.memberType, ) @@ -145,6 +147,7 @@ internal sealed interface PaymentsUiState { val upcomingPaymentInfo: UpcomingPaymentInfo, val ongoingCharges: List, val connectedPaymentInfo: ConnectedPaymentInfo, + val primaryPayinMethod: PrimaryPayinMethod?, val showPayoutButton: Boolean, val memberType: MemberType, ) : PaymentsUiState { From 8cd17e4b8d96b595096b4951cab516b31b12373f Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 10:34:50 +0200 Subject: [PATCH 48/75] changes to swish icon --- .../SelectPrimaryPayinMethodDestination.kt | 82 ++++++++++++++----- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index f5c91041aa..93dbac56c4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -1,5 +1,6 @@ package com.hedvig.android.feature.payin.account.ui.primary +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -10,12 +11,15 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.EmptyContentDescription @@ -31,7 +35,10 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.IconResource +import com.hedvig.android.design.system.hedvig.IconResource.Vector import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Error +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId @@ -44,15 +51,21 @@ import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.PayinAccount.Invoice +import com.hedvig.android.feature.payin.account.data.PayinAccount.SwishPayin +import com.hedvig.android.feature.payin.account.data.PayinAccount.Trustly import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel import com.hedvig.android.feature.payin.account.ui.components.payinMethodSubtitle import com.hedvig.android.feature.payin.account.ui.components.payinMethodTitle +import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod +import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.SelectMethod import hedvig.resources.PAYMENT_PRIMARY_CONFIRM_TITLE import hedvig.resources.PAYMENT_PRIMARY_SUBTITLE import hedvig.resources.PAYMENT_PRIMARY_TITLE import hedvig.resources.Res +import hedvig.resources.Res.string import hedvig.resources.general_cancel_button import hedvig.resources.general_continue_button import org.jetbrains.compose.resources.stringResource @@ -71,8 +84,8 @@ internal fun SelectPrimaryPayinMethodDestination( } SelectPrimaryPayinMethodScreen( uiState = uiState, - onMethodSelected = { viewModel.emit(SelectPrimaryPayinMethodEvent.SelectMethod(it)) }, - onConfirm = { viewModel.emit(SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod) }, + onMethodSelected = { viewModel.emit(SelectMethod(it)) }, + onConfirm = { viewModel.emit(ConfirmSelectedMethod) }, navigateUp = navigateUp, navigateBack = navigateBack, ) @@ -100,16 +113,17 @@ private fun SelectPrimaryPayinMethodScreen( horizontalAlignment = Alignment.CenterHorizontally, ) { HedvigText( - text = stringResource(Res.string.PAYMENT_PRIMARY_TITLE), + text = stringResource(string.PAYMENT_PRIMARY_TITLE), textAlign = TextAlign.Center, ) HedvigText( - text = stringResource(Res.string.PAYMENT_PRIMARY_SUBTITLE), + text = stringResource(string.PAYMENT_PRIMARY_SUBTITLE), color = colorScheme.textSecondary, textAlign = TextAlign.Center, ) } Spacer(Modifier.height(48.dp)) + Spacer(Modifier.weight(1f)) PayinMethodHandoverIllustration( method = uiState.selectedMethod, modifier = Modifier.align(Alignment.CenterHorizontally), @@ -129,7 +143,7 @@ private fun SelectPrimaryPayinMethodScreen( Spacer(Modifier.height(8.dp)) HedvigNotificationCard( message = uiState.errorMessage, - priority = NotificationPriority.Error, + priority = Error, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -137,7 +151,7 @@ private fun SelectPrimaryPayinMethodScreen( } Spacer(Modifier.height(16.dp)) HedvigButton( - text = stringResource(Res.string.general_continue_button), + text = stringResource(string.general_continue_button), onClick = { val method = uiState.selectedMethod if (method != null) confirmationSheetState.show(method) @@ -149,7 +163,7 @@ private fun SelectPrimaryPayinMethodScreen( ) Spacer(Modifier.height(8.dp)) HedvigTextButton( - text = stringResource(Res.string.general_cancel_button), + text = stringResource(string.general_cancel_button), onClick = navigateBack, modifier = Modifier .fillMaxWidth() @@ -172,7 +186,7 @@ private fun ConfirmPrimaryPayinMethodBottomSheet( ) { HedvigBottomSheet(sheetState) { method -> HedvigText( - text = stringResource(Res.string.PAYMENT_PRIMARY_CONFIRM_TITLE), + text = stringResource(string.PAYMENT_PRIMARY_CONFIRM_TITLE), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), ) @@ -183,7 +197,7 @@ private fun ConfirmPrimaryPayinMethodBottomSheet( // to Lokalise message = "Your next payment will be drawn from ${payinMethodTitle(method)}. " + "Claims payouts will also be sent to this account.", - priority = NotificationPriority.Info, + priority = Info, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) @@ -200,7 +214,7 @@ private fun ConfirmPrimaryPayinMethodBottomSheet( } Spacer(Modifier.height(16.dp)) HedvigButton( - text = stringResource(Res.string.general_continue_button), + text = stringResource(string.general_continue_button), onClick = onConfirm, enabled = !isConfirming, isLoading = isConfirming, @@ -208,7 +222,7 @@ private fun ConfirmPrimaryPayinMethodBottomSheet( ) Spacer(Modifier.height(8.dp)) HedvigTextButton( - text = stringResource(Res.string.general_cancel_button), + text = stringResource(string.general_cancel_button), onClick = { sheetState.dismiss() }, modifier = Modifier.fillMaxWidth(), ) @@ -233,15 +247,20 @@ private fun PayinMethodHandoverIllustration(method: PayinAccount?, modifier: Mod ) { Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { when (method) { - is PayinAccount.Trustly -> { + is Trustly -> { Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) } - is PayinAccount.SwishPayin -> { - Icon(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(39.dp)) + is SwishPayin -> { + Image( + imageVector = HedvigIcons.Swish, + contentDescription = EmptyContentDescription, + modifier = Modifier + .size((39.0).dp), + ) } - is PayinAccount.Invoice -> { + is Invoice -> { Icon(HedvigIcons.Kivra, EmptyContentDescription, Modifier.size(39.dp)) } @@ -277,23 +296,25 @@ private fun PayinAccount.toRadioOption(): RadioOption = RadioOption( text = payinMethodTitle(this), label = payinMethodSubtitle(this), iconResource = when (this) { - is PayinAccount.Trustly -> IconResource.Vector(HedvigIcons.Trustly) - is PayinAccount.SwishPayin -> IconResource.Vector(HedvigIcons.Swish) - is PayinAccount.Invoice -> IconResource.Vector(HedvigIcons.Kivra) + is Trustly -> Vector(HedvigIcons.Trustly) + is SwishPayin -> Vector(HedvigIcons.Swish) + is Invoice -> Vector(HedvigIcons.Kivra) }, ) @Composable @HedvigPreview -private fun PreviewSelectPrimaryPayinMethodScreen() { +private fun PreviewSelectPrimaryPayinMethodScreen( + @PreviewParameter (TravelAddonTriageStateProvider::class) selectedMethod: PayinAccount?, +) { HedvigTheme { Surface(color = colorScheme.backgroundPrimary) { val methods = listOf( - PayinAccount.Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), - PayinAccount.SwishPayin("0709901232", isPending = false, isDefault = false), + Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), + SwishPayin("0709901232", isPending = false, isDefault = false), ) SelectPrimaryPayinMethodScreen( - uiState = SelectPrimaryPayinMethodUiState(methods = methods, selectedMethod = methods[1]), + uiState = SelectPrimaryPayinMethodUiState(methods = methods, selectedMethod = selectedMethod), onMethodSelected = {}, onConfirm = {}, navigateUp = {}, @@ -302,3 +323,20 @@ private fun PreviewSelectPrimaryPayinMethodScreen() { } } } + +internal class TravelAddonTriageStateProvider : + CollectionPreviewParameterProvider( + listOf( + null, + SwishPayin( + "072649872", false, true + ), + Trustly( + "2342", "7793784", + bankName = "Swedbank", + isPending = false, + isDefault = false + ) + ), + ) + From d064f45253c5102612ac3d3c3570c7e60bf7622c Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 11:29:48 +0200 Subject: [PATCH 49/75] changes select primary UI --- .../PayinMethodHandoverIllustration.kt | 140 +++++++++++ .../SelectPrimaryPayinMethodDestination.kt | 236 +++++++++--------- 2 files changed, 259 insertions(+), 117 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt new file mode 100644 index 0000000000..5fa6e13547 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt @@ -0,0 +1,140 @@ +package com.hedvig.android.feature.payin.account.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigPreview +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.ThreeDotsLoading +import com.hedvig.android.design.system.hedvig.hedvigDropShadow +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.HelipadOutline +import com.hedvig.android.design.system.hedvig.icon.Plus +import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.payin.account.data.InvoiceDelivery +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.PayinAccount.Invoice +import com.hedvig.android.feature.payin.account.data.PayinAccount.SwishPayin +import com.hedvig.android.feature.payin.account.data.PayinAccount.Trustly + +@Composable +internal fun PayinMethodHandoverIllustration(method: PayinAccount?, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.backgroundPrimary, + contentColor = colorScheme.fillPrimary, + border = colorScheme.borderSecondary, + modifier = Modifier.size(74.dp) + .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge), + ) { + Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { + when (method) { + is Trustly -> { + Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) + } + + is SwishPayin -> { + Image( + imageVector = HedvigIcons.Swish, + contentDescription = EmptyContentDescription, + modifier = Modifier + .size((39.0).dp), + ) + } + + is Invoice -> { + Image( + imageVector = HedvigIcons.Kivra, + contentDescription = EmptyContentDescription, + modifier = Modifier + .size((39.0).dp), + ) + } + + null -> { + Icon( + HedvigIcons.Plus, + EmptyContentDescription, + Modifier.size(39.dp), + tint = HedvigTheme.colorScheme.fillSecondary, + ) + } + } + } + } + ThreeDotsLoading() + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.fillBlack, + contentColor = colorScheme.fillWhite, + border = colorScheme.borderPrimary, + modifier = Modifier.size(74.dp), + ) { + Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { + Icon( + HedvigIcons.HelipadOutline, + EmptyContentDescription, + Modifier.size(65.dp), + ) + } + } + } +} + +@Composable +@HedvigPreview +private fun PreviewSelectPrimaryPayinMethodScreen( + @PreviewParameter(SelectedMethodIndexProvider::class) method: PayinAccount?, +) { + HedvigTheme { + Surface(color = colorScheme.backgroundPrimary) { + PayinMethodHandoverIllustration(method, + Modifier.padding(16.dp)) + } + } +} + +private class SelectedMethodIndexProvider : + CollectionPreviewParameterProvider( + listOf( + null, + Trustly( + "8327", + "91234124", + "Swedbank", + isPending = false, + isDefault = false, + ), + SwishPayin( + "0709901232", + isPending = false, + isDefault = true, + ), + Invoice( + delivery = InvoiceDelivery.Kivra, + email = null, + isPending = false, + isDefault = false, + ), + ), + ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index 93dbac56c4..4dfd788a4f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -23,38 +22,42 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.EmptyContentDescription -import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.IconResource import com.hedvig.android.design.system.hedvig.IconResource.Vector -import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Error import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.ThreeDotsLoading import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.HelipadFilled +import com.hedvig.android.design.system.hedvig.icon.HelipadOutline +import com.hedvig.android.design.system.hedvig.icon.Plus import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.android.feature.payin.account.data.InvoiceDelivery import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.PayinAccount.Invoice import com.hedvig.android.feature.payin.account.data.PayinAccount.SwishPayin import com.hedvig.android.feature.payin.account.data.PayinAccount.Trustly import com.hedvig.android.feature.payin.account.data.provider +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel import com.hedvig.android.feature.payin.account.ui.components.payinMethodSubtitle @@ -68,6 +71,8 @@ import hedvig.resources.Res import hedvig.resources.Res.string import hedvig.resources.general_cancel_button import hedvig.resources.general_continue_button +import hedvig.resources.pillow_new_680 +import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @Composable @@ -131,7 +136,12 @@ private fun SelectPrimaryPayinMethodScreen( Spacer(Modifier.weight(1f)) Spacer(Modifier.height(48.dp)) RadioGroup( - options = uiState.methods.map { it.toRadioOption() }, + options = uiState.methods + .sortedByDescending { it.isDefault } + .map { it.toRadioOption() }, + disabledOptions = uiState.methods + .filter { it.isDefault } + .map { it.toRadioOption().id }, selectedOption = uiState.selectedMethod?.let { RadioOptionId(it.provider.rawValue) }, onRadioOptionSelected = { id -> val method = uiState.methods.firstOrNull { it.provider.rawValue == id.id } @@ -185,111 +195,68 @@ private fun ConfirmPrimaryPayinMethodBottomSheet( onConfirm: () -> Unit, ) { HedvigBottomSheet(sheetState) { method -> - HedvigText( - text = stringResource(string.PAYMENT_PRIMARY_CONFIRM_TITLE), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(24.dp)) - HedvigNotificationCard( - // TODO: Add "Your next payment will be drawn from {method}. Claims payouts will also be sent to this - // account." / "Din nästa betalning dras från {method}. Skadeutbetalningar skickas också till detta konto." - // to Lokalise - message = "Your next payment will be drawn from ${payinMethodTitle(method)}. " + - "Claims payouts will also be sent to this account.", - priority = Info, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(16.dp)) - Surface( - shape = HedvigTheme.shapes.cornerLarge, - color = colorScheme.surfacePrimary, - modifier = Modifier.fillMaxWidth(), - ) { - PayinMethodRow( - method = method, - modifier = Modifier.fillMaxWidth(), - endSlot = { PrimaryMethodLabel() }, - ) - } - Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(string.general_continue_button), - onClick = onConfirm, - enabled = !isConfirming, - isLoading = isConfirming, - modifier = Modifier.fillMaxWidth(), + ConfirmPrimaryPayinMethodBottomSheetContent( + method = method, + isConfirming = isConfirming, + onConfirm = onConfirm, + onDismiss = { sheetState.dismiss() }, ) - Spacer(Modifier.height(8.dp)) - HedvigTextButton( - text = stringResource(string.general_cancel_button), - onClick = { sheetState.dismiss() }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(16.dp)) } } -/** The chosen method handing over to Hedvig: the method's mark, a run of dots, then the Hedvig mark. */ @Composable -private fun PayinMethodHandoverIllustration(method: PayinAccount?, modifier: Modifier = Modifier) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), +private fun ConfirmPrimaryPayinMethodBottomSheetContent( + method: PayinAccount, + isConfirming: Boolean, + onConfirm: () -> Unit, + onDismiss: () -> Unit, +) { + HedvigText( + text = stringResource(string.PAYMENT_PRIMARY_CONFIRM_TITLE), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(24.dp)) + HedvigNotificationCard( + // TODO: Add "Your next payment will be drawn from {method}. Claims payouts will also be sent to this + // account." / "Din nästa betalning dras från {method}. Skadeutbetalningar skickas också till detta konto." + // to Lokalise + message = "Your next payment will be drawn from ${payinMethodTitle(method)}. " + + "Claims payouts will also be sent to this account.", + priority = Info, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) + Surface( + shape = HedvigTheme.shapes.cornerLarge, + color = colorScheme.surfacePrimary, + modifier = Modifier.fillMaxWidth(), ) { - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.fillWhite, - contentColor = colorScheme.fillBlack, - border = colorScheme.borderSecondary, - modifier = Modifier.size(74.dp), - ) { - Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { - when (method) { - is Trustly -> { - Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) - } - - is SwishPayin -> { - Image( - imageVector = HedvigIcons.Swish, - contentDescription = EmptyContentDescription, - modifier = Modifier - .size((39.0).dp), - ) - } - - is Invoice -> { - Icon(HedvigIcons.Kivra, EmptyContentDescription, Modifier.size(39.dp)) - } - - null -> {} - } - } - } - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - repeat(3) { index -> - Surface( - shape = HedvigTheme.shapes.cornerFull, - color = if (index == 0) colorScheme.fillPrimary else colorScheme.borderSecondary, - modifier = Modifier.size(6.dp), - ) {} - } - } - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.fillBlack, - contentColor = colorScheme.fillWhite, - modifier = Modifier.size(74.dp), - ) { - Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { - Icon(HedvigIcons.HelipadFilled, EmptyContentDescription, Modifier.size(39.dp)) - } - } + PayinMethodRow( + method = method, + modifier = Modifier.fillMaxWidth(), + endSlot = { PrimaryMethodLabel() }, + ) } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(string.general_continue_button), + onClick = onConfirm, + enabled = !isConfirming, + isLoading = isConfirming, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(string.general_cancel_button), + onClick = onDismiss, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) } + + @Composable private fun PayinAccount.toRadioOption(): RadioOption = RadioOption( id = RadioOptionId(provider.rawValue), @@ -302,19 +269,61 @@ private fun PayinAccount.toRadioOption(): RadioOption = RadioOption( }, ) +@Composable +@HedvigShortMultiScreenPreview +private fun ConfirmPrimaryPayinMethodBottomSheetPreview() { + HedvigTheme { + Surface(color = colorScheme.backgroundPrimary) { + Column( + Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) { + ConfirmPrimaryPayinMethodBottomSheetContent( + SwishPayin( + "0709901232", + isPending = false, + isDefault = false, + ), + isConfirming = false, + onConfirm = {}, + onDismiss = {}, + ) + } + } + } +} + @Composable @HedvigPreview private fun PreviewSelectPrimaryPayinMethodScreen( - @PreviewParameter (TravelAddonTriageStateProvider::class) selectedMethod: PayinAccount?, + @PreviewParameter(SelectedMethodIndexProvider::class) selectedMethod: Int?, ) { HedvigTheme { Surface(color = colorScheme.backgroundPrimary) { val methods = listOf( - Trustly("8327", "91234124", "Swedbank", isPending = false, isDefault = true), - SwishPayin("0709901232", isPending = false, isDefault = false), + Trustly( + "8327", + "91234124", + "Swedbank", + isPending = false, + isDefault = false, + ), + SwishPayin( + "0709901232", + isPending = false, + isDefault = true, + ), + Invoice( + delivery = InvoiceDelivery.Kivra, + email = null, + isPending = false, + isDefault = false, + ), ) SelectPrimaryPayinMethodScreen( - uiState = SelectPrimaryPayinMethodUiState(methods = methods, selectedMethod = selectedMethod), + uiState = SelectPrimaryPayinMethodUiState( + methods = methods, + selectedMethod = selectedMethod?.let { methods[it] }, + ), onMethodSelected = {}, onConfirm = {}, navigateUp = {}, @@ -324,19 +333,12 @@ private fun PreviewSelectPrimaryPayinMethodScreen( } } -internal class TravelAddonTriageStateProvider : - CollectionPreviewParameterProvider( +private class SelectedMethodIndexProvider : + CollectionPreviewParameterProvider( listOf( null, - SwishPayin( - "072649872", false, true - ), - Trustly( - "2342", "7793784", - bankName = "Swedbank", - isPending = false, - isDefault = false - ) + 0, + 1, + 2, ), ) - From c0786b840edb10217b2d75fb2f65d7d01a97db8c Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 11:54:47 +0200 Subject: [PATCH 50/75] changes connect payin UI --- .../account/navigation/PayinAccountEntries.kt | 12 +- .../PayinMethodHandoverIllustration.kt | 159 +++++++++--------- .../SelectPrimaryPayinMethodDestination.kt | 34 ++-- ...on.kt => ConnectPayinMethodDestination.kt} | 24 ++- ...odel.kt => ConnectPayinMethodViewModel.kt} | 14 +- 5 files changed, 117 insertions(+), 126 deletions(-) rename app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/{SelectPayinMethodDestination.kt => ConnectPayinMethodDestination.kt} (90%) rename app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/{SelectPayinMethodViewModel.kt => ConnectPayinMethodViewModel.kt} (82%) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 2397738fdd..44c9ccee92 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -15,9 +15,9 @@ import com.hedvig.android.feature.payin.account.ui.overview.PayinAccountOverview import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodDestination import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodViewModel import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodViewModelFactory -import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodDestination -import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodViewModel -import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodViewModelFactory +import com.hedvig.android.feature.payin.account.ui.selectmethod.ConnectPayinMethodDestination +import com.hedvig.android.feature.payin.account.ui.selectmethod.ConnectPayinMethodViewModel +import com.hedvig.android.feature.payin.account.ui.selectmethod.ConnectPayinMethodViewModelFactory import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinDestination import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinDestination @@ -101,11 +101,11 @@ fun EntryProviderScope.payinAccountEntries( } entry { key -> - val viewModel: SelectPayinMethodViewModel = - assistedMetroViewModel { + val viewModel: ConnectPayinMethodViewModel = + assistedMetroViewModel { create(key.availableProviders) } - SelectPayinMethodDestination( + ConnectPayinMethodDestination( viewModel = viewModel, onTrustlySelected = dropUnlessResumed { backstack.popUpTo(inclusive = true) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt index 5fa6e13547..bc5c57f88a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt @@ -3,7 +3,10 @@ package com.hedvig.android.feature.payin.account.ui.components import androidx.compose.foundation.Image 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.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable @@ -26,115 +29,105 @@ import com.hedvig.android.design.system.hedvig.icon.Plus import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish -import com.hedvig.android.feature.payin.account.data.InvoiceDelivery -import com.hedvig.android.feature.payin.account.data.PayinAccount -import com.hedvig.android.feature.payin.account.data.PayinAccount.Invoice -import com.hedvig.android.feature.payin.account.data.PayinAccount.SwishPayin -import com.hedvig.android.feature.payin.account.data.PayinAccount.Trustly +import octopus.type.MemberPaymentProvider @Composable -internal fun PayinMethodHandoverIllustration(method: PayinAccount?, modifier: Modifier = Modifier) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.backgroundPrimary, - contentColor = colorScheme.fillPrimary, - border = colorScheme.borderSecondary, - modifier = Modifier.size(74.dp) - .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge), +internal fun PayinMethodHandoverIllustration(provider: MemberPaymentProvider?, modifier: Modifier = Modifier) { + Column(modifier) { + Spacer(Modifier.height(48.dp)) + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { - when (method) { - is Trustly -> { - Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) - } + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.backgroundPrimary, + contentColor = colorScheme.fillPrimary, + border = colorScheme.borderSecondary, + modifier = Modifier.size(74.dp) + .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge), + ) { + Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> { + Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) + } - is SwishPayin -> { - Image( - imageVector = HedvigIcons.Swish, - contentDescription = EmptyContentDescription, - modifier = Modifier - .size((39.0).dp), - ) - } + MemberPaymentProvider.SWISH -> { + Image( + imageVector = HedvigIcons.Swish, + contentDescription = EmptyContentDescription, + modifier = Modifier + .size((39.0).dp), + ) + } - is Invoice -> { - Image( - imageVector = HedvigIcons.Kivra, - contentDescription = EmptyContentDescription, - modifier = Modifier - .size((39.0).dp), - ) - } + MemberPaymentProvider.INVOICE -> { + Image( + imageVector = HedvigIcons.Kivra, + contentDescription = EmptyContentDescription, + modifier = Modifier + .size((39.0).dp), + ) + } - null -> { - Icon( - HedvigIcons.Plus, - EmptyContentDescription, - Modifier.size(39.dp), - tint = HedvigTheme.colorScheme.fillSecondary, - ) + null, + MemberPaymentProvider.NORDEA, + MemberPaymentProvider.UNKNOWN__, + -> { + Icon( + HedvigIcons.Plus, + EmptyContentDescription, + Modifier.size(39.dp), + tint = HedvigTheme.colorScheme.fillSecondary, + ) + } } } } - } - ThreeDotsLoading() - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.fillBlack, - contentColor = colorScheme.fillWhite, - border = colorScheme.borderPrimary, - modifier = Modifier.size(74.dp), - ) { - Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { - Icon( - HedvigIcons.HelipadOutline, - EmptyContentDescription, - Modifier.size(65.dp), - ) + ThreeDotsLoading() + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.fillBlack, + contentColor = colorScheme.fillWhite, + border = colorScheme.borderPrimary, + modifier = Modifier.size(74.dp), + ) { + Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { + Icon( + HedvigIcons.HelipadOutline, + EmptyContentDescription, + Modifier.size(65.dp), + ) + } } } + Spacer(Modifier.height(48.dp)) } } @Composable @HedvigPreview private fun PreviewSelectPrimaryPayinMethodScreen( - @PreviewParameter(SelectedMethodIndexProvider::class) method: PayinAccount?, + @PreviewParameter(SelectedMethodIndexProvider::class) provider: MemberPaymentProvider?, ) { HedvigTheme { Surface(color = colorScheme.backgroundPrimary) { - PayinMethodHandoverIllustration(method, - Modifier.padding(16.dp)) + PayinMethodHandoverIllustration( + provider, + Modifier.padding(16.dp), + ) } } } private class SelectedMethodIndexProvider : - CollectionPreviewParameterProvider( + CollectionPreviewParameterProvider( listOf( null, - Trustly( - "8327", - "91234124", - "Swedbank", - isPending = false, - isDefault = false, - ), - SwishPayin( - "0709901232", - isPending = false, - isDefault = true, - ), - Invoice( - delivery = InvoiceDelivery.Kivra, - email = null, - isPending = false, - isDefault = false, - ), + MemberPaymentProvider.TRUSTLY, + MemberPaymentProvider.SWISH, + MemberPaymentProvider.INVOICE, ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index 4dfd788a4f..a2331bc182 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -41,6 +41,7 @@ import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.ThreeDotsLoading +import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.HedvigIcons @@ -64,6 +65,8 @@ import com.hedvig.android.feature.payin.account.ui.components.payinMethodSubtitl import com.hedvig.android.feature.payin.account.ui.components.payinMethodTitle import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.SelectMethod +import hedvig.resources.PAYMENT_CONNECT_SUBTITLE +import hedvig.resources.PAYMENT_CONNECT_TITLE import hedvig.resources.PAYMENT_PRIMARY_CONFIRM_TITLE import hedvig.resources.PAYMENT_PRIMARY_SUBTITLE import hedvig.resources.PAYMENT_PRIMARY_TITLE @@ -111,30 +114,19 @@ private fun SelectPrimaryPayinMethodScreen( modifier = Modifier.fillMaxSize(), ) { Spacer(Modifier.height(8.dp)) - Column( - Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - HedvigText( - text = stringResource(string.PAYMENT_PRIMARY_TITLE), - textAlign = TextAlign.Center, - ) - HedvigText( - text = stringResource(string.PAYMENT_PRIMARY_SUBTITLE), - color = colorScheme.textSecondary, - textAlign = TextAlign.Center, - ) - } - Spacer(Modifier.height(48.dp)) + FlowHeading( + title = stringResource(Res.string.PAYMENT_PRIMARY_TITLE), + description = stringResource(Res.string.PAYMENT_PRIMARY_SUBTITLE), + baseStyle = HedvigTheme.typography.bodySmall, + modifier = Modifier.padding(horizontal = 16.dp), + ) Spacer(Modifier.weight(1f)) PayinMethodHandoverIllustration( - method = uiState.selectedMethod, - modifier = Modifier.align(Alignment.CenterHorizontally), + provider = uiState.selectedMethod?.provider, + modifier = Modifier + .align(Alignment.CenterHorizontally), ) Spacer(Modifier.weight(1f)) - Spacer(Modifier.height(48.dp)) RadioGroup( options = uiState.methods .sortedByDescending { it.isDefault } @@ -293,7 +285,7 @@ private fun ConfirmPrimaryPayinMethodBottomSheetPreview() { } @Composable -@HedvigPreview +@HedvigShortMultiScreenPreview private fun PreviewSelectPrimaryPayinMethodScreen( @PreviewParameter(SelectedMethodIndexProvider::class) selectedMethod: Int?, ) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt similarity index 90% rename from app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt index c6dc1b610a..11c57040d7 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -25,6 +26,7 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYMENT_CONNECT_SUBTITLE @@ -39,15 +41,15 @@ import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @Composable -internal fun SelectPayinMethodDestination( - viewModel: SelectPayinMethodViewModel, +internal fun ConnectPayinMethodDestination( + viewModel: ConnectPayinMethodViewModel, onTrustlySelected: () -> Unit, onSwishSelected: () -> Unit, onInvoiceSelected: () -> Unit, navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - SelectPayinMethodScreen( + ConnectPayinMethodScreen( uiState = uiState, onProviderSelected = { viewModel.emit(SelectPayinMethodEvent.SelectProvider(it)) }, onSubmitSelected = { @@ -72,8 +74,8 @@ internal fun SelectPayinMethodDestination( } @Composable -private fun SelectPayinMethodScreen( - uiState: SelectPayinMethodUiState, +private fun ConnectPayinMethodScreen( + uiState: ConnectPayinMethodUiState, onProviderSelected: (MemberPaymentProvider) -> Unit, onSubmitSelected: () -> Unit, navigateUp: () -> Unit, @@ -91,7 +93,11 @@ private fun SelectPayinMethodScreen( modifier = Modifier.padding(horizontal = 16.dp), ) Spacer(Modifier.weight(1f)) - Spacer(Modifier.height(8.dp)) + PayinMethodHandoverIllustration( + uiState.selectedProvider, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + Spacer(Modifier.weight(1f)) RadioGroup( options = uiState.availableProviders.mapNotNull { it.toRadioOption() }, selectedOption = uiState.selectedProvider?.let { RadioOptionId(it.rawValue) }, @@ -156,11 +162,11 @@ private fun MemberPaymentProvider.toRadioOption(): RadioOption? { @Composable @HedvigShortMultiScreenPreview -private fun PreviewSelectPayinMethodScreen() { +private fun PreviewConnectPayinMethodScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - SelectPayinMethodScreen( - uiState = SelectPayinMethodUiState( + ConnectPayinMethodScreen( + uiState = ConnectPayinMethodUiState( availableProviders = listOf( MemberPaymentProvider.SWISH, MemberPaymentProvider.INVOICE, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt similarity index 82% rename from app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt index 3955578224..17a04bbf13 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt @@ -16,10 +16,10 @@ import octopus.type.MemberPaymentProvider @AssistedInject @HedvigViewModel(ActivityRetainedScope::class) -internal class SelectPayinMethodViewModel( +internal class ConnectPayinMethodViewModel( @Assisted availableProviders: List, -) : MoleculeViewModel( - initialState = SelectPayinMethodUiState( +) : MoleculeViewModel( + initialState = ConnectPayinMethodUiState( availableProviders = availableProviders.map(MemberPaymentProvider::safeValueOf), selectedProvider = null, ), @@ -30,17 +30,17 @@ internal sealed interface SelectPayinMethodEvent { data class SelectProvider(val provider: MemberPaymentProvider) : SelectPayinMethodEvent } -internal data class SelectPayinMethodUiState( +internal data class ConnectPayinMethodUiState( val availableProviders: List, val selectedProvider: MemberPaymentProvider?, ) internal class SelectPayinMethodPresenter : - MoleculePresenter { + MoleculePresenter { @Composable override fun MoleculePresenterScope.present( - lastState: SelectPayinMethodUiState, - ): SelectPayinMethodUiState { + lastState: ConnectPayinMethodUiState, + ): ConnectPayinMethodUiState { var selectedProvider by remember { mutableStateOf(lastState.selectedProvider) } CollectEvents { event -> From 644413667dccb84d8bf9b6e825ac97e9eda82bb8 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 12:02:41 +0200 Subject: [PATCH 51/75] do not preselect --- .../account/ui/primary/SelectPrimaryPayinMethodViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt index a7e2918598..8dae50f42a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt @@ -25,7 +25,7 @@ internal class SelectPrimaryPayinMethodViewModel( ) : MoleculeViewModel( initialState = SelectPrimaryPayinMethodUiState( methods = currentMethods, - selectedMethod = currentMethods.firstOrNull { it.isDefault } ?: currentMethods.firstOrNull(), + selectedMethod = null, ), presenter = SelectPrimaryPayinMethodPresenter(setAsDefaultUseCase), ) From 8b8279d99736eb25f7b933e90cb84193df17da80 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 15:29:45 +0200 Subject: [PATCH 52/75] always show payout --- .../data/GetShouldShowPayoutUseCase.kt | 44 ------------ .../SwitchingGetShouldShowPayoutUseCase.kt | 20 ------ .../ui/payments/PaymentsDestination.kt | 71 +++++-------------- .../payments/ui/payments/PaymentsPresenter.kt | 33 --------- .../payments/ui/payments/PaymentsViewModel.kt | 3 - 5 files changed, 19 insertions(+), 152 deletions(-) delete mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetShouldShowPayoutUseCase.kt delete mode 100644 app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/SwitchingGetShouldShowPayoutUseCase.kt diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetShouldShowPayoutUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetShouldShowPayoutUseCase.kt deleted file mode 100644 index 03225cb5e5..0000000000 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetShouldShowPayoutUseCase.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.hedvig.android.feature.payments.overview.data - -import arrow.core.Either -import arrow.core.raise.either -import arrow.core.right -import com.apollographql.apollo.ApolloClient -import com.apollographql.cache.normalized.FetchPolicy -import com.apollographql.cache.normalized.fetchPolicy -import com.hedvig.android.apollo.ErrorMessage -import com.hedvig.android.apollo.safeExecute -import com.hedvig.android.core.common.ErrorMessage -import dev.zacsweers.metro.Inject -import octopus.ShouldShowPayoutButtonQuery - -internal interface GetShouldShowPayoutUseCase { - suspend fun invoke(): Either -} - -/** - * We do not want to show the payout button at all when there is no payout method connected nor is there a possibility - * to add one in the member's current state - */ -@Inject -internal class GetShouldShowPayoutUseCaseImpl( - private val apolloClient: ApolloClient, -) : GetShouldShowPayoutUseCase { - override suspend fun invoke(): Either = either { - val result = apolloClient - .query(ShouldShowPayoutButtonQuery()) - .fetchPolicy(FetchPolicy.NetworkFirst) - .safeExecute(::ErrorMessage) - .bind() - - val paymentMethods = result.currentMember.paymentMethods - paymentMethods.availableMethods.any { it.supportsPayout } || - paymentMethods.defaultPayoutMethod != null || - paymentMethods.payoutMethods.isNotEmpty() - } -} - -@Inject -internal class GetShouldShowPayoutUseCaseDemo : GetShouldShowPayoutUseCase { - override suspend fun invoke(): Either = false.right() -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/SwitchingGetShouldShowPayoutUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/SwitchingGetShouldShowPayoutUseCase.kt deleted file mode 100644 index 3979515a65..0000000000 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/SwitchingGetShouldShowPayoutUseCase.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.hedvig.android.feature.payments.overview.data - -import com.hedvig.android.core.common.di.AppScope -import com.hedvig.android.core.demomode.DemoManager -import com.hedvig.android.core.demomode.DemoSwitcher -import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn -import dev.zacsweers.metro.binding - -@Inject -@SingleIn(AppScope::class) -@ContributesBinding(AppScope::class, binding = binding()) -internal class SwitchingGetShouldShowPayoutUseCase( - override val demoManager: DemoManager, - override val prodImpl: GetShouldShowPayoutUseCaseImpl, - override val demoImpl: GetShouldShowPayoutUseCaseDemo, -) : GetShouldShowPayoutUseCase, DemoSwitcher() { - override suspend fun invoke() = pick().invoke() -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt index f544b3a516..3d4df6a409 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt @@ -1,8 +1,5 @@ package com.hedvig.android.feature.payments.ui.payments -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.expandVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -32,10 +29,8 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics @@ -66,7 +61,6 @@ import com.hedvig.android.design.system.hedvig.NotificationDefaults.InfoCardStyl import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.debugBorder import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.Campaign import com.hedvig.android.design.system.hedvig.icon.Card @@ -363,7 +357,8 @@ private fun PaymentsContent( ConnectedPaymentInfo.Unknown, is ConnectedPaymentInfo.Active, - -> {} + -> { + } } } @@ -381,9 +376,7 @@ private fun PaymentsContent( onDiscountClicked = onDiscountClicked, onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, - onPaymentMethodsClicked = onPaymentMethodsClicked, - showPayoutButton = (uiState as? Content)?.showPayoutButton == true, - ) + onPaymentMethodsClicked = onPaymentMethodsClicked) } } @@ -530,7 +523,6 @@ private fun PaymentsListItems( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, - showPayoutButton: Boolean, ) { val listItemsSideSpacingModifier = Modifier .padding(horizontal = 16.dp) @@ -615,32 +607,22 @@ private fun PaymentsListItems( ) HorizontalDivider(modifier = listItemsSideSpacingModifier) } - - val showPayoutItem = when (uiState.memberType) { - MemberType.QASA_ONLY_MEMBER, - MemberType.STANDARD_TO_QASA_MEMBER, - -> true - - MemberType.STANDARD_MEMBER -> showPayoutButton - } - if (showPayoutItem) { - PaymentsListItem( - text = stringResource(Res.string.PAYOUT_PAGE_HEADING), - icon = { - Icon( - imageVector = HedvigIcons.PaymentOutline, - contentDescription = null, - modifier = Modifier.size(24.dp), - ) - }, - modifier = Modifier - .clickable(onClick = onPayoutAccountClicked) - .then(listItemsSideSpacingModifier) - .padding(vertical = 16.dp) - .fillMaxWidth(), - ) - HorizontalDivider(modifier = listItemsSideSpacingModifier) - } + PaymentsListItem( + text = stringResource(Res.string.PAYOUT_PAGE_HEADING), + icon = { + Icon( + imageVector = HedvigIcons.PaymentOutline, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + modifier = Modifier + .clickable(onClick = onPayoutAccountClicked) + .then(listItemsSideSpacingModifier) + .padding(vertical = 16.dp) + .fillMaxWidth(), + ) + HorizontalDivider(modifier = listItemsSideSpacingModifier) } } } @@ -915,7 +897,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = listOf(OngoingCharge("id", LocalDate.fromEpochDays(401), UiMoney(200.0, SEK))), connectedPaymentInfo = ConnectedPaymentInfo.Active, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = true, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -931,7 +912,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -949,7 +929,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< null, ), primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), ) @@ -965,7 +944,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -987,7 +965,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -1003,7 +980,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Pending, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -1025,7 +1001,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< null, ), primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -1043,7 +1018,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< null, ), primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -1065,7 +1039,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< dueDateToConnect = System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -1087,7 +1060,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_MEMBER, ), ) @@ -1099,7 +1071,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), ) @@ -1111,7 +1082,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), ) @@ -1123,7 +1093,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.QASA_ONLY_MEMBER, ), ) @@ -1135,7 +1104,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = false, memberType = MemberType.QASA_ONLY_MEMBER, ), ) @@ -1151,7 +1119,6 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, primaryPayinMethod = PrimaryPayinMethod(PayinMethodId.Swish, "070-990 12 32"), - showPayoutButton = true, memberType = MemberType.STANDARD_MEMBER, ), ) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt index c6f5031fc6..1efe6f648b 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt @@ -21,21 +21,15 @@ import com.hedvig.android.feature.payments.data.PaymentConnection.Unknown import com.hedvig.android.feature.payments.data.PaymentOverview import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge import com.hedvig.android.feature.payments.data.PrimaryPayinMethod -import com.hedvig.android.feature.payments.overview.data.GetShouldShowPayoutUseCase import com.hedvig.android.feature.payments.overview.data.GetUpcomingPaymentUseCase import com.hedvig.android.feature.payments.ui.payments.PaymentsUiState.Content.ConnectedPaymentInfo import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.delay import kotlinx.datetime.LocalDate internal class PaymentsPresenter( private val getUpcomingPaymentUseCase: GetUpcomingPaymentUseCase, - getShouldShowPayoutUseCase: GetShouldShowPayoutUseCase, ) : MoleculePresenter { - private val shouldShowPayoutPresenter = ShouldShowPayoutPresenter(getShouldShowPayoutUseCase) - @Composable override fun MoleculePresenterScope.present(lastState: PaymentsUiState): PaymentsUiState { var loadIteration by remember { mutableIntStateOf(0) } @@ -52,8 +46,6 @@ internal class PaymentsPresenter( paymentOverviewResult = getUpcomingPaymentUseCase.invoke() } - val shouldShowPayout = shouldShowPayoutPresenter.present(loadIteration) - val currentPaymentResult = paymentOverviewResult ?: return PaymentsUiState.Loading return currentPaymentResult.fold( @@ -85,7 +77,6 @@ internal class PaymentsPresenter( ongoingCharges = paymentOverview.ongoingCharges, connectedPaymentInfo = paymentOverview.paymentConnection.toConnectedPaymentInfo(), primaryPayinMethod = paymentOverview.primaryPayinMethod, - showPayoutButton = shouldShowPayout, memberType = paymentOverview.memberType, ) }, @@ -93,29 +84,6 @@ internal class PaymentsPresenter( } } -private class ShouldShowPayoutPresenter( - private val getShouldShowPayoutUseCase: GetShouldShowPayoutUseCase, -) { - @Composable - fun present(loadIteration: Int): Boolean { - var shouldShowPayout by remember { mutableStateOf(false) } - LaunchedEffect(loadIteration) { - shouldShowPayout = false - for (attempt in 0..2) { - delay(attempt.seconds) - getShouldShowPayoutUseCase.invoke().fold( - ifLeft = {}, - ifRight = { result -> - shouldShowPayout = result - return@LaunchedEffect - }, - ) - } - } - return shouldShowPayout - } -} - private fun PaymentConnection.toConnectedPaymentInfo(): ConnectedPaymentInfo { return when (this) { Active -> ConnectedPaymentInfo.Active @@ -148,7 +116,6 @@ internal sealed interface PaymentsUiState { val ongoingCharges: List, val connectedPaymentInfo: ConnectedPaymentInfo, val primaryPayinMethod: PrimaryPayinMethod?, - val showPayoutButton: Boolean, val memberType: MemberType, ) : PaymentsUiState { sealed interface UpcomingPayment { diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsViewModel.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsViewModel.kt index 21dc4d8064..0cd7996b1c 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsViewModel.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsViewModel.kt @@ -2,7 +2,6 @@ package com.hedvig.android.feature.payments.ui.payments import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel -import com.hedvig.android.feature.payments.overview.data.GetShouldShowPayoutUseCase import com.hedvig.android.feature.payments.overview.data.GetUpcomingPaymentUseCase import com.hedvig.android.molecule.public.MoleculeViewModel import dev.zacsweers.metro.Inject @@ -11,11 +10,9 @@ import dev.zacsweers.metro.Inject @HedvigViewModel(ActivityRetainedScope::class) internal class PaymentsViewModel( getUpcomingPaymentUseCase: GetUpcomingPaymentUseCase, - getShouldShowPayoutUseCase: GetShouldShowPayoutUseCase, ) : MoleculeViewModel( PaymentsUiState.Loading, PaymentsPresenter( getUpcomingPaymentUseCase = getUpcomingPaymentUseCase, - getShouldShowPayoutUseCase = getShouldShowPayoutUseCase, ), ) From 9d8b9ffa6effdd347780b996b9d88c50208633be Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 15:34:29 +0200 Subject: [PATCH 53/75] from PrimaryPayinMethodSection navigate to PaymentMethods --- .../payments/navigation/PaymentsEntries.kt | 3 --- .../ui/payments/PaymentsDestination.kt | 23 ++++++++----------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt index 889b73812e..5ba66d81c5 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt @@ -50,9 +50,6 @@ fun EntryProviderScope.paymentsEntries( backstack.add(PaymentDetailsKey(id)) }, onPaymentMethodsClicked = dropUnlessResumed { navigateToPayinAccount() }, - onPrimaryPayinMethodClicked = dropUnlessResumed { method: PayinMethodId -> - navigateToPayinMethodDetails(method) - }, onOpenManualCharge = { backstack.add(ManualChargeKey) }, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt index 3d4df6a409..7649d572b7 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt @@ -137,7 +137,6 @@ internal fun PaymentsDestination( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, - onPrimaryPayinMethodClicked: (PayinMethodId) -> Unit, onChangeBankAccount: () -> Unit, onOpenManualCharge: () -> Unit, ) { @@ -151,7 +150,6 @@ internal fun PaymentsDestination( onPayoutAccountClicked = onPayoutAccountClicked, onRetry = { viewModel.emit(Retry) }, onPaymentMethodsClicked = onPaymentMethodsClicked, - onPrimaryPayinMethodClicked = onPrimaryPayinMethodClicked, onOpenManualCharge = onOpenManualCharge, ) } @@ -165,7 +163,6 @@ private fun PaymentsScreen( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, - onPrimaryPayinMethodClicked: (PayinMethodId) -> Unit, onOpenManualCharge: () -> Unit, onRetry: () -> Unit, ) { @@ -229,7 +226,6 @@ private fun PaymentsScreen( onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, onPaymentMethodsClicked = onPaymentMethodsClicked, - onPrimaryPayinMethodClicked = onPrimaryPayinMethodClicked, onOpenManualCharge = onOpenManualCharge, ) Spacer(Modifier.height(16.dp)) @@ -256,7 +252,6 @@ private fun PaymentsContent( onPaymentHistoryClicked: () -> Unit, onPayoutAccountClicked: () -> Unit, onPaymentMethodsClicked: () -> Unit, - onPrimaryPayinMethodClicked: (PayinMethodId) -> Unit, onOpenManualCharge: () -> Unit, modifier: Modifier = Modifier, ) { @@ -366,7 +361,9 @@ private fun PaymentsContent( if (primaryPayinMethod != null) { PrimaryPayinMethodSection( method = primaryPayinMethod, - onClick = { onPrimaryPayinMethodClicked(primaryPayinMethod.id) }, + onClick = { + onPaymentMethodsClicked() + }, ) Spacer(Modifier.height(8.dp)) } @@ -376,7 +373,8 @@ private fun PaymentsContent( onDiscountClicked = onDiscountClicked, onPaymentHistoryClicked = onPaymentHistoryClicked, onPayoutAccountClicked = onPayoutAccountClicked, - onPaymentMethodsClicked = onPaymentMethodsClicked) + onPaymentMethodsClicked = onPaymentMethodsClicked, + ) } } @@ -486,11 +484,11 @@ private fun PrimaryPayinMethodSection(method: PrimaryPayinMethod, onClick: () -> ) } } - Icon( - imageVector = HedvigIcons.ChevronRight, - contentDescription = null, - modifier = Modifier.size(24.dp), - ) +// Icon( +// imageVector = HedvigIcons.ChevronRight, +// contentDescription = null, +// modifier = Modifier.size(24.dp), +// ) } } } @@ -877,7 +875,6 @@ private fun PreviewPaymentScreen( onPaymentHistoryClicked = {}, onPayoutAccountClicked = {}, onPaymentMethodsClicked = {}, - onPrimaryPayinMethodClicked = {}, onOpenManualCharge = {}, onRetry = {}, ) From bb95437aed9a34e233869064ba1aeca006f53726 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 16:10:55 +0200 Subject: [PATCH 54/75] fix payment method details --- .../androidMain/res/values-sv-rSE/strings.xml | 13 +- .../src/androidMain/res/values/strings.xml | 11 +- .../values-sv-rSE/strings.xml | 13 +- .../composeResources/values/strings.xml | 11 +- .../src/main/graphql/GetPayinMethods.graphql | 1 + .../account/data/GetPayinAccountUseCase.kt | 3 + .../account/ui/components/PayinMethodRow.kt | 9 +- .../PayinMethodDetailsDestination.kt | 156 +++++++++++++++--- .../PayinMethodDetailsViewModel.kt | 4 +- .../SelectPrimaryPayinMethodDestination.kt | 2 - 10 files changed, 174 insertions(+), 49 deletions(-) diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index bfd63085bc..91f8b8930f 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -724,11 +724,12 @@ Rabatt via inbjudningar Betalningsmetod Delsumma + Nummer Kommande betalning Giltig till %1$s Lägg till betalningsmetod Välj primär metod - Lägg till en betalningsmetod för att hålla din försäkring aktiv + Lägg till en ny betalningsmetod för din försäkring Koppla betalning Betalningshistorik Betalningsmetoder @@ -737,11 +738,11 @@ Direktbetalning via faktura Betalningar och utbetalningar via Swish Anslut din bank via Trustly - Bekräfta primär betalningsmetod - Primär - Ändra ditt föredragna betalsätt - Primär betalningsmetod - Är du säker på att du vill ta bort betalningsmetoden? + Bekräfta betalningsmetod + Förvalt + Den här metoden används för dina månadsbetalningar + Välj betalningsmetod + Du kan lägga till den igen när som helst Ta bort betalningsmetod Lägg till utbetalningsmetod Direktutbetalning via Faktura diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index 6cee9c8d0a..b6ca324dca 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -724,11 +724,12 @@ Referral discount Payment method Subtotal + Number Upcoming payment Valid until %1$s Add payment method Choose primary method - Add a payment method to keep your insurance active + Add a new payment method for your insurance Connect payment Payment history Payment methods @@ -737,11 +738,11 @@ Direct payment via Invoice Payments and payouts via Swish Connect your bank via Trustly - Confirm primary payment method + Confirm payment method Primary - Change your preferred way of payment - Primary payment method - Are you sure you want to remove payment method? + This method will be used for your monthly payments + Choose payment method + You can add it back any time Remove payment method Add payout method Direct payment via Invoice diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index e9d658d8c3..7590df26af 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -723,11 +723,12 @@ Rabatt via inbjudningar Betalningsmetod Delsumma + Nummer Kommande betalning Giltig till %1$s Lägg till betalningsmetod Välj primär metod - Lägg till en betalningsmetod för att hålla din försäkring aktiv + Lägg till en ny betalningsmetod för din försäkring Koppla betalning Betalningshistorik Betalningsmetoder @@ -736,11 +737,11 @@ Direktbetalning via faktura Betalningar och utbetalningar via Swish Anslut din bank via Trustly - Bekräfta primär betalningsmetod - Primär - Ändra ditt föredragna betalsätt - Primär betalningsmetod - Är du säker på att du vill ta bort betalningsmetoden? + Bekräfta betalningsmetod + Förvalt + Den här metoden används för dina månadsbetalningar + Välj betalningsmetod + Du kan lägga till den igen när som helst Ta bort betalningsmetod Lägg till utbetalningsmetod Direktutbetalning via Faktura diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index e53ecae56e..97b3c03ed9 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -723,11 +723,12 @@ Referral discount Payment method Subtotal + Number Upcoming payment Valid until %1$s Add payment method Choose primary method - Add a payment method to keep your insurance active + Add a new payment method for your insurance Connect payment Payment history Payment methods @@ -736,11 +737,11 @@ Direct payment via Invoice Payments and payouts via Swish Connect your bank via Trustly - Confirm primary payment method + Confirm payment method Primary - Change your preferred way of payment - Primary payment method - Are you sure you want to remove payment method? + This method will be used for your monthly payments + Choose payment method + You can add it back any time Remove payment method Add payout method Direct payment via Invoice diff --git a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql index eaac18a154..ec071b2a44 100644 --- a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql +++ b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql @@ -1,6 +1,7 @@ query GetPayinMethods { currentMember { paymentMethods { + chargingDay payinMethods { provider status diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index a1e634dc8c..98fddc4ca8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -25,6 +25,8 @@ import octopus.type.PaymentMethodInvoiceDelivery internal data class PayinAccountData( val currentMethods: List, val availablePayinMethods: List, + /** Day of the month the member is charged on. Set per member, so it is the same for every method. */ + val chargingDay: Int?, ) @SingleIn(AppScope::class) @@ -91,6 +93,7 @@ internal class GetPayinAccountUseCase( PayinAccountData( currentMethods = currentMethods, availablePayinMethods = availablePayinMethods, + chargingDay = paymentMethods.chargingDay, ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index eb0ac64329..08182edc00 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -25,6 +25,7 @@ import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.toDeliveryString +import com.hedvig.android.logger.logcat import hedvig.resources.PAYMENTS_BANK_LABEL import hedvig.resources.PAYMENT_PRIMARY_LABEL import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL @@ -142,8 +143,12 @@ private fun PayinAccount.Trustly.maskedAccount(): String? { } internal fun PayinAccount.Trustly.maskedAccountNumber(): String? { - val lastFour = accountNumber?.takeLast(4)?.takeIf { it.isNotBlank() } ?: return null - return "**** $lastFour" + val whole = "${clearingNumber?.takeIf{ it.isNotBlank() } ?: ""}${accountNumber?.takeIf{ it.isNotBlank() } ?: ""}" + if (whole.length > 8) { + val lastFour = whole.takeLast(4).takeIf { it.isNotBlank() } ?: return null + return "**** $lastFour" + } else return null + } internal fun formatSwishPhoneNumber(phoneNumber: String): String { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt index fb83b06d3f..2cac3de6f4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -1,12 +1,17 @@ package com.hedvig.android.feature.payin.account.ui.methoddetails +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter @@ -14,6 +19,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection @@ -22,10 +28,18 @@ import com.hedvig.android.design.system.hedvig.HedvigRedTextButton import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HorizontalDivider import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.IconButton import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.datepicker.getLocale +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.InfoFilled +import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.feature.payin.account.data.InvoiceDelivery import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.toDeliveryString @@ -33,13 +47,20 @@ import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel import com.hedvig.android.feature.payin.account.ui.components.maskedAccountNumber import hedvig.resources.GENERAL_REMOVE +import hedvig.resources.KIVRA_PAYMENT_INFO import hedvig.resources.PAYMENTS_ACCOUNT import hedvig.resources.PAYMENTS_AUTOGIRO_LABEL import hedvig.resources.PAYMENTS_BANK_LABEL +import hedvig.resources.PAYMENTS_DUE_DESCRIPTION import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYMENTS_PAYMENT_DUE +import hedvig.resources.PAYMENTS_PAYMENT_DUE_INFO import hedvig.resources.PAYMENTS_PAYMENT_METHOD +import hedvig.resources.PAYMENTS_SWISH_NUMBER import hedvig.resources.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT +import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION import hedvig.resources.Res +import hedvig.resources.general_close_button import hedvig.resources.swish import org.jetbrains.compose.resources.stringResource @@ -81,6 +102,7 @@ internal fun PayinMethodDetailsDestination( is PayinMethodDetailsUiState.Content -> { PayinMethodDetailsScreen( method = state.method, + chargingDay = state.chargingDay, navigateUp = navigateUp, onChangeMethod = { onChangeMethod(state.method) }, onRemoveMethod = onRemoveMethod, @@ -92,10 +114,13 @@ internal fun PayinMethodDetailsDestination( @Composable private fun PayinMethodDetailsScreen( method: PayinAccount, + chargingDay: Int?, navigateUp: () -> Unit, onChangeMethod: () -> Unit, onRemoveMethod: () -> Unit, ) { + val explanationSheetState = rememberHedvigBottomSheetState() + ExplanationBottomSheet(explanationSheetState) HedvigScaffold( topAppBarText = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), navigateUp = navigateUp, @@ -122,17 +147,33 @@ private fun PayinMethodDetailsScreen( is PayinAccount.Invoice -> stringResource(Res.string.PAYMENTS_INVOICE) }, ) - // The charging day comes from MemberPaymentMethods.chargingDay, which the payin methods query does not - // request yet. Restore this row, and the info-button explanation sheet that belongs to it, once it does. - // DetailRow( - // label = stringResource(Res.string.PAYMENTS_PAYMENT_DUE), - // value = stringResource(Res.string.PAYMENTS_DUE_DESCRIPTION, chargingDay.format()), - // ) + if (chargingDay != null) { + val formattedDay = chargingDay.format() + DetailRow( + label = stringResource(Res.string.PAYMENTS_PAYMENT_DUE), + value = stringResource(Res.string.PAYMENTS_DUE_DESCRIPTION, formattedDay), + endSlot = { + val explanation = method.explanation(formattedDay) + if (explanation != null) { + IconButton( + onClick = { explanationSheetState.show(explanation) }, + modifier = Modifier.size(24.dp), + ) { + Icon( + imageVector = HedvigIcons.InfoFilled, + contentDescription = stringResource(Res.string.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION), + modifier = Modifier.size(24.dp), + tint = HedvigTheme.colorScheme.fillSecondaryTransparent, + ) + } + } + }, + ) + } when (method) { is PayinAccount.SwishPayin -> { DetailRow( - // TODO: Add "Number" / "Nummer" to Lokalise - label = "Number", + label = stringResource(Res.string.PAYMENTS_SWISH_NUMBER), value = method.phoneNumber.orEmpty(), ) } @@ -146,12 +187,6 @@ private fun PayinMethodDetailsScreen( label = stringResource(Res.string.PAYMENTS_BANK_LABEL), value = method.bankName.orEmpty(), ) - // The mandate holder comes from MemberPaymentInformation.chargeMethod.mandate, which nothing in this - // feature queries yet. - // DetailRow( - // label = stringResource(Res.string.PAYMENTS_MANDATE), - // value = mandate, - // ) } is PayinAccount.Invoice -> { @@ -195,16 +230,22 @@ private fun PayinMethodDetailsScreen( } @Composable -private fun DetailRow(label: String, value: String) { +private fun DetailRow(label: String, value: String, endSlot: @Composable (() -> Unit)? = null) { HorizontalItemsWithMaximumSpaceTaken( startSlot = { HedvigText(label) }, endSlot = { - HedvigText( - text = value, - textAlign = TextAlign.End, - modifier = Modifier.fillMaxWidth(), - color = HedvigTheme.colorScheme.textSecondary, - ) + Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { + HedvigText( + text = value, + textAlign = TextAlign.End, + modifier = Modifier.weight(1f, fill = false), + color = HedvigTheme.colorScheme.textSecondary, + ) + if (endSlot != null) { + Spacer(Modifier.width(8.dp)) + endSlot() + } + } }, spaceBetween = 8.dp, modifier = Modifier.padding(horizontal = 18.dp, vertical = 16.dp), @@ -212,6 +253,78 @@ private fun DetailRow(label: String, value: String) { HorizontalDivider(Modifier.padding(horizontal = 18.dp)) } +private sealed interface PaymentDueExplanation { + data object Kivra : PaymentDueExplanation + + data class DirectDebit(val dueDate: String) : PaymentDueExplanation +} + +private fun PayinAccount.explanation(formattedDay: String): PaymentDueExplanation? = when { + this is PayinAccount.Trustly -> PaymentDueExplanation.DirectDebit(formattedDay) + this is PayinAccount.Invoice && delivery == InvoiceDelivery.Kivra -> PaymentDueExplanation.Kivra + else -> null +} + +@Composable +private fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { + HedvigBottomSheet(sheetState) { explanation -> + HedvigText( + text = when (explanation) { + PaymentDueExplanation.Kivra -> { + stringResource(Res.string.KIVRA_PAYMENT_INFO) + } + + is PaymentDueExplanation.DirectDebit -> { + stringResource(Res.string.PAYMENTS_PAYMENT_DUE_INFO, explanation.dueDate) + } + }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(32.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_close_button), + onClick = { sheetState.dismiss() }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) + } +} + +@Composable +private fun Int.format(): String { + val day = this + val lastDigit = day % 10 + + val locale = getLocale() + + val suffix = when (locale.language) { + "en" -> when (lastDigit) { + 1 -> "st" + 2 -> "nd" + 3 -> "rd" + else -> "th" + } + + "sv" -> when (day) { + 11, 12 -> ":e" + + else -> when (lastDigit) { + 1, 2 -> ":a" + else -> ":e" + } + } + + else -> when (lastDigit) { + 1 -> "st" + 2 -> "nd" + 3 -> "rd" + else -> "th" + } + } + + return "$day$suffix" +} + @Composable @HedvigShortMultiScreenPreview private fun PreviewPayinMethodDetailsDestination( @@ -221,6 +334,7 @@ private fun PreviewPayinMethodDetailsDestination( Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { PayinMethodDetailsScreen( method = method, + chargingDay = 27, navigateUp = {}, onChangeMethod = {}, onRemoveMethod = {}, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt index 6ef994e560..59caa7112d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -38,7 +38,7 @@ internal sealed interface PayinMethodDetailsUiState { data object Error : PayinMethodDetailsUiState - data class Content(val method: PayinAccount) : PayinMethodDetailsUiState + data class Content(val method: PayinAccount, val chargingDay: Int?) : PayinMethodDetailsUiState } internal class PayinMethodDetailsPresenter( @@ -62,7 +62,7 @@ internal class PayinMethodDetailsPresenter( uiState = if (method == null) { PayinMethodDetailsUiState.Error } else { - PayinMethodDetailsUiState.Content(method) + PayinMethodDetailsUiState.Content(method, data.chargingDay) } }, ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index a2331bc182..8d8f02b542 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -247,8 +247,6 @@ private fun ConfirmPrimaryPayinMethodBottomSheetContent( Spacer(Modifier.height(16.dp)) } - - @Composable private fun PayinAccount.toRadioOption(): RadioOption = RadioOption( id = RadioOptionId(provider.rawValue), From 2f6afec3e259c56e84aebfb8c71b147c29f94d65 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 16:28:13 +0200 Subject: [PATCH 55/75] change subtitle for connected if same provider already connected --- .../navigation/PayinAccountDestination.kt | 1 + .../account/navigation/PayinAccountEntries.kt | 4 +++- .../PayinAccountOverviewDestination.kt | 1 - .../ConnectPayinMethodDestination.kt | 23 ++++++++++++++----- .../ConnectPayinMethodViewModel.kt | 6 ++++- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 64b6ebc78d..f2731e6aff 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable @Serializable internal data class SelectPayinMethodKey( val availableProviders: List, + val currentProviders: List, ) : HedvigNavKey @Serializable diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 44c9ccee92..1781830c56 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -6,6 +6,7 @@ import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.id +import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsDestination import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsViewModel import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsViewModelFactory @@ -44,6 +45,7 @@ fun EntryProviderScope.payinAccountEntries( backstack.add( SelectPayinMethodKey( availableProviders = content?.availablePayinMethods?.map { it.rawValue } ?: emptyList(), + currentProviders = content?.currentMethods?.map { it.provider.rawValue } ?: emptyList(), ), ) }, @@ -103,7 +105,7 @@ fun EntryProviderScope.payinAccountEntries( entry { key -> val viewModel: ConnectPayinMethodViewModel = assistedMetroViewModel { - create(key.availableProviders) + create(key.availableProviders, key.currentProviders) } ConnectPayinMethodDestination( viewModel = viewModel, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index f2d764f0ec..a5c3cc510e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt index 11c57040d7..f92095e7f6 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt @@ -31,6 +31,7 @@ import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYMENT_CONNECT_SUBTITLE import hedvig.resources.PAYMENT_CONNECT_TITLE +import hedvig.resources.PAYMENT_OPTION_CONNECTED_LABEL import hedvig.resources.PAYMENT_OPTION_INVOICE_SUBTITLE import hedvig.resources.PAYMENT_OPTION_SWISH_SUBTITLE import hedvig.resources.PAYMENT_OPTION_TRUSTLY_SUBTITLE @@ -99,7 +100,8 @@ private fun ConnectPayinMethodScreen( ) Spacer(Modifier.weight(1f)) RadioGroup( - options = uiState.availableProviders.mapNotNull { it.toRadioOption() }, + options = uiState.availableProviders.mapNotNull { it.toRadioOption( + uiState.currentProviders) }, selectedOption = uiState.selectedProvider?.let { RadioOptionId(it.rawValue) }, onRadioOptionSelected = { onProviderSelected(MemberPaymentProvider.safeValueOf(it.id)) }, modifier = Modifier.padding(horizontal = 16.dp), @@ -131,28 +133,35 @@ private fun ConnectPayinMethodScreen( * of the group. */ @Composable -private fun MemberPaymentProvider.toRadioOption(): RadioOption? { +private fun MemberPaymentProvider.toRadioOption( + currentProviders: List +): RadioOption? { val id = RadioOptionId(rawValue) return when (this) { MemberPaymentProvider.TRUSTLY -> RadioOption( id = id, text = stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL), - label = stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE), + label = if (currentProviders.contains(this)) + stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) else + stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE), iconResource = IconResource.Vector(HedvigIcons.Trustly), ) MemberPaymentProvider.SWISH -> RadioOption( id = id, - // todo: check text = stringResource(Res.string.swish), - label = stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE), + label = if (currentProviders.contains(this)) + stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) + else stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE), iconResource = IconResource.Vector(HedvigIcons.Swish), ) MemberPaymentProvider.INVOICE -> RadioOption( id = id, text = stringResource(Res.string.PAYMENTS_INVOICE), - label = stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE), + label = if (currentProviders.contains(this)) + stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) else + stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE), iconResource = IconResource.Vector(HedvigIcons.Kivra), ) @@ -173,6 +182,8 @@ private fun PreviewConnectPayinMethodScreen() { MemberPaymentProvider.TRUSTLY, ), selectedProvider = MemberPaymentProvider.TRUSTLY, + currentProviders = listOf( + MemberPaymentProvider.SWISH) ), onProviderSelected = {}, onSubmitSelected = {}, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt index 17a04bbf13..d621c0845a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.feature.payin.account.ui.selectmethod.SelectPayinMethodEvent.SelectProvider import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -18,10 +19,12 @@ import octopus.type.MemberPaymentProvider @HedvigViewModel(ActivityRetainedScope::class) internal class ConnectPayinMethodViewModel( @Assisted availableProviders: List, + @Assisted currentProviders: List, ) : MoleculeViewModel( initialState = ConnectPayinMethodUiState( availableProviders = availableProviders.map(MemberPaymentProvider::safeValueOf), selectedProvider = null, + currentProviders = currentProviders.map(MemberPaymentProvider::safeValueOf), ), presenter = SelectPayinMethodPresenter(), ) @@ -31,6 +34,7 @@ internal sealed interface SelectPayinMethodEvent { } internal data class ConnectPayinMethodUiState( + val currentProviders: List, val availableProviders: List, val selectedProvider: MemberPaymentProvider?, ) @@ -45,7 +49,7 @@ internal class SelectPayinMethodPresenter : CollectEvents { event -> when (event) { - is SelectPayinMethodEvent.SelectProvider -> selectedProvider = event.provider + is SelectProvider -> selectedProvider = event.provider } } From 753ed1c50903fd956bca76439aef1e72c0843f49 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 17:02:37 +0200 Subject: [PATCH 56/75] add remove BS --- .../androidMain/res/values-sv-rSE/strings.xml | 9 +- .../src/androidMain/res/values/strings.xml | 7 +- .../values-sv-rSE/strings.xml | 9 +- .../composeResources/values/strings.xml | 7 +- .../android/design/system/hedvig/Modifier.kt | 22 +++ .../OnboardingConnectingPaymentSymbol.kt | 25 +-- .../payin/account/data/RemoveMethodUseCase.kt | 24 +++ .../account/navigation/PayinAccountEntries.kt | 3 - .../PayinMethodHandoverIllustration.kt | 6 +- .../account/ui/components/PayinMethodRow.kt | 19 +- .../PayinMethodDetailsDestination.kt | 38 ++-- .../PayinMethodDetailsViewModel.kt | 35 +++- .../RemovePayinMethodBottomSheet.kt | 175 ++++++++++++++++++ 13 files changed, 314 insertions(+), 65 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 91f8b8930f..94216c97fc 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -744,6 +744,7 @@ Välj betalningsmetod Du kan lägga till den igen när som helst Ta bort betalningsmetod + Change number Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish @@ -827,10 +828,10 @@ Ett utkast sparas i 7 dagar. Du kan göra en ny anmälan när du vill. Utkastet finns inte kvar Fortsätt - Det du fyllt i sparas som ett utkast i 7 dagar. - Stanna kvar - Lämna - Vill du lämna anmälan? + Det du fyllt i sparas som utkast i 7 dagar. + Fortsätt + Spara utkast + Stäng anmälan? Påbörjad %1$s Spara och fortsätt Inga träffar på din sökning diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index b6ca324dca..31c7c4a759 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -744,6 +744,7 @@ Choose payment method You can add it back any time Remove payment method + Change number Add payout method Direct payment via Invoice Instant payout with Swish @@ -828,9 +829,9 @@ This draft has expired Continue What you\'ve filled in is saved as a draft for 7 days. - Stay - Leave - Leave this claim? + Continue + Save draft + Close this claim? Started %1$s Save and continue No results for your search diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 7590df26af..60dccbe205 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -743,6 +743,7 @@ Välj betalningsmetod Du kan lägga till den igen när som helst Ta bort betalningsmetod + Change number Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish @@ -826,10 +827,10 @@ Ett utkast sparas i 7 dagar. Du kan göra en ny anmälan när du vill. Utkastet finns inte kvar Fortsätt - Det du fyllt i sparas som ett utkast i 7 dagar. - Stanna kvar - Lämna - Vill du lämna anmälan? + Det du fyllt i sparas som utkast i 7 dagar. + Fortsätt + Spara utkast + Stäng anmälan? Påbörjad %1$s Spara och fortsätt Inga träffar på din sökning diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index 97b3c03ed9..5c85b6c1d4 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -743,6 +743,7 @@ Choose payment method You can add it back any time Remove payment method + Change number Add payout method Direct payment via Invoice Instant payout with Swish @@ -827,9 +828,9 @@ This draft has expired Continue What you've filled in is saved as a draft for 7 days. - Stay - Leave - Leave this claim? + Continue + Save draft + Close this claim? Started %1$s Save and continue No results for your search diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Modifier.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Modifier.kt index fb84b2f2e4..267130005c 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Modifier.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Modifier.kt @@ -22,7 +22,10 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager @@ -51,6 +54,25 @@ fun Modifier.clearFocusOnTap(): Modifier = this.composed { } } +/** Draws a dashed outline that follows [shape], on top of the content so it is not clipped. */ +fun Modifier.dashedBorder( + color: Color, + shape: Shape, + strokeWidth: Dp = 1.dp, + dashOn: Dp = 4.dp, + dashOff: Dp = 4.dp, +): Modifier = this.drawWithContent { + drawContent() + drawOutline( + outline = shape.createOutline(size, layoutDirection, this), + color = color, + style = Stroke( + width = strokeWidth.toPx(), + pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashOn.toPx(), dashOff.toPx())), + ), + ) +} + /** * Used when quickly prototyping something and want to see how it renders */ diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt index 2ef573f64a..b46a1c4cf2 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt @@ -22,15 +22,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.PathEffect -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.drawOutline -import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText @@ -38,6 +31,7 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.ThreeDotsLoading +import com.hedvig.android.design.system.hedvig.dashedBorder import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.Checkmark import com.hedvig.android.design.system.hedvig.icon.HedvigIcons @@ -103,9 +97,6 @@ private fun BankCard() { .dashedBorder( color = HedvigTheme.colorScheme.borderSecondary, shape = HedvigTheme.shapes.cornerXXLarge, - strokeWidth = 1.dp, - dashOn = 4.dp, - dashOff = 4.dp, ), ) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { @@ -117,20 +108,6 @@ private fun BankCard() { } } -/** Draws a dashed outline that follows [shape], on top of the content so it is not clipped. */ -private fun Modifier.dashedBorder(color: Color, shape: Shape, strokeWidth: Dp, dashOn: Dp, dashOff: Dp): Modifier = - drawWithContent { - drawContent() - drawOutline( - outline = shape.createOutline(size, layoutDirection, this), - color = color, - style = Stroke( - width = strokeWidth.toPx(), - pathEffect = PathEffect.dashPathEffect(floatArrayOf(dashOn.toPx(), dashOff.toPx())), - ), - ) - } - @Composable private fun HedvigSymbolWithCheck(checkVisible: Boolean) { val checkScale by animateFloatAsState( diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt new file mode 100644 index 0000000000..902599498d --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt @@ -0,0 +1,24 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +import arrow.core.right +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import octopus.type.MemberPaymentProvider + +internal interface RemoveMethodUseCase { + suspend fun invoke(provider: MemberPaymentProvider): Either +} + +@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +@Inject +internal class RemoveMethodUseCaseImpl : RemoveMethodUseCase { + override suspend fun invoke(provider: MemberPaymentProvider): Either { + // TODO: run the remove-payin-method mutation once the backend exposes one. + return Unit.right() + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 1781830c56..25d2caf384 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -84,9 +84,6 @@ fun EntryProviderScope.payinAccountEntries( } } }, - onRemoveMethod = { - // TODO: call the remove-payin-method API once the backend exposes one. - }, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt index bc5c57f88a..08da65329e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt @@ -22,6 +22,7 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.ThreeDotsLoading +import com.hedvig.android.design.system.hedvig.dashedBorder import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.HelipadOutline @@ -44,8 +45,11 @@ internal fun PayinMethodHandoverIllustration(provider: MemberPaymentProvider?, m shape = HedvigTheme.shapes.cornerXXLarge, color = colorScheme.backgroundPrimary, contentColor = colorScheme.fillPrimary, - border = colorScheme.borderSecondary, modifier = Modifier.size(74.dp) + .dashedBorder( + color = colorScheme.borderSecondary, + shape = HedvigTheme.shapes.cornerXXLarge, + ) .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge), ) { Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index 08182edc00..52b4b8575e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -47,15 +47,24 @@ private fun PayinMethodPillow(method: PayinAccount, modifier: Modifier = Modifie modifier = modifier.size(40.dp), ) { Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { - when (method) { - is PayinAccount.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(28.dp)) - is PayinAccount.SwishPayin -> Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(28.dp)) - is PayinAccount.Invoice -> Image(HedvigIcons.Kivra, EmptyContentDescription, Modifier.size(28.dp)) - } + PayinMethodMark(method, Modifier.size(28.dp)) } } } +/** + * The method's brand mark. Trustly's is monochrome and picks up the surrounding content colour; the + * others are full-colour and ignore it. + */ +@Composable +internal fun PayinMethodMark(method: PayinAccount, modifier: Modifier = Modifier) { + when (method) { + is PayinAccount.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, modifier) + is PayinAccount.SwishPayin -> Image(HedvigIcons.Swish, EmptyContentDescription, modifier) + is PayinAccount.Invoice -> Image(HedvigIcons.Kivra, EmptyContentDescription, modifier) + } +} + @Composable internal fun payinMethodTitle(method: PayinAccount): String = when (method) { is PayinAccount.Trustly -> stringResource(Res.string.PAYMENTS_BANK_LABEL) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt index 2cac3de6f4..af4e06bf66 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -57,6 +57,7 @@ import hedvig.resources.PAYMENTS_PAYMENT_DUE import hedvig.resources.PAYMENTS_PAYMENT_DUE_INFO import hedvig.resources.PAYMENTS_PAYMENT_METHOD import hedvig.resources.PAYMENTS_SWISH_NUMBER +import hedvig.resources.PAYMENT_SWISH_CHANGE_NUMBER import hedvig.resources.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION import hedvig.resources.Res @@ -69,7 +70,6 @@ internal fun PayinMethodDetailsDestination( viewModel: PayinMethodDetailsViewModel, navigateUp: () -> Unit, onChangeMethod: (PayinAccount) -> Unit, - onRemoveMethod: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() when (val state = uiState) { @@ -103,9 +103,10 @@ internal fun PayinMethodDetailsDestination( PayinMethodDetailsScreen( method = state.method, chargingDay = state.chargingDay, + isRemoving = state.isRemoving, navigateUp = navigateUp, onChangeMethod = { onChangeMethod(state.method) }, - onRemoveMethod = onRemoveMethod, + onConfirmRemoveMethod = { viewModel.emit(PayinMethodDetailsEvent.RemoveMethod) }, ) } } @@ -115,12 +116,19 @@ internal fun PayinMethodDetailsDestination( private fun PayinMethodDetailsScreen( method: PayinAccount, chargingDay: Int?, + isRemoving: Boolean, navigateUp: () -> Unit, onChangeMethod: () -> Unit, - onRemoveMethod: () -> Unit, + onConfirmRemoveMethod: () -> Unit, ) { val explanationSheetState = rememberHedvigBottomSheetState() ExplanationBottomSheet(explanationSheetState) + val removeSheetState = rememberHedvigBottomSheetState() + RemovePayinMethodBottomSheet( + sheetState = removeSheetState, + isRemoving = isRemoving, + onConfirmRemove = onConfirmRemoveMethod, + ) HedvigScaffold( topAppBarText = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), navigateUp = navigateUp, @@ -199,11 +207,8 @@ private fun PayinMethodDetailsScreen( Spacer(Modifier.weight(1f)) Spacer(Modifier.height(16.dp)) val changeButtonText = when (method) { - // TODO: Add "Change number" / "Ändra nummer" to Lokalise - is PayinAccount.SwishPayin -> "Change number" - + is PayinAccount.SwishPayin -> stringResource(Res.string.PAYMENT_SWISH_CHANGE_NUMBER) is PayinAccount.Trustly -> stringResource(Res.string.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT) - is PayinAccount.Invoice -> null } if (changeButtonText != null) { @@ -218,13 +223,15 @@ private fun PayinMethodDetailsScreen( ) Spacer(Modifier.height(8.dp)) } - HedvigRedTextButton( - text = stringResource(Res.string.GENERAL_REMOVE), - onClick = onRemoveMethod, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) + if (!method.isDefault) { + HedvigRedTextButton( + text = stringResource(Res.string.GENERAL_REMOVE), + onClick = { removeSheetState.show(method) }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } Spacer(Modifier.height(16.dp)) } } @@ -335,9 +342,10 @@ private fun PreviewPayinMethodDetailsDestination( PayinMethodDetailsScreen( method = method, chargingDay = 27, + isRemoving = false, navigateUp = {}, onChangeMethod = {}, - onRemoveMethod = {}, + onConfirmRemoveMethod = {}, ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt index 59caa7112d..b37765bfd2 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -11,7 +11,9 @@ import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.RemoveMethodUseCase import com.hedvig.android.feature.payin.account.data.id +import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope @@ -24,13 +26,16 @@ import dev.zacsweers.metro.AssistedInject internal class PayinMethodDetailsViewModel( @Assisted methodId: PayinMethodId, getPayinAccountUseCase: GetPayinAccountUseCase, + removeMethodUseCase: RemoveMethodUseCase, ) : MoleculeViewModel( initialState = PayinMethodDetailsUiState.Loading, - presenter = PayinMethodDetailsPresenter(methodId, getPayinAccountUseCase), + presenter = PayinMethodDetailsPresenter(methodId, getPayinAccountUseCase, removeMethodUseCase), ) internal sealed interface PayinMethodDetailsEvent { data object Retry : PayinMethodDetailsEvent + + data object RemoveMethod : PayinMethodDetailsEvent } internal sealed interface PayinMethodDetailsUiState { @@ -38,12 +43,17 @@ internal sealed interface PayinMethodDetailsUiState { data object Error : PayinMethodDetailsUiState - data class Content(val method: PayinAccount, val chargingDay: Int?) : PayinMethodDetailsUiState + data class Content( + val method: PayinAccount, + val chargingDay: Int?, + val isRemoving: Boolean = false, + ) : PayinMethodDetailsUiState } internal class PayinMethodDetailsPresenter( private val methodId: PayinMethodId, private val getPayinAccountUseCase: GetPayinAccountUseCase, + private val removeMethodUseCase: RemoveMethodUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present( @@ -51,6 +61,7 @@ internal class PayinMethodDetailsPresenter( ): PayinMethodDetailsUiState { var loadIteration by remember { mutableIntStateOf(0) } var uiState by remember { mutableStateOf(lastState) } + var methodToRemove by remember { mutableStateOf(null) } LaunchedEffect(loadIteration) { uiState = PayinMethodDetailsUiState.Loading @@ -68,9 +79,27 @@ internal class PayinMethodDetailsPresenter( ) } + LaunchedEffect(methodToRemove) { + val method = methodToRemove ?: return@LaunchedEffect + val content = uiState as? PayinMethodDetailsUiState.Content ?: return@LaunchedEffect + uiState = content.copy(isRemoving = true) + removeMethodUseCase.invoke(method.provider) + // TODO: reload the methods on success and surface the failure once the API exists. + uiState = content.copy(isRemoving = false) + methodToRemove = null + } + CollectEvents { event -> when (event) { - PayinMethodDetailsEvent.Retry -> loadIteration++ + PayinMethodDetailsEvent.Retry -> { + loadIteration++ + } + + PayinMethodDetailsEvent.RemoveMethod -> { + if (methodToRemove == null) { + methodToRemove = (uiState as? PayinMethodDetailsUiState.Content)?.method + } + } } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt new file mode 100644 index 0000000000..59633f884a --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -0,0 +1,175 @@ +package com.hedvig.android.feature.payin.account.ui.methoddetails + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.dashedBorder +import com.hedvig.android.design.system.hedvig.hedvigDropShadow +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Minus +import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow +import hedvig.resources.PAYMENT_REMOVE_SUBTITLE +import hedvig.resources.PAYMENT_REMOVE_TITLE +import hedvig.resources.REMOVE_CONFIRMATION_BUTTON +import hedvig.resources.Res +import hedvig.resources.general_cancel_button +import org.jetbrains.compose.resources.stringResource + +private val IllustrationTileSize = 74.dp +private val IllustrationMarkSize = 39.dp +private val RemoveBadgeSize = 24.dp + +@Composable +internal fun RemovePayinMethodBottomSheet( + sheetState: HedvigBottomSheetState, + isRemoving: Boolean, + onConfirmRemove: () -> Unit, +) { + HedvigBottomSheet(sheetState) { method -> + RemovePayinMethodBottomSheetContent( + method = method, + isRemoving = isRemoving, + onConfirmRemove = onConfirmRemove, + onDismiss = { sheetState.dismiss() }, + ) + } +} + +@Composable +private fun RemovePayinMethodBottomSheetContent( + method: PayinAccount, + isRemoving: Boolean, + onConfirmRemove: () -> Unit, + onDismiss: () -> Unit, +) { + HedvigText( + text = stringResource(Res.string.PAYMENT_REMOVE_TITLE), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + HedvigText( + text = stringResource(Res.string.PAYMENT_REMOVE_SUBTITLE), + textAlign = TextAlign.Center, + color = colorScheme.textSecondary, + modifier = Modifier.fillMaxWidth(), + ) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 64.dp), + ) { + RemovedMethodIllustration(method) + } + Surface( + shape = HedvigTheme.shapes.cornerLarge, + color = colorScheme.surfacePrimary, + modifier = Modifier.fillMaxWidth(), + ) { + PayinMethodRow( + method = method, + modifier = Modifier.fillMaxWidth(), + ) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.REMOVE_CONFIRMATION_BUTTON), + onClick = onConfirmRemove, + enabled = !isRemoving, + isLoading = isRemoving, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_cancel_button), + onClick = onDismiss, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) +} + +/** The method's tile drawn as an outline with a minus badge: what is about to be given up. */ +@Composable +private fun RemovedMethodIllustration(method: PayinAccount, modifier: Modifier = Modifier) { + Box(modifier) { + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = colorScheme.backgroundPrimary, + contentColor = colorScheme.fillPrimary, + modifier = Modifier + .size(IllustrationTileSize) + .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge) + .dashedBorder( + color = colorScheme.borderSecondary, + shape = HedvigTheme.shapes.cornerXXLarge, + ), + ) { + Box(Modifier.size(IllustrationTileSize), contentAlignment = Alignment.Center) { + PayinMethodMark(method, Modifier.size(IllustrationMarkSize)) + } + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .align(Alignment.TopEnd) + .offset(x = 8.dp, y = (-8).dp) + .size(RemoveBadgeSize) + .clip(CircleShape) + .background(colorScheme.fillPrimary), + ) { + Icon( + imageVector = HedvigIcons.Minus, + contentDescription = EmptyContentDescription, + tint = colorScheme.fillNegative, + modifier = Modifier.size(16.dp), + ) + } + } +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewRemovePayinMethodBottomSheetContent() { + HedvigTheme { + Surface(color = colorScheme.backgroundPrimary) { + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + RemovePayinMethodBottomSheetContent( + method = PayinAccount.SwishPayin("0709901232", isPending = false, isDefault = false), + isRemoving = false, + onConfirmRemove = {}, + onDismiss = {}, + ) + } + } + } +} From 7d56d39177b762a09f166b40cf663ef99b751d67 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 9 Sep 2026 17:06:44 +0200 Subject: [PATCH 57/75] radio option in BS --- .../account/ui/components/PayinMethodRow.kt | 17 ++++++++++++++++ .../RemovePayinMethodBottomSheet.kt | 18 ++++++++--------- .../SelectPrimaryPayinMethodDestination.kt | 20 +------------------ 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index 52b4b8575e..84daf88d62 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -18,12 +18,16 @@ import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.IconResource.Vector +import com.hedvig.android.design.system.hedvig.RadioOption +import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.data.toDeliveryString import com.hedvig.android.logger.logcat import hedvig.resources.PAYMENTS_BANK_LABEL @@ -129,6 +133,19 @@ internal fun PayinMethodRow( } } +/** The same mark, title and subtitle as [PayinMethodRow], for the screens that list methods as radio options. */ +@Composable +internal fun PayinAccount.toRadioOption(): RadioOption = RadioOption( + id = RadioOptionId(provider.rawValue), + text = payinMethodTitle(this), + label = payinMethodSubtitle(this), + iconResource = when (this) { + is PayinAccount.Trustly -> Vector(HedvigIcons.Trustly) + is PayinAccount.SwishPayin -> Vector(HedvigIcons.Swish) + is PayinAccount.Invoice -> Vector(HedvigIcons.Kivra) + }, +) + /** Static counterpart to the button the design draws it with: the primary method is not a choice made here. */ @Composable internal fun PrimaryMethodLabel(modifier: Modifier = Modifier) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index 59633f884a..ee3424f577 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -25,6 +25,7 @@ import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.dashedBorder @@ -33,7 +34,7 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Minus import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow +import com.hedvig.android.feature.payin.account.ui.components.toRadioOption import hedvig.resources.PAYMENT_REMOVE_SUBTITLE import hedvig.resources.PAYMENT_REMOVE_TITLE import hedvig.resources.REMOVE_CONFIRMATION_BUTTON @@ -87,16 +88,13 @@ private fun RemovePayinMethodBottomSheetContent( ) { RemovedMethodIllustration(method) } - Surface( - shape = HedvigTheme.shapes.cornerLarge, - color = colorScheme.surfacePrimary, + val option = method.toRadioOption() + RadioGroup( + options = listOf(option), + selectedOption = option.id, + onRadioOptionSelected = {}, modifier = Modifier.fillMaxWidth(), - ) { - PayinMethodRow( - method = method, - modifier = Modifier.fillMaxWidth(), - ) - } + ) Spacer(Modifier.height(16.dp)) HedvigButton( text = stringResource(Res.string.REMOVE_CONFIRMATION_BUTTON), diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index 8d8f02b542..83e57c9576 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -33,24 +33,18 @@ import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.IconResource.Vector import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Error import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.RadioGroup -import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.ThreeDotsLoading import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.hedvigDropShadow -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.HelipadFilled import com.hedvig.android.design.system.hedvig.icon.HelipadOutline import com.hedvig.android.design.system.hedvig.icon.Plus -import com.hedvig.android.design.system.hedvig.icon.Trustly -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra -import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.feature.payin.account.data.InvoiceDelivery import com.hedvig.android.feature.payin.account.data.PayinAccount @@ -61,8 +55,8 @@ import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel -import com.hedvig.android.feature.payin.account.ui.components.payinMethodSubtitle import com.hedvig.android.feature.payin.account.ui.components.payinMethodTitle +import com.hedvig.android.feature.payin.account.ui.components.toRadioOption import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.SelectMethod import hedvig.resources.PAYMENT_CONNECT_SUBTITLE @@ -247,18 +241,6 @@ private fun ConfirmPrimaryPayinMethodBottomSheetContent( Spacer(Modifier.height(16.dp)) } -@Composable -private fun PayinAccount.toRadioOption(): RadioOption = RadioOption( - id = RadioOptionId(provider.rawValue), - text = payinMethodTitle(this), - label = payinMethodSubtitle(this), - iconResource = when (this) { - is Trustly -> Vector(HedvigIcons.Trustly) - is SwishPayin -> Vector(HedvigIcons.Swish) - is Invoice -> Vector(HedvigIcons.Kivra) - }, -) - @Composable @HedvigShortMultiScreenPreview private fun ConfirmPrimaryPayinMethodBottomSheetPreview() { From a22dc39140ff16862b72884645fbb2b6d88bb3a7 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 10 Sep 2026 13:58:52 +0200 Subject: [PATCH 58/75] aprrove swish screen --- .../androidMain/res/values-sv-rSE/strings.xml | 22 +- .../src/androidMain/res/values/strings.xml | 18 +- .../values-sv-rSE/strings.xml | 22 +- .../composeResources/values/strings.xml | 18 +- .../account/navigation/PayinAccountEntries.kt | 1 + .../account/ui/components/PayinMethodTile.kt | 89 ++++++ .../RemovePayinMethodBottomSheet.kt | 65 +--- .../setupswish/SetupSwishPayinDestination.kt | 285 +++++++++++------- 8 files changed, 323 insertions(+), 197 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 94216c97fc..87cb56080b 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -344,7 +344,7 @@ Avslutas %1$s Se certifikat Populärt val - Upptäck fler försäkringar + Upptäck mer skydd För dig %1$s/mnd - Kort ej kopplat Aktiv @@ -645,7 +645,7 @@ Du kan byta konto senare i inställningar Koppla betalning Till appen - Du får 15\u0025 rabatt när du har två eller fler försäkringar. Gäller alla utom olycksfall + Du får 15\u0025 samlingsrabatt när du har två eller fler försäkringar Få samlingsrabatt Gör det senare När någon skaffar Hedvig med din kod får ni båda %1$s rabatt per månad @@ -724,27 +724,32 @@ Rabatt via inbjudningar Betalningsmetod Delsumma - Nummer + Telefonnummer Kommande betalning Giltig till %1$s Lägg till betalningsmetod - Välj primär metod + Välj förvald metod Lägg till en ny betalningsmetod för din försäkring Koppla betalning Betalningshistorik Betalningsmetoder Betalningsmetod - Kopplat - Direktbetalning via faktura + Öppna Swish + Kopplad + Faktura till din Kivra Betalningar och utbetalningar via Swish Anslut din bank via Trustly Bekräfta betalningsmetod - Förvalt + Förvald Den här metoden används för dina månadsbetalningar Välj betalningsmetod Du kan lägga till den igen när som helst Ta bort betalningsmetod - Change number + Godkänn i Swish-appen + Byt telefonnummer + How does recurring Swish work? + Lägg till ett betalsätt för att aktivera din försäkring + Koppla Swish till det här numret Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish @@ -1245,6 +1250,7 @@ Fördelar Läs mer Få pris + Se pris För din kropp. Gäller dygnet runt. Ingen självrisk. Beräkna ditt pris diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index 31c7c4a759..bca4ca2a21 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -344,7 +344,7 @@ Terminates on %1$s View certificate Popular choice - Discover more insurances + Discover more coverage For you %1$s/mnd - Card not connected Active @@ -645,7 +645,7 @@ You can switch accounts later in settings Connect payment method Go to the app - You get a 15\u0025 discount when you have two or more insurances with us. Applies to all except accident + You get a 15\u0025 bundle discount when you have two or more insurances with us Get bundle discount Do this later When someone gets Hedvig with your code you both get %1$s discount per month @@ -724,18 +724,19 @@ Referral discount Payment method Subtotal - Number + Phone number Upcoming payment Valid until %1$s Add payment method - Choose primary method + Change primary method Add a new payment method for your insurance Connect payment Payment history Payment methods Payment method + Open Swish Connected - Direct payment via Invoice + Invoice to your Kivra Payments and payouts via Swish Connect your bank via Trustly Confirm payment method @@ -744,7 +745,11 @@ Choose payment method You can add it back any time Remove payment method - Change number + Approve in the Swish app + Change phone number + How does recurring Swish work? + Add a payment method to activate your insurance + Connect Swish to this number Add payout method Direct payment via Invoice Instant payout with Swish @@ -1245,6 +1250,7 @@ Highlights Learn more Get price + See price For your body. 24/7 coverage. No deductible. For your trip. Worldwide coverage for 45 days. Calculate your price diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 60dccbe205..8f44b4b0cc 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -343,7 +343,7 @@ Avslutas %1$s Se certifikat Populärt val - Upptäck fler försäkringar + Upptäck mer skydd För dig %1$s/mnd - Kort ej kopplat Aktiv @@ -644,7 +644,7 @@ Du kan byta konto senare i inställningar Koppla betalning Till appen - Du får 15\u0025 rabatt när du har två eller fler försäkringar. Gäller alla utom olycksfall + Du får 15\u0025 samlingsrabatt när du har två eller fler försäkringar Få samlingsrabatt Gör det senare När någon skaffar Hedvig med din kod får ni båda %1$s rabatt per månad @@ -723,27 +723,32 @@ Rabatt via inbjudningar Betalningsmetod Delsumma - Nummer + Telefonnummer Kommande betalning Giltig till %1$s Lägg till betalningsmetod - Välj primär metod + Välj förvald metod Lägg till en ny betalningsmetod för din försäkring Koppla betalning Betalningshistorik Betalningsmetoder Betalningsmetod - Kopplat - Direktbetalning via faktura + Öppna Swish + Kopplad + Faktura till din Kivra Betalningar och utbetalningar via Swish Anslut din bank via Trustly Bekräfta betalningsmetod - Förvalt + Förvald Den här metoden används för dina månadsbetalningar Välj betalningsmetod Du kan lägga till den igen när som helst Ta bort betalningsmetod - Change number + Godkänn i Swish-appen + Byt telefonnummer + How does recurring Swish work? + Lägg till ett betalsätt för att aktivera din försäkring + Koppla Swish till det här numret Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish @@ -1244,6 +1249,7 @@ Fördelar Läs mer Få pris + Se pris För din kropp. Gäller dygnet runt. Ingen självrisk. Beräkna ditt pris diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index 5c85b6c1d4..5bf3426bb1 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -343,7 +343,7 @@ Terminates on %1$s View certificate Popular choice - Discover more insurances + Discover more coverage For you %1$s/mnd - Card not connected Active @@ -644,7 +644,7 @@ You can switch accounts later in settings Connect payment method Go to the app - You get a 15\u0025 discount when you have two or more insurances with us. Applies to all except accident + You get a 15\u0025 bundle discount when you have two or more insurances with us Get bundle discount Do this later When someone gets Hedvig with your code you both get %1$s discount per month @@ -723,18 +723,19 @@ Referral discount Payment method Subtotal - Number + Phone number Upcoming payment Valid until %1$s Add payment method - Choose primary method + Change primary method Add a new payment method for your insurance Connect payment Payment history Payment methods Payment method + Open Swish Connected - Direct payment via Invoice + Invoice to your Kivra Payments and payouts via Swish Connect your bank via Trustly Confirm payment method @@ -743,7 +744,11 @@ Choose payment method You can add it back any time Remove payment method - Change number + Approve in the Swish app + Change phone number + How does recurring Swish work? + Add a payment method to activate your insurance + Connect Swish to this number Add payout method Direct payment via Invoice Instant payout with Swish @@ -1244,6 +1249,7 @@ Highlights Learn more Get price + See price For your body. 24/7 coverage. No deductible. For your trip. Worldwide coverage for 45 days. Calculate your price diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 25d2caf384..54c70b44d5 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -123,6 +123,7 @@ fun EntryProviderScope.payinAccountEntries( globalSnackBarState = globalSnackBarState, onSuccessfullyConnected = { backstack.popUpTo(inclusive = true) }, navigateUp = backstack::navigateUp, + navigateBack = backstack::popBackstack, openUrl = { backstack.popUpTo(inclusive = true) openUrl(it) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt new file mode 100644 index 0000000000..c1968c5267 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt @@ -0,0 +1,89 @@ +package com.hedvig.android.feature.payin.account.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.dashedBorder +import com.hedvig.android.design.system.hedvig.hedvigDropShadow + +/** The size a [PayinMethodTile]'s mark is drawn at. */ +internal val PayinMethodMarkSize = 39.dp + +private val TileSize = 74.dp +private val BadgeSize = 24.dp + +/** + * The outlined tile a payin method is shown on while it is being connected or given up, with an + * optional status badge on its top-right corner. + */ +@Composable +internal fun PayinMethodTile( + modifier: Modifier = Modifier, + badge: @Composable (() -> Unit)? = null, + mark: @Composable () -> Unit, +) { + Box(modifier) { + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = HedvigTheme.colorScheme.backgroundPrimary, + contentColor = HedvigTheme.colorScheme.fillPrimary, + modifier = Modifier + .size(TileSize) + .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge) + .dashedBorder( + color = HedvigTheme.colorScheme.borderSecondary, + shape = HedvigTheme.shapes.cornerXXLarge, + ), + ) { + Box(Modifier.size(TileSize), contentAlignment = Alignment.Center) { + mark() + } + } + if (badge != null) { + Box( + Modifier + .align(Alignment.TopEnd) + .offset(x = 8.dp, y = (-8).dp), + ) { + badge() + } + } + } +} + +/** A circular status badge for [PayinMethodTile]'s corner. */ +@Composable +internal fun PayinMethodTileBadge( + icon: ImageVector, + containerColor: Color, + contentColor: Color, + modifier: Modifier = Modifier, +) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .size(BadgeSize) + .clip(CircleShape) + .background(containerColor), + ) { + Icon( + imageVector = icon, + contentDescription = EmptyContentDescription, + tint = contentColor, + modifier = Modifier.size(16.dp), + ) + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index ee3424f577..4c4b77d365 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -1,22 +1,17 @@ package com.hedvig.android.feature.payin.account.ui.methoddetails -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview @@ -24,16 +19,16 @@ import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme -import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.dashedBorder -import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Minus import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMarkSize +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTile +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTileBadge import com.hedvig.android.feature.payin.account.ui.components.toRadioOption import hedvig.resources.PAYMENT_REMOVE_SUBTITLE import hedvig.resources.PAYMENT_REMOVE_TITLE @@ -42,10 +37,6 @@ import hedvig.resources.Res import hedvig.resources.general_cancel_button import org.jetbrains.compose.resources.stringResource -private val IllustrationTileSize = 74.dp -private val IllustrationMarkSize = 39.dp -private val RemoveBadgeSize = 24.dp - @Composable internal fun RemovePayinMethodBottomSheet( sheetState: HedvigBottomSheetState, @@ -86,7 +77,16 @@ private fun RemovePayinMethodBottomSheetContent( .fillMaxWidth() .padding(vertical = 64.dp), ) { - RemovedMethodIllustration(method) + PayinMethodTile( + badge = { + PayinMethodTileBadge( + icon = HedvigIcons.Minus, + containerColor = colorScheme.fillPrimary, + contentColor = colorScheme.fillNegative, + ) + }, + mark = { PayinMethodMark(method, Modifier.size(PayinMethodMarkSize)) }, + ) } val option = method.toRadioOption() RadioGroup( @@ -112,45 +112,6 @@ private fun RemovePayinMethodBottomSheetContent( Spacer(Modifier.height(16.dp)) } -/** The method's tile drawn as an outline with a minus badge: what is about to be given up. */ -@Composable -private fun RemovedMethodIllustration(method: PayinAccount, modifier: Modifier = Modifier) { - Box(modifier) { - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.backgroundPrimary, - contentColor = colorScheme.fillPrimary, - modifier = Modifier - .size(IllustrationTileSize) - .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge) - .dashedBorder( - color = colorScheme.borderSecondary, - shape = HedvigTheme.shapes.cornerXXLarge, - ), - ) { - Box(Modifier.size(IllustrationTileSize), contentAlignment = Alignment.Center) { - PayinMethodMark(method, Modifier.size(IllustrationMarkSize)) - } - } - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .align(Alignment.TopEnd) - .offset(x = 8.dp, y = (-8).dp) - .size(RemoveBadgeSize) - .clip(CircleShape) - .background(colorScheme.fillPrimary), - ) { - Icon( - imageVector = HedvigIcons.Minus, - contentDescription = EmptyContentDescription, - tint = colorScheme.fillNegative, - modifier = Modifier.size(16.dp), - ) - } - } -} - @Composable @HedvigShortMultiScreenPreview private fun PreviewRemovePayinMethodBottomSheetContent() { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 23396486ac..4facb9feca 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -1,10 +1,9 @@ package com.hedvig.android.feature.payin.account.ui.setupswish -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -12,53 +11,65 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.runtime.simulateHotReload import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.core.common.ErrorMessage -import com.hedvig.android.design.system.hedvig.ButtonDefaults -import com.hedvig.android.design.system.hedvig.EmptyState -import com.hedvig.android.design.system.hedvig.EmptyStateDefaults import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.a11y.FlowHeading +import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMarkSize +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTile +import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTileBadge import com.hedvig.android.feature.payin.account.ui.components.formatSwishPhoneNumber import hedvig.resources.CONTACT_INFO_CHANGES_SAVED +import hedvig.resources.GENERAL_CONFIRM import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL +import hedvig.resources.PAYMENT_OPEN_SWISH_BUTTON +import hedvig.resources.PAYMENT_SWISH_APPROVE_TITLE +import hedvig.resources.PAYMENT_SWISH_EXPLANATION_BUTTON +import hedvig.resources.PAYMENT_SWISH_SUBTITLE +import hedvig.resources.PAYMENT_SWISH_TITLE import hedvig.resources.Res -import hedvig.resources.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION -import hedvig.resources.general_save_button +import hedvig.resources.general_cancel_button import hedvig.resources.something_went_wrong -import hedvig.resources.swish import org.jetbrains.compose.resources.stringResource @Composable @@ -67,10 +78,11 @@ internal fun SetupSwishPayinDestination( globalSnackBarState: GlobalSnackBarState, onSuccessfullyConnected: () -> Unit, navigateUp: () -> Unit, + navigateBack: () -> Unit, openUrl: (String) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - SetupSwishPayoutScreen( + SetupSwishPayinScreen( uiState = uiState, globalSnackBarState = globalSnackBarState, onSave = { viewModel.emit(SetupSwishPayoutEvent.Save) }, @@ -79,7 +91,11 @@ internal fun SetupSwishPayinDestination( onSuccessfullyConnected() }, navigateUp = navigateUp, + navigateBack = navigateBack, openUrl = openUrl, + onLearnMoreAboutRecurringSwish = { + // TODO: open the recurring-Swish explanation once we know where it lives. + }, updateText = { viewModel.emit(SetupSwishPayoutEvent.UpdateText(it)) }, @@ -89,16 +105,17 @@ internal fun SetupSwishPayinDestination( // todo fetch payment methods continuously to see if it already not in pending state @Composable -private fun SetupSwishPayoutScreen( +private fun SetupSwishPayinScreen( uiState: SetupSwishPayoutUiState, globalSnackBarState: GlobalSnackBarState, onSave: () -> Unit, showedSnackBar: () -> Unit, navigateUp: () -> Unit, + navigateBack: () -> Unit, openUrl: (String) -> Unit, + onLearnMoreAboutRecurringSwish: () -> Unit, updateText: (String) -> Unit, ) { - val focusManager = LocalFocusManager.current val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) LaunchedEffect(uiState.showSuccessSnackBar) { if (!uiState.showSuccessSnackBar) return@LaunchedEffect @@ -106,116 +123,148 @@ private fun SetupSwishPayoutScreen( showedSnackBar() } + val successUrl = uiState.successUrl HedvigScaffold( - topAppBarText = stringResource(Res.string.swish), + topAppBarText = null, navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { + Spacer(Modifier.height(8.dp)) + FlowHeading( + title = if (successUrl == null) { + stringResource(Res.string.PAYMENT_SWISH_TITLE) + } else { + stringResource(Res.string.PAYMENT_SWISH_APPROVE_TITLE) + }, + description = if (successUrl == null) { + stringResource(Res.string.PAYMENT_SWISH_SUBTITLE) + } else { + null + }, + baseStyle = HedvigTheme.typography.bodySmall, + modifier = Modifier.padding(horizontal = 16.dp), + ) Spacer(Modifier.weight(1f)) - if (uiState.error != null) { - EmptyState( - text = stringResource(Res.string.something_went_wrong), - description = uiState.error.message, - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.ERROR, - ) - } - AnimatedVisibility(uiState.successUrl != null) { - if (uiState.successUrl != null) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - EmptyState( - text = "Please give your approval in the Swish app", // todo - description = null, - modifier = Modifier.fillMaxWidth(), - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.SWISH, - ) - Spacer(Modifier.height(16.dp)) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Surface( - color = Color.White, - shape = HedvigTheme.shapes.cornerMedium, - border = HedvigTheme.colorScheme.borderPrimary, - modifier = Modifier.padding(16.dp), - ) { - QRCode( - token = uiState.successUrl, - modifier = Modifier - .size(180.dp) - .padding(16.dp), - ) - } - } - Spacer(Modifier.height(16.dp)) - HedvigButton( - "Open Swish", - enabled = true, - onClick = { - openUrl(uiState.successUrl) - }, - buttonStyle = ButtonDefaults.ButtonStyle.PrimaryAlt, - buttonSize = ButtonDefaults.ButtonSize.Medium, + PayinMethodTile( + modifier = Modifier.align(Alignment.CenterHorizontally), + badge = if (successUrl == null) { + null + } else { + { + PayinMethodTileBadge( + icon = HedvigIcons.ArrowNorthEast, + containerColor = HedvigTheme.colorScheme.signalBlueElement, + contentColor = HedvigTheme.colorScheme.fillWhite, ) } - } - } + }, + mark = { + Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(PayinMethodMarkSize)) + }, + ) Spacer(Modifier.weight(1f)) - AnimatedVisibility(uiState.successUrl == null) { - Column { - Spacer(Modifier.height(16.dp)) - Column(Modifier.padding(horizontal = 16.dp)) { - var input by remember { mutableStateOf(uiState.phoneNumber) } - val mask = "000-000-00-00" - val maskColor = HedvigTheme.colorScheme.textTertiary - val visualTransformation = ChipIdVisualTransformation(mask, maskColor) - val interactionSource = remember { MutableInteractionSource() } - HedvigTextField( - text = input, - labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), - textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Phone, - ), - modifier = Modifier.fillMaxWidth(), - visualTransformation = visualTransformation, - errorState = HedvigTextFieldDefaults.ErrorState.NoError, - interactionSource = interactionSource, - onValueChange = { - val digitsOnly = it.filter { char -> char.isDigit() } - if (digitsOnly.length <= 15) { - updateText(digitsOnly) - input = digitsOnly - } - }, - ) - } - Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(Res.string.general_save_button), - onClick = { - focusManager.clearFocus() - onSave() - }, - enabled = !uiState.isLoading && - uiState.phoneNumber.length >= 8 && - uiState.phoneNumber.length <= 15, - isLoading = uiState.isLoading, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - } + if (uiState.error != null) { + HedvigNotificationCard( + message = uiState.error.message ?: stringResource(Res.string.something_went_wrong), + priority = NotificationPriority.Error, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + } + if (successUrl == null) { + EnterPhoneNumberSection( + uiState = uiState, + onSave = onSave, + onLearnMoreAboutRecurringSwish = onLearnMoreAboutRecurringSwish, + updateText = updateText, + ) + } else { + HedvigButton( + text = stringResource(Res.string.PAYMENT_OPEN_SWISH_BUTTON), + onClick = { openUrl(successUrl) }, + enabled = true, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_cancel_button), + onClick = navigateBack, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) } - Spacer(Modifier.height(16.dp)) } } -private class ChipIdVisualTransformation( +@Composable +private fun ColumnScope.EnterPhoneNumberSection( + uiState: SetupSwishPayoutUiState, + onSave: () -> Unit, + onLearnMoreAboutRecurringSwish: () -> Unit, + updateText: (String) -> Unit, +) { + val focusManager = LocalFocusManager.current + HedvigText( + text = stringResource(Res.string.PAYMENT_SWISH_EXPLANATION_BUTTON), + style = HedvigTheme.typography.label, + color = HedvigTheme.colorScheme.textSecondary, + textAlign = TextAlign.Center, + textDecoration = TextDecoration.Underline, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .clip(HedvigTheme.shapes.cornerSmall) + .clickable(onClick = onLearnMoreAboutRecurringSwish) + .padding(horizontal = 8.dp, vertical = 4.dp), + ) + Spacer(Modifier.height(16.dp)) + var input by remember { mutableStateOf(uiState.phoneNumber) } + val visualTransformation = SwishPhoneNumberVisualTransformation( + mask = "000-000-00-00", + maskColor = HedvigTheme.colorScheme.textTertiary, + ) + HedvigTextField( + text = input, + labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), + textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + visualTransformation = visualTransformation, + errorState = HedvigTextFieldDefaults.ErrorState.NoError, + interactionSource = remember { MutableInteractionSource() }, + onValueChange = { + val digitsOnly = it.filter { char -> char.isDigit() } + if (digitsOnly.length <= 15) { + updateText(digitsOnly) + input = digitsOnly + } + }, + ) + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.GENERAL_CONFIRM), + onClick = { + focusManager.clearFocus() + onSave() + }, + enabled = !uiState.isLoading && + uiState.phoneNumber.length >= 8 && + uiState.phoneNumber.length <= 15, + isLoading = uiState.isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) +} + +private class SwishPhoneNumberVisualTransformation( private val mask: String, private val maskColor: Color, ) : VisualTransformation { @@ -253,20 +302,22 @@ private class ChipIdVisualTransformation( } @Composable -@HedvigPreview +@HedvigShortMultiScreenPreview private fun PreviewSetupSwishPayinScreen( @PreviewParameter(SetupSwishPayinUiStateProvider::class) uiState: SetupSwishPayoutUiState, ) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - SetupSwishPayoutScreen( + SetupSwishPayinScreen( uiState = uiState, globalSnackBarState = GlobalSnackBarState(), onSave = {}, showedSnackBar = {}, navigateUp = {}, - {}, - {}, + navigateBack = {}, + openUrl = {}, + onLearnMoreAboutRecurringSwish = {}, + updateText = {}, ) } } From fe82198b50f846d49bf885d7811b686ce0dc38ca Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 10 Sep 2026 14:17:48 +0200 Subject: [PATCH 59/75] explanation bottom sheet --- .../RecurringSwishExplanationBottomSheet.kt | 119 ++++++++++++++++++ .../setupswish/SetupSwishPayinDestination.kt | 17 ++- 2 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/RecurringSwishExplanationBottomSheet.kt diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/RecurringSwishExplanationBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/RecurringSwishExplanationBottomSheet.kt new file mode 100644 index 0000000000..99032ae3b8 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/RecurringSwishExplanationBottomSheet.kt @@ -0,0 +1,119 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import hedvig.resources.PAYMENT_SWISH_EXPLANATION_BUTTON +import hedvig.resources.Res +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun RecurringSwishExplanationBottomSheet(sheetState: HedvigBottomSheetState, onLearnMore: () -> Unit) { + HedvigBottomSheet(sheetState) { + RecurringSwishExplanationContent( + onLearnMore = onLearnMore, + onDismiss = { sheetState.dismiss() }, + ) + } +} + +@Composable +private fun RecurringSwishExplanationContent(onLearnMore: () -> Unit, onDismiss: () -> Unit) { + HedvigText( + text = stringResource(Res.string.PAYMENT_SWISH_EXPLANATION_BUTTON), + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp), + ) + Column( + verticalArrangement = Arrangement.spacedBy(24.dp), + modifier = Modifier.fillMaxWidth(), + ) { + ExplanationItem( + // TODO: Add "Paid automatically" / "Betalas automatiskt" to Lokalise + title = "Paid automatically", + // TODO: Add "Your premium is then charged automatically each month, no monthly approval needed." / + // "Din premie dras sedan automatiskt varje månad, utan att du behöver godkänna varje gång." to Lokalise + description = "Your premium is then charged automatically each month, no monthly approval needed.", + ) + ExplanationItem( + // TODO: Add "Get paid instantly" / "Få betalt direkt" to Lokalise + title = "Get paid instantly", + // TODO: Add "When a claim is approved, your compensation lands in your account right away via Swish." / + // "När en skadeanmälan godkänns landar din ersättning direkt på ditt konto via Swish." to Lokalise + description = "When a claim is approved, your compensation lands in your account right away via Swish.", + ) + ExplanationItem( + // TODO: Add "Get notified" / "Få påminnelser" to Lokalise + title = "Get notified", + // TODO: Add "We'll send you a reminder a few days before each monthly payment." / + // "Vi skickar en påminnelse några dagar före varje månadsbetalning." to Lokalise + description = "We'll send you a reminder a few days before each monthly payment.", + ) + ExplanationItem( + // TODO: Add "Change anytime" / "Ändra när du vill" to Lokalise + title = "Change anytime", + // TODO: Add "You can change or remove Swish anytime in Settings." / + // "Du kan ändra eller ta bort Swish när som helst i inställningarna." to Lokalise + description = "You can change or remove Swish anytime in Settings.", + ) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + // TODO: Add "Learn more" / "Läs mer" to Lokalise + text = "Learn more", + onClick = onLearnMore, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + HedvigButton( + // TODO: Add "Got it" / "Okej" to Lokalise + text = "Got it", + onClick = onDismiss, + enabled = true, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(32.dp)) +} + +@Composable +private fun ExplanationItem(title: String, description: String) { + Column(Modifier.fillMaxWidth()) { + HedvigText(title) + HedvigText(text = description, color = HedvigTheme.colorScheme.textSecondary) + } +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewRecurringSwishExplanationContent() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + Column( + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + RecurringSwishExplanationContent(onLearnMore = {}, onDismiss = {}) + } + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 4facb9feca..fc0d82a1c4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -55,6 +55,8 @@ import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.show import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMarkSize import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTile import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTileBadge @@ -93,9 +95,6 @@ internal fun SetupSwishPayinDestination( navigateUp = navigateUp, navigateBack = navigateBack, openUrl = openUrl, - onLearnMoreAboutRecurringSwish = { - // TODO: open the recurring-Swish explanation once we know where it lives. - }, updateText = { viewModel.emit(SetupSwishPayoutEvent.UpdateText(it)) }, @@ -113,7 +112,6 @@ private fun SetupSwishPayinScreen( navigateUp: () -> Unit, navigateBack: () -> Unit, openUrl: (String) -> Unit, - onLearnMoreAboutRecurringSwish: () -> Unit, updateText: (String) -> Unit, ) { val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) @@ -123,6 +121,14 @@ private fun SetupSwishPayinScreen( showedSnackBar() } + val explanationSheetState = rememberHedvigBottomSheetState() + RecurringSwishExplanationBottomSheet( + sheetState = explanationSheetState, + onLearnMore = { + // TODO: point this at the recurring-Swish article once we know where it lives. + }, + ) + val successUrl = uiState.successUrl HedvigScaffold( topAppBarText = null, @@ -177,7 +183,7 @@ private fun SetupSwishPayinScreen( EnterPhoneNumberSection( uiState = uiState, onSave = onSave, - onLearnMoreAboutRecurringSwish = onLearnMoreAboutRecurringSwish, + onLearnMoreAboutRecurringSwish = { explanationSheetState.show() }, updateText = updateText, ) } else { @@ -316,7 +322,6 @@ private fun PreviewSetupSwishPayinScreen( navigateUp = {}, navigateBack = {}, openUrl = {}, - onLearnMoreAboutRecurringSwish = {}, updateText = {}, ) } From 94c4b7921a97a1dd700a695b0f72e77e3989fc15 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 11 Sep 2026 09:20:27 +0200 Subject: [PATCH 60/75] onboarding entry point --- .../system/hedvig/PaymentMethodHandover.kt | 150 +++++++++++++++++ .../feature-onboarding/build.gradle.kts | 1 + .../src/main/graphql/OnboardingQuery.graphql | 4 + .../feature/onboarding/data/OnboardingData.kt | 10 ++ .../onboarding/data/OnboardingRepository.kt | 13 ++ .../navigation/OnboardingNavigator.kt | 18 +- .../OnboardingConnectingPaymentSymbol.kt | 155 +++++++----------- .../payment/OnboardingPaymentDestination.kt | 96 ++++++++++- .../onboarding/FakeOnboardingRepository.kt | 2 + .../onboarding/data/OnboardingPathTest.kt | 2 + .../payment/OnboardingPaymentPresenterTest.kt | 54 +++++- .../account/navigation/PayinAccountKey.kt | 4 + .../navigation/PayinAccountDestination.kt | 3 - .../account/navigation/PayinAccountEntries.kt | 12 +- .../PayinMethodHandoverIllustration.kt | 98 +---------- .../account/ui/components/PayinMethodRow.kt | 21 ++- .../account/ui/components/PayinMethodTile.kt | 89 ---------- .../RemovePayinMethodBottomSheet.kt | 12 +- .../setupswish/SetupSwishPayinDestination.kt | 12 +- 19 files changed, 442 insertions(+), 314 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt delete mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt new file mode 100644 index 0000000000..49e0b63087 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt @@ -0,0 +1,150 @@ +package com.hedvig.android.design.system.hedvig + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Plus +import hedvig.resources.Res +import hedvig.resources.pillow_new_680 +import org.jetbrains.compose.resources.painterResource + +/** The size a [PaymentMethodTile]'s mark is drawn at. */ +val PaymentMethodMarkSize = 39.dp + +private val TileSize = 74.dp +private val BadgeSize = 24.dp + +/** + * The outlined tile a payment method is shown on while it is being connected or given up, with an + * optional status badge on its top-right corner. [mark] is the method's brand mark, drawn at + * [PaymentMethodMarkSize]; it inherits the tile's content colour, so a monochrome mark reads + * correctly in both themes. + */ +@Composable +fun PaymentMethodTile( + modifier: Modifier = Modifier, + badge: @Composable (() -> Unit)? = null, + mark: @Composable () -> Unit = { PaymentMethodPlusMark(Modifier.size(PaymentMethodMarkSize)) }, +) { + Box(modifier) { + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = HedvigTheme.colorScheme.backgroundPrimary, + contentColor = HedvigTheme.colorScheme.fillPrimary, + modifier = Modifier + .size(TileSize) + .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge) + .dashedBorder( + color = HedvigTheme.colorScheme.borderSecondary, + shape = HedvigTheme.shapes.cornerXXLarge, + ), + ) { + Box(Modifier.size(TileSize), contentAlignment = Alignment.Center) { + mark() + } + } + if (badge != null) { + Box( + Modifier + .align(Alignment.TopEnd) + .offset(x = 8.dp, y = (-8).dp), + ) { + badge() + } + } + } +} + +/** The mark a [PaymentMethodTile] shows before a method has been picked. */ +@Composable +fun PaymentMethodPlusMark(modifier: Modifier = Modifier) { + Icon( + imageVector = HedvigIcons.Plus, + contentDescription = null, + tint = HedvigTheme.colorScheme.fillSecondary, + modifier = modifier, + ) +} + +/** A circular status badge for the corner of a [PaymentMethodTile] or the Hedvig symbol. */ +@Composable +fun PaymentMethodTileBadge( + icon: ImageVector, + containerColor: Color, + contentColor: Color, + modifier: Modifier = Modifier, +) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .size(BadgeSize) + .clip(CircleShape) + .background(containerColor), + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(16.dp), + ) + } +} + +/** + * The method being connected, linked by pulsing dots to the Hedvig symbol. [destinationBadge] marks + * the symbol once the hand-off has landed. + */ +@Composable +fun PaymentMethodHandoverIllustration( + modifier: Modifier = Modifier, + destinationBadge: @Composable (() -> Unit)? = null, + mark: @Composable () -> Unit = { PaymentMethodPlusMark(Modifier.size(PaymentMethodMarkSize)) }, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + PaymentMethodTile(mark = mark) + ThreeDotsLoading() + Box { + Image( + painter = painterResource(Res.drawable.pillow_new_680), + contentDescription = null, + modifier = Modifier.size(TileSize), + ) + if (destinationBadge != null) { + Box( + Modifier + .align(Alignment.TopEnd) + .offset(x = 6.dp, y = (-6).dp), + ) { + destinationBadge() + } + } + } + } +} + +@HedvigPreview +@Composable +private fun PreviewPaymentMethodHandoverIllustration() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + PaymentMethodHandoverIllustration(modifier = Modifier.size(width = 210.dp, height = 74.dp)) + } + } +} diff --git a/app/feature/feature-onboarding/build.gradle.kts b/app/feature/feature-onboarding/build.gradle.kts index fe7e6f5b49..826b509d26 100644 --- a/app/feature/feature-onboarding/build.gradle.kts +++ b/app/feature/feature-onboarding/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.dataSettingsDatastorePublic) implementation(projects.designSystemHedvig) implementation(projects.dataCoinsured) + implementation(projects.featureConnectPaymentTrustlyNavigation) implementation(projects.featureEditCoinsuredNavigation) implementation(projects.featureFlags) implementation(projects.featurePayinAccountNavigation) diff --git a/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql b/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql index 0b621b77e4..fabae8fe86 100644 --- a/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql +++ b/app/feature/feature-onboarding/src/main/graphql/OnboardingQuery.graphql @@ -38,6 +38,10 @@ query Onboarding { status isDefault } + availableMethods { + provider + supportsPayin + } } crossSellV2(input: { userFlow: ONBOARDING, flowSource: null, experiments: [] }) { otherCrossSells { diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingData.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingData.kt index 073b8a9b93..5bea5d9baa 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingData.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingData.kt @@ -12,6 +12,7 @@ internal data class OnboardingData( val contracts: List, val referralInformation: OnboardingReferralInformation?, val payinStatus: OnboardingPayinStatus, + val availablePayinProviders: List, val crossSells: List, ) { /** Contracts missing co-insured OR co-owner info, i.e. anything the co-insured step should show. */ @@ -32,6 +33,15 @@ internal data class OnboardingData( val hasConnectedPayinMethod: Boolean = payinStatus != OnboardingPayinStatus.NeedsSetup } +/** + * The payin providers onboarding can connect. Only these two are offered during onboarding, so a + * provider the backend adds later is left out until this step knows how to hand off to it. + */ +internal enum class OnboardingPayinProvider { + Trustly, + Swish, +} + /** Mirrors the backend `MemberPaymentConnectionStatus`. */ internal enum class OnboardingPayinStatus { Active, diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt index 02d6937dea..435f92f189 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingRepository.kt @@ -15,6 +15,7 @@ import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject import octopus.OnboardingQuery import octopus.OnboardingUpdateContactInfoMutation +import octopus.type.MemberPaymentProvider internal interface OnboardingRepository { suspend fun getOnboardingData(): Either @@ -97,6 +98,12 @@ internal class OnboardingRepositoryImpl( else -> OnboardingPayinStatus.NeedsSetup } }, + availablePayinProviders = member.paymentMethods.availableMethods + .filter { it.supportsPayin } + .mapNotNull { it.provider.toOnboardingPayinProvider() } + // TODO: the backend does not list Swish or Trustly as available payin methods yet, so both + // are offered unconditionally. Delete this fallback once it does. + .ifEmpty { listOf(OnboardingPayinProvider.Trustly, OnboardingPayinProvider.Swish) }, crossSells = member.crossSellV2.otherCrossSells.map { crossSell -> OnboardingCrossSell( id = crossSell.id, @@ -119,3 +126,9 @@ internal class OnboardingRepositoryImpl( ensure(userError == null) { ErrorMessage(userError?.message) } } } + +private fun MemberPaymentProvider.toOnboardingPayinProvider(): OnboardingPayinProvider? = when (this) { + MemberPaymentProvider.TRUSTLY -> OnboardingPayinProvider.Trustly + MemberPaymentProvider.SWISH -> OnboardingPayinProvider.Swish + else -> null +} diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt index a5a78c46a7..eb67edd880 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/navigation/OnboardingNavigator.kt @@ -2,10 +2,12 @@ package com.hedvig.android.feature.onboarding.navigation import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.data.coinsured.CoInsuredFlowType +import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyKey import com.hedvig.android.feature.editcoinsured.navigation.CoInsuredAddInfoKey import com.hedvig.android.feature.onboarding.data.CompleteOnboardingUseCase +import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import com.hedvig.android.feature.onboarding.data.OnboardingSessionStore -import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey +import com.hedvig.android.feature.payin.account.navigation.SetupSwishPayinKey import com.hedvig.android.logger.logcat import com.hedvig.android.navigation.compose.Backstack import com.hedvig.android.navigation.compose.add @@ -66,9 +68,17 @@ internal class OnboardingNavigator( backstack.add(CoInsuredAddInfoKey(contractId, type)) } - /** Pushes the payin account overview, where the member picks a payin method. */ - fun openConnectPayment() { - backstack.add(PayinAccountKey) + /** + * Pushes the setup screen for the provider the member picked on the connect-payment step. Each + * screen pops itself when it is done, landing back on the step, which re-reads the live payin + * status on resume. + */ + fun openPayinSetup(provider: OnboardingPayinProvider) { + val key = when (provider) { + OnboardingPayinProvider.Trustly -> TrustlyKey + OnboardingPayinProvider.Swish -> SetupSwishPayinKey + } + backstack.add(key) } /** Pushes the shared Forever screen; the member leaves it with system back. */ diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt index b46a1c4cf2..e5d7c49861 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt @@ -5,59 +5,52 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.HedvigPreview -import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.PaymentMethodHandoverIllustration +import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize +import com.hedvig.android.design.system.hedvig.PaymentMethodPlusMark +import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.ThreeDotsLoading -import com.hedvig.android.design.system.hedvig.dashedBorder -import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.Checkmark import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.tokens.MotionTokens -import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL -import hedvig.resources.Res -import hedvig.resources.pillow_new_680 +import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.delay -import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.stringResource - -private val SymbolSize = 74.dp -private val CheckBadgeSize = 24.dp // How long after the graphic appears the connected check pops in. private const val CheckPopDelayMillis = 400L /** - * The graphic on the connect-payment step: a "Bank" card linked by pulsing dots to the Hedvig - * symbol. It shows from the start; while [showCheck] is true (a payment method is connected) a green - * checkmark pops onto the symbol. [showCheck] tracks the live status directly, so the check appears - * only while the backend actually reports a connected method. The dots pulse continuously. + * The graphic on the connect-payment step: the method the member picked, linked by pulsing dots to + * the Hedvig symbol. While [showCheck] is true (a payment method is connected) a green checkmark + * pops onto the symbol. [showCheck] tracks the live status directly, so the check appears only while + * the backend actually reports a connected method. */ @Composable -internal fun OnboardingConnectingPaymentSymbol(showCheck: Boolean, modifier: Modifier = Modifier) { +internal fun OnboardingConnectingPaymentSymbol( + provider: OnboardingPayinProvider?, + showCheck: Boolean, + modifier: Modifier = Modifier, +) { // In a static preview the pop's delay never elapses, so seed the check as already shown there to // keep the connected states visible; at runtime it starts hidden and pops in. val inPreview = LocalInspectionMode.current @@ -70,46 +63,15 @@ internal fun OnboardingConnectingPaymentSymbol(showCheck: Boolean, modifier: Mod delay(CheckPopDelayMillis.milliseconds) checkVisible = true } - ConnectingGraphic(checkVisible = checkVisible, modifier = modifier) -} - -@Composable -private fun ConnectingGraphic(checkVisible: Boolean, modifier: Modifier = Modifier) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - BankCard() - ThreeDotsLoading() - HedvigSymbolWithCheck(checkVisible = checkVisible) - } -} - -@Composable -private fun BankCard() { - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = HedvigTheme.colorScheme.backgroundPrimary, - modifier = Modifier - .size(SymbolSize) - .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge) - .dashedBorder( - color = HedvigTheme.colorScheme.borderSecondary, - shape = HedvigTheme.shapes.cornerXXLarge, - ), - ) { - Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { - HedvigText( - text = stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL), - style = HedvigTheme.typography.bodySmall.copy(fontFamily = HedvigTheme.typography.serif), - ) - } - } + ConnectingGraphic(provider = provider, checkVisible = checkVisible, modifier = modifier) } @Composable -private fun HedvigSymbolWithCheck(checkVisible: Boolean) { +private fun ConnectingGraphic( + provider: OnboardingPayinProvider?, + checkVisible: Boolean, + modifier: Modifier = Modifier, +) { val checkScale by animateFloatAsState( targetValue = if (checkVisible) 1f else 0f, animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMediumLow), @@ -120,52 +82,55 @@ private fun HedvigSymbolWithCheck(checkVisible: Boolean) { animationSpec = tween(MotionTokens.DurationShort4.toInt(), easing = MotionTokens.EasingStandardCubicBezier), label = "connected check alpha", ) - Box { - Image( - painter = painterResource(Res.drawable.pillow_new_680), - contentDescription = null, - modifier = Modifier.size(SymbolSize), - ) - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .align(Alignment.TopEnd) - .offset(x = 6.dp, y = (-6).dp) - .size(CheckBadgeSize) - .graphicsLayer { + PaymentMethodHandoverIllustration( + modifier = modifier, + destinationBadge = { + PaymentMethodTileBadge( + icon = HedvigIcons.Checkmark, + containerColor = HedvigTheme.colorScheme.signalGreenElement, + contentColor = HedvigTheme.colorScheme.fillWhite, + modifier = Modifier.graphicsLayer { scaleX = checkScale scaleY = checkScale alpha = checkAlpha - } - .clip(CircleShape) - .background(HedvigTheme.colorScheme.signalGreenElement), - ) { - Icon( - imageVector = HedvigIcons.Checkmark, - contentDescription = null, - tint = HedvigTheme.colorScheme.fillWhite, - modifier = Modifier.size(16.dp), + }, ) - } - } + }, + mark = { OnboardingPayinProviderMark(provider) }, + ) } -@HedvigPreview @Composable -private fun PreviewOnboardingConnectingPaymentSymbolConnected() { - HedvigTheme { - Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - ConnectingGraphic(checkVisible = true, modifier = Modifier.size(width = 210.dp, height = 74.dp)) - } +private fun OnboardingPayinProviderMark(provider: OnboardingPayinProvider?) { + val markModifier = Modifier.size(PaymentMethodMarkSize) + when (provider) { + OnboardingPayinProvider.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, markModifier) + OnboardingPayinProvider.Swish -> Image(HedvigIcons.Swish, EmptyContentDescription, markModifier) + null -> PaymentMethodPlusMark(markModifier) } } @HedvigPreview @Composable -private fun PreviewOnboardingConnectingPaymentSymbolInitial() { +private fun PreviewOnboardingConnectingPaymentSymbol( + @PreviewParameter(ConnectingSymbolProvider::class) provider: OnboardingPayinProvider?, +) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - ConnectingGraphic(checkVisible = false, modifier = Modifier.size(width = 210.dp, height = 74.dp)) + ConnectingGraphic( + provider = provider, + checkVisible = provider != null, + modifier = Modifier.size(width = 210.dp, height = 74.dp), + ) } } } + +private class ConnectingSymbolProvider : + CollectionPreviewParameterProvider( + listOf( + null, + OnboardingPayinProvider.Trustly, + OnboardingPayinProvider.Swish, + ), + ) diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt index c59f4677ef..f7f44f5706 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt @@ -1,7 +1,9 @@ package com.hedvig.android.feature.onboarding.ui.payment import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -12,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp @@ -22,8 +25,17 @@ import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced import com.hedvig.android.design.system.hedvig.HedvigPreview +import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.IconResource +import com.hedvig.android.design.system.hedvig.RadioGroup +import com.hedvig.android.design.system.hedvig.RadioOption +import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import com.hedvig.android.feature.onboarding.data.OnboardingPayinStatus import com.hedvig.android.feature.onboarding.data.OnboardingSessionStore import com.hedvig.android.feature.onboarding.navigation.OnboardingNavigator @@ -38,13 +50,17 @@ import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel import dev.zacsweers.metro.Inject -import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_FOOTNOTE +import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_SUBTITLE import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_SWITCH_ACCOUNTS_LATER import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_TITLE import hedvig.resources.ONBOARDING_DO_THIS_LATER_BUTTON +import hedvig.resources.PAYMENT_CONNECT_TITLE +import hedvig.resources.PAYMENT_OPTION_SWISH_SUBTITLE +import hedvig.resources.PAYMENT_OPTION_TRUSTLY_SUBTITLE import hedvig.resources.Res import hedvig.resources.general_continue_button +import hedvig.resources.swish import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource @@ -70,6 +86,7 @@ internal class OnboardingPaymentPresenter( var currentState by remember { mutableStateOf(lastState) } var loadIteration by remember { mutableIntStateOf(0) } var hasAttemptedToConnect by remember { mutableStateOf(false) } + var selectedProvider by remember { mutableStateOf(null) } LaunchedEffect(loadIteration) { if (currentState is OnboardingPaymentUiState.Content) return@LaunchedEffect @@ -80,6 +97,7 @@ internal class OnboardingPaymentPresenter( currentState = OnboardingPaymentUiState.Content( progress = session.progressFor(OnboardingStepId.ConnectPayment), payinStatus = session.data.payinStatus, + availableProviders = session.data.availablePayinProviders, ) }, ) @@ -101,14 +119,22 @@ internal class OnboardingPaymentPresenter( currentState = OnboardingPaymentUiState.Content( progress = refreshed.progressFor(OnboardingStepId.ConnectPayment), payinStatus = refreshed.data.payinStatus, + availableProviders = refreshed.data.availablePayinProviders, ) } } } + is OnboardingPaymentEvent.SelectProvider -> { + selectedProvider = event.provider + } + OnboardingPaymentEvent.ConnectPayment -> { - hasAttemptedToConnect = true - navigator.openConnectPayment() + val provider = selectedProvider + if (provider != null) { + hasAttemptedToConnect = true + navigator.openPayinSetup(provider) + } } OnboardingPaymentEvent.Continue -> { @@ -118,7 +144,11 @@ internal class OnboardingPaymentPresenter( } return when (val state = currentState) { - is OnboardingPaymentUiState.Content -> state.copy(hasAttemptedToConnect = hasAttemptedToConnect) + is OnboardingPaymentUiState.Content -> state.copy( + hasAttemptedToConnect = hasAttemptedToConnect, + selectedProvider = selectedProvider, + ) + else -> state } } @@ -132,6 +162,8 @@ internal sealed interface OnboardingPaymentUiState { data class Content( val progress: OnboardingProgress, val payinStatus: OnboardingPayinStatus, + val availableProviders: List, + val selectedProvider: OnboardingPayinProvider? = null, // The skip is offered only once the member has entered the connect flow at least once, so a // member who tries and does not finish is never stuck on this step. val hasAttemptedToConnect: Boolean = false, @@ -145,6 +177,8 @@ internal sealed interface OnboardingPaymentEvent { data object Refresh : OnboardingPaymentEvent + data class SelectProvider(val provider: OnboardingPayinProvider) : OnboardingPaymentEvent + data object ConnectPayment : OnboardingPaymentEvent data object Continue : OnboardingPaymentEvent @@ -170,6 +204,7 @@ internal fun OnboardingPaymentDestination(viewModel: OnboardingPaymentViewModel, navigateUp = navigateUp, onClose = { viewModel.emit(OnboardingPaymentEvent.Close) }, onRetry = { viewModel.emit(OnboardingPaymentEvent.Retry) }, + onProviderSelected = { viewModel.emit(OnboardingPaymentEvent.SelectProvider(it)) }, onConnectPayment = { viewModel.emit(OnboardingPaymentEvent.ConnectPayment) }, onContinue = { viewModel.emit(OnboardingPaymentEvent.Continue) }, ) @@ -182,6 +217,7 @@ private fun OnboardingPaymentScreen( navigateUp: () -> Unit, onClose: () -> Unit, onRetry: () -> Unit, + onProviderSelected: (OnboardingPayinProvider) -> Unit, onConnectPayment: () -> Unit, onContinue: () -> Unit, ) { @@ -219,23 +255,48 @@ private fun OnboardingPaymentScreen( ) Spacer(Modifier.weight(1f)) OnboardingConnectingPaymentSymbol( + provider = content.selectedProvider, showCheck = isConnected, modifier = Modifier.align(Alignment.CenterHorizontally), ) Spacer(Modifier.weight(1f)) + if (!isConnected) { + HedvigText( + // TODO: Add "Required to keep your insurance active" / + // "Krävs för att din försäkring ska vara aktiv" to Lokalise + text = "Required to keep your insurance active", + style = HedvigTheme.typography.label, + color = HedvigTheme.colorScheme.textSecondaryTranslucent, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + RadioGroup( + options = content.availableProviders.map { it.toRadioOption() }, + selectedOption = content.selectedProvider?.let { RadioOptionId(it.name) }, + onRadioOptionSelected = { id -> + val provider = content.availableProviders.firstOrNull { it.name == id.id } + if (provider != null) onProviderSelected(provider) + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } OnboardingStepButtons( primaryText = if (isConnected) { stringResource(Res.string.general_continue_button) } else { - stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_TITLE) + stringResource(Res.string.PAYMENT_CONNECT_TITLE) }, onPrimaryClick = if (isConnected) onContinue else onConnectPayment, + primaryEnabled = isConnected || content.selectedProvider != null, secondaryText = if (canSkip) stringResource(Res.string.ONBOARDING_DO_THIS_LATER_BUTTON) else null, onSecondaryClick = if (canSkip) onContinue else null, caption = if (isConnected) { stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_SWITCH_ACCOUNTS_LATER) } else { - stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_FOOTNOTE) + null }, ) } @@ -243,6 +304,23 @@ private fun OnboardingPaymentScreen( } } +@Composable +private fun OnboardingPayinProvider.toRadioOption(): RadioOption = when (this) { + OnboardingPayinProvider.Trustly -> RadioOption( + id = RadioOptionId(name), + text = stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL), + label = stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE), + iconResource = IconResource.Vector(HedvigIcons.Trustly), + ) + + OnboardingPayinProvider.Swish -> RadioOption( + id = RadioOptionId(name), + text = stringResource(Res.string.swish), + label = stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE), + iconResource = IconResource.Vector(HedvigIcons.Swish), + ) +} + @HedvigPreview @Composable private fun PreviewOnboardingPaymentScreen( @@ -256,6 +334,7 @@ private fun PreviewOnboardingPaymentScreen( navigateUp = {}, onClose = {}, onRetry = {}, + onProviderSelected = {}, onConnectPayment = {}, onContinue = {}, ) @@ -270,19 +349,24 @@ private class OnboardingPaymentUiStateProvider : CollectionPreviewParameterProvi OnboardingPaymentUiState.Content( progress = OnboardingProgress(totalSteps = 5, currentIndex = 3), payinStatus = OnboardingPayinStatus.NeedsSetup, + availableProviders = OnboardingPayinProvider.entries, ), OnboardingPaymentUiState.Content( progress = OnboardingProgress(totalSteps = 5, currentIndex = 3), payinStatus = OnboardingPayinStatus.NeedsSetup, + availableProviders = OnboardingPayinProvider.entries, + selectedProvider = OnboardingPayinProvider.Swish, hasAttemptedToConnect = true, ), OnboardingPaymentUiState.Content( progress = OnboardingProgress(totalSteps = 5, currentIndex = 3), payinStatus = OnboardingPayinStatus.Pending, + availableProviders = OnboardingPayinProvider.entries, ), OnboardingPaymentUiState.Content( progress = OnboardingProgress(totalSteps = 5, currentIndex = 3), payinStatus = OnboardingPayinStatus.Active, + availableProviders = OnboardingPayinProvider.entries, ), ), ) diff --git a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/FakeOnboardingRepository.kt b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/FakeOnboardingRepository.kt index f21013aeb7..8451322611 100644 --- a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/FakeOnboardingRepository.kt +++ b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/FakeOnboardingRepository.kt @@ -9,6 +9,7 @@ import com.hedvig.android.feature.onboarding.data.OnboardingContract import com.hedvig.android.feature.onboarding.data.OnboardingCrossSell import com.hedvig.android.feature.onboarding.data.OnboardingData import com.hedvig.android.feature.onboarding.data.OnboardingMemberIdProvider +import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import com.hedvig.android.feature.onboarding.data.OnboardingPayinStatus import com.hedvig.android.feature.onboarding.data.OnboardingReferralInformation import com.hedvig.android.feature.onboarding.data.OnboardingRepository @@ -81,5 +82,6 @@ internal fun testOnboardingData( contracts = contracts, referralInformation = referralInformation, payinStatus = payinStatus, + availablePayinProviders = OnboardingPayinProvider.entries, crossSells = crossSells, ) diff --git a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingPathTest.kt b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingPathTest.kt index c12dfca2f5..8d9ffe212f 100644 --- a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingPathTest.kt +++ b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/data/OnboardingPathTest.kt @@ -6,6 +6,7 @@ import assertk.assertions.containsExactly import assertk.assertions.doesNotContain import assertk.assertions.hasSize import assertk.assertions.isEqualTo +import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import com.hedvig.android.feature.onboarding.data.OnboardingPayinStatus import com.hedvig.android.feature.onboarding.navigation.OnboardingStepId import org.junit.Test @@ -47,6 +48,7 @@ class OnboardingPathTest { contracts = contracts, referralInformation = referralInformation, payinStatus = if (hasConnectedPayinMethod) OnboardingPayinStatus.Active else OnboardingPayinStatus.NeedsSetup, + availablePayinProviders = OnboardingPayinProvider.entries, crossSells = crossSells, ) diff --git a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt index e1970b8586..9a95caaf71 100644 --- a/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt +++ b/app/feature/feature-onboarding/src/test/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentPresenterTest.kt @@ -3,20 +3,24 @@ package com.hedvig.android.feature.onboarding.ui.payment import androidx.compose.runtime.mutableStateListOf import arrow.core.right import assertk.assertThat +import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isInstanceOf +import assertk.assertions.isNull import assertk.assertions.isTrue +import com.hedvig.android.feature.connect.payment.trustly.ui.TrustlyKey import com.hedvig.android.feature.onboarding.FakeOnboardingMemberIdProvider import com.hedvig.android.feature.onboarding.FakeOnboardingRepository import com.hedvig.android.feature.onboarding.data.CompleteOnboardingUseCase +import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import com.hedvig.android.feature.onboarding.data.OnboardingPayinStatus import com.hedvig.android.feature.onboarding.navigation.OnboardingNavigator import com.hedvig.android.feature.onboarding.navigation.OnboardingStepId import com.hedvig.android.feature.onboarding.navigation.OnboardingStepKey import com.hedvig.android.feature.onboarding.testOnboardingData import com.hedvig.android.feature.onboarding.testSessionStore -import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey +import com.hedvig.android.feature.payin.account.navigation.SetupSwishPayinKey import com.hedvig.android.logger.TestLogcatLoggingRule import com.hedvig.android.molecule.test.test import com.hedvig.android.navigation.common.HedvigNavKey @@ -57,7 +61,7 @@ internal class OnboardingPaymentPresenterTest { } @Test - fun `connect payment pushes the payin account overview`() = runTest { + fun `connect payment pushes the swish setup screen when swish is selected`() = runTest { val backstack = TestBackstack().apply { entries.add(OnboardingStepKey(OnboardingStepId.ConnectPayment)) } val repository = FakeOnboardingRepository() val sessionStore = testSessionStore(repository, FakeOnboardingMemberIdProvider()) @@ -68,10 +72,52 @@ internal class OnboardingPaymentPresenterTest { skipItems(1) repository.onboardingDataResponses.add(testOnboardingData().right()) awaitItem() + sendEvent(OnboardingPaymentEvent.SelectProvider(OnboardingPayinProvider.Swish)) + awaitItem() sendEvent(OnboardingPaymentEvent.ConnectPayment) awaitItem() runCurrent() - assertThat(backstack.entries.last()).isEqualTo(PayinAccountKey) + assertThat(backstack.entries.last()).isEqualTo(SetupSwishPayinKey) + } + } + + @Test + fun `connect payment pushes trustly when the bank option is selected`() = runTest { + val backstack = TestBackstack().apply { entries.add(OnboardingStepKey(OnboardingStepId.ConnectPayment)) } + val repository = FakeOnboardingRepository() + val sessionStore = testSessionStore(repository, FakeOnboardingMemberIdProvider()) + val navigator = OnboardingNavigator(backstack, sessionStore, NoopCompleteOnboardingUseCase()) + val presenter = OnboardingPaymentPresenter(sessionStore, navigator) + + presenter.test(OnboardingPaymentUiState.Loading) { + skipItems(1) + repository.onboardingDataResponses.add(testOnboardingData().right()) + awaitItem() + sendEvent(OnboardingPaymentEvent.SelectProvider(OnboardingPayinProvider.Trustly)) + awaitItem() + sendEvent(OnboardingPaymentEvent.ConnectPayment) + awaitItem() + runCurrent() + assertThat(backstack.entries.last()).isEqualTo(TrustlyKey) + } + } + + @Test + fun `connect payment does nothing while no provider is selected`() = runTest { + val backstack = TestBackstack().apply { entries.add(OnboardingStepKey(OnboardingStepId.ConnectPayment)) } + val repository = FakeOnboardingRepository() + val sessionStore = testSessionStore(repository, FakeOnboardingMemberIdProvider()) + val navigator = OnboardingNavigator(backstack, sessionStore, NoopCompleteOnboardingUseCase()) + val presenter = OnboardingPaymentPresenter(sessionStore, navigator) + + presenter.test(OnboardingPaymentUiState.Loading) { + skipItems(1) + repository.onboardingDataResponses.add(testOnboardingData().right()) + val content = awaitItem() as OnboardingPaymentUiState.Content + assertThat(content.selectedProvider).isNull() + sendEvent(OnboardingPaymentEvent.ConnectPayment) + runCurrent() + assertThat(backstack.entries).containsExactly(OnboardingStepKey(OnboardingStepId.ConnectPayment)) } } @@ -89,6 +135,8 @@ internal class OnboardingPaymentPresenterTest { val initial = awaitItem() as OnboardingPaymentUiState.Content assertThat(initial.hasAttemptedToConnect).isFalse() + sendEvent(OnboardingPaymentEvent.SelectProvider(OnboardingPayinProvider.Swish)) + awaitItem() sendEvent(OnboardingPaymentEvent.ConnectPayment) val afterAttempt = awaitItem() as OnboardingPaymentUiState.Content assertThat(afterAttempt.hasAttemptedToConnect).isTrue() diff --git a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt index fe262c6f60..43f20dabe5 100644 --- a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt +++ b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt @@ -21,3 +21,7 @@ enum class PayinMethodId { data class PayinMethodDetailsKey( val method: PayinMethodId, ) : HedvigNavKey + +/** The Swish payin setup screen, reachable both from the payin flow and from onboarding. */ +@Serializable +data object SetupSwishPayinKey : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index f2731e6aff..365d723005 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -10,9 +10,6 @@ internal data class SelectPayinMethodKey( val currentProviders: List, ) : HedvigNavKey -@Serializable -internal data object SetupSwishPayinKey : HedvigNavKey - @Serializable internal data object SetupInvoicePayinKey : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 54c70b44d5..3e0e7dfc13 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -26,6 +26,7 @@ import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinVie import com.hedvig.android.navigation.common.HedvigNavKey import com.hedvig.android.navigation.compose.Backstack import com.hedvig.android.navigation.compose.add +import com.hedvig.android.navigation.compose.navigateAndPopUpTo import com.hedvig.android.navigation.compose.popUpTo import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel import dev.zacsweers.metrox.viewmodel.metroViewModel @@ -110,7 +111,9 @@ fun EntryProviderScope.payinAccountEntries( backstack.popUpTo(inclusive = true) navigateToConnectPayment() }, - onSwishSelected = dropUnlessResumed { backstack.add(SetupSwishPayinKey) }, + onSwishSelected = dropUnlessResumed { + backstack.navigateAndPopUpTo(SetupSwishPayinKey, inclusive = true) + }, onInvoiceSelected = dropUnlessResumed { backstack.add(SetupInvoicePayinKey) }, navigateUp = backstack::navigateUp, ) @@ -121,13 +124,10 @@ fun EntryProviderScope.payinAccountEntries( SetupSwishPayinDestination( viewModel = viewModel, globalSnackBarState = globalSnackBarState, - onSuccessfullyConnected = { backstack.popUpTo(inclusive = true) }, + onSuccessfullyConnected = backstack::popBackstack, navigateUp = backstack::navigateUp, navigateBack = backstack::popBackstack, - openUrl = { - backstack.popUpTo(inclusive = true) - openUrl(it) - }, + openUrl = openUrl, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt index 08da65329e..47863211f7 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt @@ -1,120 +1,38 @@ package com.hedvig.android.feature.payin.account.ui.components -import androidx.compose.foundation.Image -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.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp -import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme -import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.PaymentMethodHandoverIllustration +import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.ThreeDotsLoading -import com.hedvig.android.design.system.hedvig.dashedBorder -import com.hedvig.android.design.system.hedvig.hedvigDropShadow -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.HelipadOutline -import com.hedvig.android.design.system.hedvig.icon.Plus -import com.hedvig.android.design.system.hedvig.icon.Trustly -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra -import com.hedvig.android.design.system.hedvig.icon.colored.Swish import octopus.type.MemberPaymentProvider @Composable internal fun PayinMethodHandoverIllustration(provider: MemberPaymentProvider?, modifier: Modifier = Modifier) { Column(modifier) { Spacer(Modifier.height(48.dp)) - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.backgroundPrimary, - contentColor = colorScheme.fillPrimary, - modifier = Modifier.size(74.dp) - .dashedBorder( - color = colorScheme.borderSecondary, - shape = HedvigTheme.shapes.cornerXXLarge, - ) - .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge), - ) { - Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - Icon(HedvigIcons.Trustly, EmptyContentDescription, Modifier.size(39.dp)) - } - - MemberPaymentProvider.SWISH -> { - Image( - imageVector = HedvigIcons.Swish, - contentDescription = EmptyContentDescription, - modifier = Modifier - .size((39.0).dp), - ) - } - - MemberPaymentProvider.INVOICE -> { - Image( - imageVector = HedvigIcons.Kivra, - contentDescription = EmptyContentDescription, - modifier = Modifier - .size((39.0).dp), - ) - } - - null, - MemberPaymentProvider.NORDEA, - MemberPaymentProvider.UNKNOWN__, - -> { - Icon( - HedvigIcons.Plus, - EmptyContentDescription, - Modifier.size(39.dp), - tint = HedvigTheme.colorScheme.fillSecondary, - ) - } - } - } - } - ThreeDotsLoading() - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = colorScheme.fillBlack, - contentColor = colorScheme.fillWhite, - border = colorScheme.borderPrimary, - modifier = Modifier.size(74.dp), - ) { - Box(Modifier.size(74.dp), contentAlignment = Alignment.Center) { - Icon( - HedvigIcons.HelipadOutline, - EmptyContentDescription, - Modifier.size(65.dp), - ) - } - } - } + PaymentMethodHandoverIllustration( + mark = { PayinProviderMark(provider, Modifier.size(PaymentMethodMarkSize)) }, + ) Spacer(Modifier.height(48.dp)) } } @Composable @HedvigPreview -private fun PreviewSelectPrimaryPayinMethodScreen( - @PreviewParameter(SelectedMethodIndexProvider::class) provider: MemberPaymentProvider?, +private fun PreviewPayinMethodHandoverIllustration( + @PreviewParameter(PayinProviderProvider::class) provider: MemberPaymentProvider?, ) { HedvigTheme { Surface(color = colorScheme.backgroundPrimary) { @@ -126,7 +44,7 @@ private fun PreviewSelectPrimaryPayinMethodScreen( } } -private class SelectedMethodIndexProvider : +private class PayinProviderProvider : CollectionPreviewParameterProvider( listOf( null, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index 84daf88d62..1b41c94581 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -19,6 +19,7 @@ import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.IconResource.Vector +import com.hedvig.android.design.system.hedvig.PaymentMethodPlusMark import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface @@ -35,6 +36,7 @@ import hedvig.resources.PAYMENT_PRIMARY_LABEL import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res import hedvig.resources.swish +import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource /** @@ -58,17 +60,24 @@ private fun PayinMethodPillow(method: PayinAccount, modifier: Modifier = Modifie /** * The method's brand mark. Trustly's is monochrome and picks up the surrounding content colour; the - * others are full-colour and ignore it. + * others are full-colour and ignore it. A provider with no mark of its own falls back to the plus, + * so one the backend adds later still renders. */ @Composable -internal fun PayinMethodMark(method: PayinAccount, modifier: Modifier = Modifier) { - when (method) { - is PayinAccount.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, modifier) - is PayinAccount.SwishPayin -> Image(HedvigIcons.Swish, EmptyContentDescription, modifier) - is PayinAccount.Invoice -> Image(HedvigIcons.Kivra, EmptyContentDescription, modifier) +internal fun PayinProviderMark(provider: MemberPaymentProvider?, modifier: Modifier = Modifier) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> Icon(HedvigIcons.Trustly, EmptyContentDescription, modifier) + MemberPaymentProvider.SWISH -> Image(HedvigIcons.Swish, EmptyContentDescription, modifier) + MemberPaymentProvider.INVOICE -> Image(HedvigIcons.Kivra, EmptyContentDescription, modifier) + else -> PaymentMethodPlusMark(modifier) } } +@Composable +internal fun PayinMethodMark(method: PayinAccount, modifier: Modifier = Modifier) { + PayinProviderMark(method.provider, modifier) +} + @Composable internal fun payinMethodTitle(method: PayinAccount): String = when (method) { is PayinAccount.Trustly -> stringResource(Res.string.PAYMENTS_BANK_LABEL) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt deleted file mode 100644 index c1968c5267..0000000000 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodTile.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.hedvig.android.feature.payin.account.ui.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.unit.dp -import com.hedvig.android.compose.ui.EmptyContentDescription -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.dashedBorder -import com.hedvig.android.design.system.hedvig.hedvigDropShadow - -/** The size a [PayinMethodTile]'s mark is drawn at. */ -internal val PayinMethodMarkSize = 39.dp - -private val TileSize = 74.dp -private val BadgeSize = 24.dp - -/** - * The outlined tile a payin method is shown on while it is being connected or given up, with an - * optional status badge on its top-right corner. - */ -@Composable -internal fun PayinMethodTile( - modifier: Modifier = Modifier, - badge: @Composable (() -> Unit)? = null, - mark: @Composable () -> Unit, -) { - Box(modifier) { - Surface( - shape = HedvigTheme.shapes.cornerXXLarge, - color = HedvigTheme.colorScheme.backgroundPrimary, - contentColor = HedvigTheme.colorScheme.fillPrimary, - modifier = Modifier - .size(TileSize) - .hedvigDropShadow(HedvigTheme.shapes.cornerXXLarge) - .dashedBorder( - color = HedvigTheme.colorScheme.borderSecondary, - shape = HedvigTheme.shapes.cornerXXLarge, - ), - ) { - Box(Modifier.size(TileSize), contentAlignment = Alignment.Center) { - mark() - } - } - if (badge != null) { - Box( - Modifier - .align(Alignment.TopEnd) - .offset(x = 8.dp, y = (-8).dp), - ) { - badge() - } - } - } -} - -/** A circular status badge for [PayinMethodTile]'s corner. */ -@Composable -internal fun PayinMethodTileBadge( - icon: ImageVector, - containerColor: Color, - contentColor: Color, - modifier: Modifier = Modifier, -) { - Box( - contentAlignment = Alignment.Center, - modifier = modifier - .size(BadgeSize) - .clip(CircleShape) - .background(containerColor), - ) { - Icon( - imageVector = icon, - contentDescription = EmptyContentDescription, - tint = contentColor, - modifier = Modifier.size(16.dp), - ) - } -} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index 4c4b77d365..bff6db76c4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -19,6 +19,9 @@ import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme +import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize +import com.hedvig.android.design.system.hedvig.PaymentMethodTile +import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState @@ -26,9 +29,6 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Minus import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMarkSize -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTile -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTileBadge import com.hedvig.android.feature.payin.account.ui.components.toRadioOption import hedvig.resources.PAYMENT_REMOVE_SUBTITLE import hedvig.resources.PAYMENT_REMOVE_TITLE @@ -77,15 +77,15 @@ private fun RemovePayinMethodBottomSheetContent( .fillMaxWidth() .padding(vertical = 64.dp), ) { - PayinMethodTile( + PaymentMethodTile( badge = { - PayinMethodTileBadge( + PaymentMethodTileBadge( icon = HedvigIcons.Minus, containerColor = colorScheme.fillPrimary, contentColor = colorScheme.fillNegative, ) }, - mark = { PayinMethodMark(method, Modifier.size(PayinMethodMarkSize)) }, + mark = { PayinMethodMark(method, Modifier.size(PaymentMethodMarkSize)) }, ) } val option = method.toRadioOption() diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index fc0d82a1c4..77ac4b3be2 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -50,6 +50,9 @@ import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize +import com.hedvig.android.design.system.hedvig.PaymentMethodTile +import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast @@ -57,9 +60,6 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.design.system.hedvig.show -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMarkSize -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTile -import com.hedvig.android.feature.payin.account.ui.components.PayinMethodTileBadge import com.hedvig.android.feature.payin.account.ui.components.formatSwishPhoneNumber import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.GENERAL_CONFIRM @@ -151,13 +151,13 @@ private fun SetupSwishPayinScreen( modifier = Modifier.padding(horizontal = 16.dp), ) Spacer(Modifier.weight(1f)) - PayinMethodTile( + PaymentMethodTile( modifier = Modifier.align(Alignment.CenterHorizontally), badge = if (successUrl == null) { null } else { { - PayinMethodTileBadge( + PaymentMethodTileBadge( icon = HedvigIcons.ArrowNorthEast, containerColor = HedvigTheme.colorScheme.signalBlueElement, contentColor = HedvigTheme.colorScheme.fillWhite, @@ -165,7 +165,7 @@ private fun SetupSwishPayinScreen( } }, mark = { - Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(PayinMethodMarkSize)) + Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(PaymentMethodMarkSize)) }, ) Spacer(Modifier.weight(1f)) From 1d25daf1f1cbfd3b7f51a86989127b52221cd1f0 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 11 Sep 2026 09:54:50 +0200 Subject: [PATCH 61/75] PayinMethodHandoverIllustration icon change --- .../system/hedvig/PaymentMethodHandover.kt | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt index 49e0b63087..26d22614d3 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt @@ -1,6 +1,5 @@ package com.hedvig.android.design.system.hedvig -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -16,16 +15,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.HelipadOutline import com.hedvig.android.design.system.hedvig.icon.Plus -import hedvig.resources.Res -import hedvig.resources.pillow_new_680 -import org.jetbrains.compose.resources.painterResource /** The size a [PaymentMethodTile]'s mark is drawn at. */ val PaymentMethodMarkSize = 39.dp private val TileSize = 74.dp private val BadgeSize = 24.dp +private val BadgeOffset = 8.dp /** * The outlined tile a payment method is shown on while it is being connected or given up, with an @@ -57,11 +55,7 @@ fun PaymentMethodTile( } } if (badge != null) { - Box( - Modifier - .align(Alignment.TopEnd) - .offset(x = 8.dp, y = (-8).dp), - ) { + Box(Modifier.align(Alignment.TopEnd).offset(BadgeOffset, -BadgeOffset)) { badge() } } @@ -121,17 +115,23 @@ fun PaymentMethodHandoverIllustration( PaymentMethodTile(mark = mark) ThreeDotsLoading() Box { - Image( - painter = painterResource(Res.drawable.pillow_new_680), - contentDescription = null, + Surface( + shape = HedvigTheme.shapes.cornerXXLarge, + color = HedvigTheme.colorScheme.fillBlack, + contentColor = HedvigTheme.colorScheme.fillWhite, + border = HedvigTheme.colorScheme.borderPrimary, modifier = Modifier.size(TileSize), - ) + ) { + Box(Modifier.size(TileSize), contentAlignment = Alignment.Center) { + Icon( + imageVector = HedvigIcons.HelipadOutline, + contentDescription = null, + modifier = Modifier.size(65.dp), + ) + } + } if (destinationBadge != null) { - Box( - Modifier - .align(Alignment.TopEnd) - .offset(x = 6.dp, y = (-6).dp), - ) { + Box(Modifier.align(Alignment.TopEnd).offset(BadgeOffset, -BadgeOffset)) { destinationBadge() } } From 3cd86f13b9531a608fb3489b92b2866af1b22de6 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 11 Sep 2026 13:49:10 +0200 Subject: [PATCH 62/75] radio option UI --- .../system/hedvig/PaymentMethodHandover.kt | 27 +++++++ .../design/system/hedvig/RadioGroup.kt | 15 +++- .../OnboardingConnectingPaymentSymbol.kt | 18 +---- .../payment/OnboardingPaymentDestination.kt | 36 ++++++++- .../PayinMethodHandoverIllustration.kt | 4 +- .../account/ui/components/PayinMethodRow.kt | 81 ++++++++++++++----- .../RemovePayinMethodBottomSheet.kt | 3 + .../SelectPrimaryPayinMethodDestination.kt | 3 + .../ConnectPayinMethodDestination.kt | 46 ++++++----- 9 files changed, 169 insertions(+), 64 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt index 26d22614d3..caef501621 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/PaymentMethodHandover.kt @@ -21,7 +21,11 @@ import com.hedvig.android.design.system.hedvig.icon.Plus /** The size a [PaymentMethodTile]'s mark is drawn at. */ val PaymentMethodMarkSize = 39.dp +/** The size a [PaymentMethodPillow]'s mark is drawn at. */ +val PaymentMethodPillowMarkSize = 28.dp + private val TileSize = 74.dp +private val PillowSize = 40.dp private val BadgeSize = 24.dp private val BadgeOffset = 8.dp @@ -73,6 +77,29 @@ fun PaymentMethodPlusMark(modifier: Modifier = Modifier) { ) } +/** + * The small tile a payment method's brand mark sits on in a list row. The defaults suit a + * full-colour mark; a monochrome one reads better inverted, on [ColorScheme.fillBlack]. + */ +@Composable +fun PaymentMethodPillow( + modifier: Modifier = Modifier, + containerColor: Color = HedvigTheme.colorScheme.fillWhite, + contentColor: Color = HedvigTheme.colorScheme.fillBlack, + mark: @Composable () -> Unit, +) { + Surface( + shape = HedvigTheme.shapes.cornerSmall, + color = containerColor, + contentColor = contentColor, + modifier = modifier.size(PillowSize), + ) { + Box(Modifier.size(PillowSize), contentAlignment = Alignment.Center) { + mark() + } + } +} + /** A circular status badge for the corner of a [PaymentMethodTile] or the Hedvig symbol. */ @Composable fun PaymentMethodTileBadge( diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RadioGroup.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RadioGroup.kt index 30fefde5a4..aca80ca89e 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RadioGroup.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RadioGroup.kt @@ -45,6 +45,10 @@ import com.hedvig.android.design.system.hedvig.tokens.RadioGroupStyleTokens import kotlin.jvm.JvmInline import org.jetbrains.compose.resources.painterResource +/** + * [optionIcon] replaces [RadioOption.iconResource] for every option, for leading content an + * `ImageVector` cannot express — a brand mark on its own tile, say. + */ @Composable fun RadioGroup( options: List, @@ -57,6 +61,7 @@ fun RadioGroup( disabledOptions: List = emptyList(), enabled: Boolean = true, textEndContent: @Composable ((RadioOptionId) -> Unit)? = null, + optionIcon: @Composable ((RadioOptionId) -> Unit)? = null, ) { val spacings = RadioGroupDefaults.style(size, style) RadioGroup( @@ -69,6 +74,7 @@ fun RadioGroup( enabled = enabled, role = Role.RadioButton, textEndContent = textEndContent, + optionIcon = optionIcon, selectIndicator = { selected, enabled, colors, interactionSource -> RadioSelectIndicator( selected = selected, @@ -273,6 +279,7 @@ private fun RadioGroup( disabledOptions: List = emptyList(), enabled: Boolean = true, textEndContent: @Composable ((RadioOptionId) -> Unit)? = null, + optionIcon: @Composable ((RadioOptionId) -> Unit)? = null, ) { Box(modifier) { if (style.style is RadioGroupStyle.Labeled) { @@ -307,6 +314,7 @@ private fun RadioGroup( interactionSource = interactionSource, selectIndicator = selectIndicator, textEndContent = textEndContent, + optionIcon = optionIcon, modifier = Modifier.optionSelectable( onRadioOptionSelected = onRadioOptionSelected, radioOptionId = option.id, @@ -333,6 +341,7 @@ private fun RadioGroup( style = style, selectIndicator = selectIndicator, textEndContent = textEndContent, + optionIcon = optionIcon, modifier = Modifier .fillMaxWidth() .optionSelectable(onRadioOptionSelected, option.id, selected, enabled, role) @@ -363,6 +372,7 @@ private fun RadioGroup( style = style, selectIndicator = selectIndicator, textEndContent = textEndContent, + optionIcon = optionIcon, modifier = Modifier .fillMaxWidth() .optionSelectable(onRadioOptionSelected, option.id, selected, enabled, role) @@ -457,6 +467,7 @@ private fun RadioOption( modifier: Modifier = Modifier, interactionSource: MutableInteractionSource? = null, textEndContent: @Composable ((RadioOptionId) -> Unit)? = null, + optionIcon: @Composable ((RadioOptionId) -> Unit)? = null, ) { Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -468,7 +479,9 @@ private fun RadioOption( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f, false), ) { - if (option.iconResource != null) { + if (optionIcon != null) { + optionIcon(option.id) + } else if (option.iconResource != null) { RadioOptionIcon(option.iconResource) } if (style.style.leftAlignedIndicator) { diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt index e5d7c49861..69b6f6c209 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingConnectingPaymentSymbol.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -18,19 +17,14 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp -import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.PaymentMethodHandoverIllustration import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize -import com.hedvig.android.design.system.hedvig.PaymentMethodPlusMark import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.icon.Checkmark import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.Trustly -import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.tokens.MotionTokens import com.hedvig.android.feature.onboarding.data.OnboardingPayinProvider import kotlin.time.Duration.Companion.milliseconds @@ -96,20 +90,10 @@ private fun ConnectingGraphic( }, ) }, - mark = { OnboardingPayinProviderMark(provider) }, + mark = { OnboardingPayinProviderMark(provider, Modifier.size(PaymentMethodMarkSize)) }, ) } -@Composable -private fun OnboardingPayinProviderMark(provider: OnboardingPayinProvider?) { - val markModifier = Modifier.size(PaymentMethodMarkSize) - when (provider) { - OnboardingPayinProvider.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, markModifier) - OnboardingPayinProvider.Swish -> Image(HedvigIcons.Swish, EmptyContentDescription, markModifier) - null -> PaymentMethodPlusMark(markModifier) - } -} - @HedvigPreview @Composable private fun PreviewOnboardingConnectingPaymentSymbol( diff --git a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt index f7f44f5706..a91f209cb8 100644 --- a/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt +++ b/app/feature/feature-onboarding/src/main/kotlin/com/hedvig/android/feature/onboarding/ui/payment/OnboardingPaymentDestination.kt @@ -1,9 +1,11 @@ package com.hedvig.android.feature.onboarding.ui.payment +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -20,6 +22,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.design.system.hedvig.HedvigErrorSection @@ -27,7 +30,10 @@ import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProg import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.IconResource +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.PaymentMethodPillow +import com.hedvig.android.design.system.hedvig.PaymentMethodPillowMarkSize +import com.hedvig.android.design.system.hedvig.PaymentMethodPlusMark import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId @@ -280,6 +286,9 @@ private fun OnboardingPaymentScreen( val provider = content.availableProviders.firstOrNull { it.name == id.id } if (provider != null) onProviderSelected(provider) }, + optionIcon = { id -> + OnboardingPayinProviderPillow(content.availableProviders.firstOrNull { it.name == id.id }) + }, modifier = Modifier.padding(horizontal = 16.dp), ) } @@ -310,14 +319,35 @@ private fun OnboardingPayinProvider.toRadioOption(): RadioOption = when (this) { id = RadioOptionId(name), text = stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL), label = stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE), - iconResource = IconResource.Vector(HedvigIcons.Trustly), ) OnboardingPayinProvider.Swish -> RadioOption( id = RadioOptionId(name), text = stringResource(Res.string.swish), label = stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE), - iconResource = IconResource.Vector(HedvigIcons.Swish), + ) +} + +/** + * The provider's brand mark. Trustly's is monochrome and picks up the surrounding content colour; + * Swish's is full-colour and ignores it. + */ +@Composable +internal fun OnboardingPayinProviderMark(provider: OnboardingPayinProvider?, modifier: Modifier = Modifier) { + when (provider) { + OnboardingPayinProvider.Trustly -> Icon(HedvigIcons.Trustly, EmptyContentDescription, modifier) + OnboardingPayinProvider.Swish -> Image(HedvigIcons.Swish, EmptyContentDescription, modifier) + null -> PaymentMethodPlusMark(modifier) + } +} + +@Composable +private fun OnboardingPayinProviderPillow(provider: OnboardingPayinProvider?) { + val onDarkTile = provider == OnboardingPayinProvider.Trustly + PaymentMethodPillow( + containerColor = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + mark = { OnboardingPayinProviderMark(provider, Modifier.size(PaymentMethodPillowMarkSize)) }, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt index 47863211f7..82fc913acb 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodHandoverIllustration.kt @@ -32,7 +32,7 @@ internal fun PayinMethodHandoverIllustration(provider: MemberPaymentProvider?, m @Composable @HedvigPreview private fun PreviewPayinMethodHandoverIllustration( - @PreviewParameter(PayinProviderProvider::class) provider: MemberPaymentProvider?, + @PreviewParameter(PayinPreviewProvider::class) provider: MemberPaymentProvider?, ) { HedvigTheme { Surface(color = colorScheme.backgroundPrimary) { @@ -44,7 +44,7 @@ private fun PreviewPayinMethodHandoverIllustration( } } -private class PayinProviderProvider : +private class PayinPreviewProvider : CollectionPreviewParameterProvider( listOf( null, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index 1b41c94581..ca71a97752 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -13,12 +14,18 @@ import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.IconResource.Vector +import com.hedvig.android.design.system.hedvig.PaymentMethodPillow +import com.hedvig.android.design.system.hedvig.PaymentMethodPillowMarkSize import com.hedvig.android.design.system.hedvig.PaymentMethodPlusMark import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId @@ -27,10 +34,10 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.feature.payin.account.data.InvoiceDelivery import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.data.toDeliveryString -import com.hedvig.android.logger.logcat import hedvig.resources.PAYMENTS_BANK_LABEL import hedvig.resources.PAYMENT_PRIMARY_LABEL import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL @@ -44,18 +51,14 @@ import org.jetbrains.compose.resources.stringResource * behind Trustly's monochrome one, which picks up the tile's content colour. */ @Composable -private fun PayinMethodPillow(method: PayinAccount, modifier: Modifier = Modifier) { - val onDarkTile = method is PayinAccount.Trustly - Surface( - shape = HedvigTheme.shapes.cornerSmall, - color = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, +internal fun PayinProviderPillow(provider: MemberPaymentProvider?, modifier: Modifier = Modifier) { + val onDarkTile = provider == MemberPaymentProvider.TRUSTLY + PaymentMethodPillow( + modifier = modifier, + containerColor = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, - modifier = modifier.size(40.dp), - ) { - Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { - PayinMethodMark(method, Modifier.size(28.dp)) - } - } + mark = { PayinProviderMark(provider, Modifier.size(PaymentMethodPillowMarkSize)) }, + ) } /** @@ -121,7 +124,7 @@ internal fun PayinMethodRow( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - PayinMethodPillow(method) + PayinProviderPillow(method.provider) Spacer(Modifier.width(2.dp)) Column( Modifier @@ -148,11 +151,6 @@ internal fun PayinAccount.toRadioOption(): RadioOption = RadioOption( id = RadioOptionId(provider.rawValue), text = payinMethodTitle(this), label = payinMethodSubtitle(this), - iconResource = when (this) { - is PayinAccount.Trustly -> Vector(HedvigIcons.Trustly) - is PayinAccount.SwishPayin -> Vector(HedvigIcons.Swish) - is PayinAccount.Invoice -> Vector(HedvigIcons.Kivra) - }, ) /** Static counterpart to the button the design draws it with: the primary method is not a choice made here. */ @@ -197,3 +195,48 @@ internal fun formatSwishPhoneNumber(phoneNumber: String): String { } return sb.toString() } + +@Composable +@HedvigPreview +private fun PreviewPayinMethodRow( + @PreviewParameter(PayinAccountPreviewProvider::class) account: PayinAccount, +) { + HedvigTheme { + Surface(color = colorScheme.backgroundPrimary) { + HedvigCard( + shape = HedvigTheme.shapes.cornerLarge, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + PayinMethodRow( + account, + Modifier.fillMaxWidth(), + ) + } + } + } +} + +private class PayinAccountPreviewProvider : CollectionPreviewParameterProvider( + listOf( + PayinAccount.Trustly( + clearingNumber = "****", + accountNumber = "*45678", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ), + PayinAccount.SwishPayin( + phoneNumber = "0701234567", + isPending = false, + isDefault = false, + ), + PayinAccount.Invoice( + delivery = InvoiceDelivery.Kivra, + isPending = false, + isDefault = false, + email = "", + ), + ), +) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index bff6db76c4..fd0da70db2 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -28,7 +28,9 @@ import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Minus import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark +import com.hedvig.android.feature.payin.account.ui.components.PayinProviderPillow import com.hedvig.android.feature.payin.account.ui.components.toRadioOption import hedvig.resources.PAYMENT_REMOVE_SUBTITLE import hedvig.resources.PAYMENT_REMOVE_TITLE @@ -93,6 +95,7 @@ private fun RemovePayinMethodBottomSheetContent( options = listOf(option), selectedOption = option.id, onRadioOptionSelected = {}, + optionIcon = { PayinProviderPillow(method.provider) }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index 83e57c9576..f1b2320e3a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -54,6 +54,7 @@ import com.hedvig.android.feature.payin.account.data.PayinAccount.Trustly import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow +import com.hedvig.android.feature.payin.account.ui.components.PayinProviderPillow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel import com.hedvig.android.feature.payin.account.ui.components.payinMethodTitle import com.hedvig.android.feature.payin.account.ui.components.toRadioOption @@ -69,6 +70,7 @@ import hedvig.resources.Res.string import hedvig.resources.general_cancel_button import hedvig.resources.general_continue_button import hedvig.resources.pillow_new_680 +import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -133,6 +135,7 @@ private fun SelectPrimaryPayinMethodScreen( val method = uiState.methods.firstOrNull { it.provider.rawValue == id.id } if (method != null) onMethodSelected(method) }, + optionIcon = { PayinProviderPillow(MemberPaymentProvider.safeValueOf(it.id)) }, modifier = Modifier.padding(horizontal = 16.dp), ) if (uiState.errorMessage != null) { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt index f92095e7f6..cc340d8af1 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/ConnectPayinMethodDestination.kt @@ -16,17 +16,13 @@ import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.IconResource import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.a11y.FlowHeading -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.Trustly -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra -import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration +import com.hedvig.android.feature.payin.account.ui.components.PayinProviderPillow import hedvig.resources.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL import hedvig.resources.PAYMENTS_INVOICE import hedvig.resources.PAYMENT_CONNECT_SUBTITLE @@ -100,10 +96,14 @@ private fun ConnectPayinMethodScreen( ) Spacer(Modifier.weight(1f)) RadioGroup( - options = uiState.availableProviders.mapNotNull { it.toRadioOption( - uiState.currentProviders) }, + options = uiState.availableProviders.mapNotNull { + it.toRadioOption( + uiState.currentProviders, + ) + }, selectedOption = uiState.selectedProvider?.let { RadioOptionId(it.rawValue) }, onRadioOptionSelected = { onProviderSelected(MemberPaymentProvider.safeValueOf(it.id)) }, + optionIcon = { PayinProviderPillow(MemberPaymentProvider.safeValueOf(it.id)) }, modifier = Modifier.padding(horizontal = 16.dp), ) Spacer(Modifier.height(16.dp)) @@ -133,36 +133,37 @@ private fun ConnectPayinMethodScreen( * of the group. */ @Composable -private fun MemberPaymentProvider.toRadioOption( - currentProviders: List -): RadioOption? { +private fun MemberPaymentProvider.toRadioOption(currentProviders: List): RadioOption? { val id = RadioOptionId(rawValue) return when (this) { MemberPaymentProvider.TRUSTLY -> RadioOption( id = id, text = stringResource(Res.string.ONBOARDING_CONNECT_PAYMENT_BANK_LABEL), - label = if (currentProviders.contains(this)) - stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) else - stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE), - iconResource = IconResource.Vector(HedvigIcons.Trustly), + label = if (currentProviders.contains(this)) { + stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) + } else { + stringResource(Res.string.PAYMENT_OPTION_TRUSTLY_SUBTITLE) + }, ) MemberPaymentProvider.SWISH -> RadioOption( id = id, text = stringResource(Res.string.swish), - label = if (currentProviders.contains(this)) + label = if (currentProviders.contains(this)) { stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) - else stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE), - iconResource = IconResource.Vector(HedvigIcons.Swish), + } else { + stringResource(Res.string.PAYMENT_OPTION_SWISH_SUBTITLE) + }, ) MemberPaymentProvider.INVOICE -> RadioOption( id = id, text = stringResource(Res.string.PAYMENTS_INVOICE), - label = if (currentProviders.contains(this)) - stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) else - stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE), - iconResource = IconResource.Vector(HedvigIcons.Kivra), + label = if (currentProviders.contains(this)) { + stringResource(Res.string.PAYMENT_OPTION_CONNECTED_LABEL) + } else { + stringResource(Res.string.PAYMENT_OPTION_INVOICE_SUBTITLE) + }, ) else -> null @@ -183,7 +184,8 @@ private fun PreviewConnectPayinMethodScreen() { ), selectedProvider = MemberPaymentProvider.TRUSTLY, currentProviders = listOf( - MemberPaymentProvider.SWISH) + MemberPaymentProvider.SWISH, + ), ), onProviderSelected = {}, onSubmitSelected = {}, From 53fc07a67b01ba2eb0d6c83a506be7d55a909fbc Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 11 Sep 2026 14:54:40 +0200 Subject: [PATCH 63/75] remove impl --- .../android/apollo/octopus/schema.graphqls | 484 +++++++++--------- .../graphql/RemovePayinMethodMutation.graphql | 5 + .../payin/account/data/RemoveMethodUseCase.kt | 41 +- .../account/navigation/PayinAccountEntries.kt | 1 + .../PayinMethodDetailsDestination.kt | 13 + .../PayinMethodDetailsViewModel.kt | 21 +- .../RemovePayinMethodBottomSheet.kt | 14 + .../PayinAccountOverviewDestination.kt | 13 + .../overview/PayinAccountOverviewViewModel.kt | 17 + 9 files changed, 368 insertions(+), 241 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/graphql/RemovePayinMethodMutation.graphql diff --git a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls index e0976850c6..3361076d26 100644 --- a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls +++ b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls @@ -1,3 +1,228 @@ +""" +A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. +""" +type __Schema { + description: String + """ + A list of all types supported by this server. + """ + types: [__Type!]! + """ + The type that query operations will be rooted at. + """ + queryType: __Type! + """ + If this server supports mutation, the type that mutation operations will be rooted at. + """ + mutationType: __Type + """ + If this server support subscription, the type that subscription operations will be rooted at. + """ + subscriptionType: __Type + """ + A list of all directives supported by this server. + """ + directives: [__Directive!]! +} +""" +The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. +Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types. +""" +type __Type { + kind: __TypeKind! + name: String + description: String + fields(includeDeprecated: Boolean = false): [__Field!] + interfaces: [__Type!] + possibleTypes: [__Type!] + enumValues(includeDeprecated: Boolean = false): [__EnumValue!] + inputFields(includeDeprecated: Boolean = false): [__InputValue!] + ofType: __Type + specifiedByURL: String +} +""" +An enum describing what kind of type a given `__Type` is. +""" +enum __TypeKind { + """ + Indicates this type is a scalar. + """ + SCALAR + """ + Indicates this type is an object. `fields` and `interfaces` are valid fields. + """ + OBJECT + """ + Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields. + """ + INTERFACE + """ + Indicates this type is a union. `possibleTypes` is a valid field. + """ + UNION + """ + Indicates this type is an enum. `enumValues` is a valid field. + """ + ENUM + """ + Indicates this type is an input object. `inputFields` is a valid field. + """ + INPUT_OBJECT + """ + Indicates this type is a list. `ofType` is a valid field. + """ + LIST + """ + Indicates this type is a non-null. `ofType` is a valid field. + """ + NON_NULL +} +""" +Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type. +""" +type __Field { + name: String! + description: String + args(includeDeprecated: Boolean = false): [__InputValue!]! + type: __Type! + isDeprecated: Boolean! + deprecationReason: String +} +""" +Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. +""" +type __InputValue { + name: String! + description: String + type: __Type! + """ + A GraphQL-formatted string representing the default value for this input value. + """ + defaultValue: String + isDeprecated: Boolean! + deprecationReason: String +} +""" +One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string. +""" +type __EnumValue { + name: String! + description: String + isDeprecated: Boolean! + deprecationReason: String +} +""" +A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. +In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor. +""" +type __Directive { + name: String! + description: String + locations: [__DirectiveLocation!]! + args(includeDeprecated: Boolean = false): [__InputValue!]! + isRepeatable: Boolean! +} +""" +A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies. +""" +enum __DirectiveLocation { + """ + Location adjacent to a query operation. + """ + QUERY + """ + Location adjacent to a mutation operation. + """ + MUTATION + """ + Location adjacent to a subscription operation. + """ + SUBSCRIPTION + """ + Location adjacent to a field. + """ + FIELD + """ + Location adjacent to a fragment definition. + """ + FRAGMENT_DEFINITION + """ + Location adjacent to a fragment spread. + """ + FRAGMENT_SPREAD + """ + Location adjacent to an inline fragment. + """ + INLINE_FRAGMENT + """ + Location adjacent to a variable definition. + """ + VARIABLE_DEFINITION + """ + Location adjacent to a schema definition. + """ + SCHEMA + """ + Location adjacent to a scalar definition. + """ + SCALAR + """ + Location adjacent to an object type definition. + """ + OBJECT + """ + Location adjacent to a field definition. + """ + FIELD_DEFINITION + """ + Location adjacent to an argument definition. + """ + ARGUMENT_DEFINITION + """ + Location adjacent to an interface definition. + """ + INTERFACE + """ + Location adjacent to a union definition. + """ + UNION + """ + Location adjacent to an enum definition. + """ + ENUM + """ + Location adjacent to an enum value definition. + """ + ENUM_VALUE + """ + Location adjacent to an input object type definition. + """ + INPUT_OBJECT + """ + Location adjacent to an input object field definition. + """ + INPUT_FIELD_DEFINITION +} +""" +The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. +""" +scalar Int +""" +The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). +""" +scalar Float +""" +The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. +""" +scalar String +""" +The `Boolean` scalar type represents `true` or `false`. +""" +scalar Boolean +""" +The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. +""" +scalar ID type ActiveAddon { id: ID! """ @@ -16,14 +241,6 @@ type ActiveAddon { displayDescription: String } """ -The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. -""" -scalar ID -""" -The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. -""" -scalar String -""" An addon is an additional insurance that can be added to a contract. It is usually a separate insurance. """ type Addon { @@ -96,10 +313,6 @@ type AddonComparisonCell { """ coverageText: String } -""" -The `Boolean` scalar type represents `true` or `false`. -""" -scalar Boolean type AddonComparisonRow { """ Peril title @@ -410,10 +623,6 @@ type Agreement { """ addons: [Addon!]! } -""" -The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. -""" -scalar Int enum AgreementCreationCause { """ No creation cause is known - this is due to the agreement being older than when we started tracking @@ -984,10 +1193,6 @@ type ClaimIntent { """ createdClaim: Claim } -""" -The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). -""" -scalar Float input ClaimIntentFormFieldSearchInput { stepId: ID! fieldId: ID! @@ -2656,7 +2861,11 @@ type Member { Id of the latest charge if it failed and self manual charge allowed. """ missedChargeIdToChargeManually: UUID - paymentMethods: MemberPaymentMethods! + """ + The member's payment methods: what they can be charged with, what they are paid out to, + and which connection is still missing. + """ + paymentMethods("Rollout gate for payment providers, not a frozen contract per version.\n\n- `1`: Trustly, Nordea, Invoice — stays as it is.\n- `2`: `1` plus what is switched on, today Swish — can change between releases.\n\nAsk for `2` only once the client can render everything it may return." version: Int! = 1): MemberPaymentMethods! conversations: [Conversation!]! legacyConversation: Conversation """ @@ -3167,9 +3376,9 @@ type MemberPaymentMethods { """ Indicates which payment connection the member is missing, if any. The rule is: - If member has only QASA-paid agreements, the payin connection is irrelevant and only payout is checked. - Returns PAYOUT if no default payout method exists, else null. + Returns PAYOUT if no default payout method exists, else null. - Otherwise (any non-QASA agreement, e.g. member-paid), payin takes priority: - Returns PAYIN if no default payin method exists, else PAYOUT if no default payout method exists, else null. + Returns PAYIN if no default payin method exists, else PAYOUT if no default payout method exists, else null. - If member has no ongoing (active or future) agreements, returns null. """ missingConnection: MissingPaymentConnection @@ -3756,6 +3965,10 @@ type Mutation { Setup Swish payin method for the member. Requires member consent in Swish app. """ paymentMethodSetupSwishPayin(input: PaymentMethodSetupSwishInput!): PaymentMethodSetupOutput! + """ + Revoke and disconnect an ACTIVE payment method + """ + paymentMethodRemoveMethod(provider: MemberPaymentProvider!): UserError """ A member can have multiple ACTIVE payment methods where one of those is default. This mutation changes the members default payment method for charging to any of his/hers other active payment methods. @@ -4136,6 +4349,16 @@ enum PaymentMethodSetupStatus { """ FAILED } +type PaymentMethodSetupStatusOutput { + """ + The status of the setup process. If FAILED the reason for failure can be found in the `error` field. + """ + status: PaymentMethodSetupStatus! + """ + An error message. Only sat if the setup process failed. + """ + error: UserError +} input PaymentMethodSetupSwishInput { """ The Swish mobile number to use for payout or payin. @@ -4809,6 +5032,10 @@ type PuppyGuideStoryMutationOutput { success: Boolean! } type Query { + """ + Returns the status of a setup method order. Returns null if no status found + """ + paymentMethodSetupStatus(orderId: ID!): PaymentMethodSetupStatusOutput """ Return a conversation for a given ID. Note that this can return null if `Mutation.conversationStart` has not been called with this ID. @@ -5744,220 +5971,14 @@ type ValueString { } union ValueType = ValueString|ValueInteger|ValueBoolean|ValueDate|ValueArray """ -A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. -""" -type __Schema { - description: String - """ - A list of all types supported by this server. - """ - types: [__Type!]! - """ - The type that query operations will be rooted at. - """ - queryType: __Type! - """ - If this server supports mutation, the type that mutation operations will be rooted at. - """ - mutationType: __Type - """ - If this server support subscription, the type that subscription operations will be rooted at. - """ - subscriptionType: __Type - """ - A list of all directives supported by this server. - """ - directives: [__Directive!]! -} -""" -The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. -Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types. -""" -type __Type { - kind: __TypeKind! - name: String - description: String - specifiedByURL: String - fields(includeDeprecated: Boolean = false): [__Field!] - interfaces: [__Type!] - possibleTypes: [__Type!] - enumValues(includeDeprecated: Boolean = false): [__EnumValue!] - inputFields(includeDeprecated: Boolean = false): [__InputValue!] - ofType: __Type -} -""" -An enum describing what kind of type a given `__Type` is. -""" -enum __TypeKind { - """ - Indicates this type is a scalar. - """ - SCALAR - """ - Indicates this type is an object. `fields` and `interfaces` are valid fields. - """ - OBJECT - """ - Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields. - """ - INTERFACE - """ - Indicates this type is a union. `possibleTypes` is a valid field. - """ - UNION - """ - Indicates this type is an enum. `enumValues` is a valid field. - """ - ENUM - """ - Indicates this type is an input object. `inputFields` is a valid field. - """ - INPUT_OBJECT - """ - Indicates this type is a list. `ofType` is a valid field. - """ - LIST - """ - Indicates this type is a non-null. `ofType` is a valid field. - """ - NON_NULL -} -""" -Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type. -""" -type __Field { - name: String! - description: String - args(includeDeprecated: Boolean = false): [__InputValue!]! - type: __Type! - isDeprecated: Boolean! - deprecationReason: String -} -""" -Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. -""" -type __InputValue { - name: String! - description: String - type: __Type! - """ - A GraphQL-formatted string representing the default value for this input value. - """ - defaultValue: String - isDeprecated: Boolean! - deprecationReason: String -} -""" -One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string. -""" -type __EnumValue { - name: String! - description: String - isDeprecated: Boolean! - deprecationReason: String -} -""" -A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. -In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor. -""" -type __Directive { - name: String! - description: String - isRepeatable: Boolean! - locations: [__DirectiveLocation!]! - args(includeDeprecated: Boolean = false): [__InputValue!]! -} -""" -A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies. +Directs the executor to skip this field or fragment when the `if` argument is true. """ -enum __DirectiveLocation { - """ - Location adjacent to a query operation. - """ - QUERY - """ - Location adjacent to a mutation operation. - """ - MUTATION - """ - Location adjacent to a subscription operation. - """ - SUBSCRIPTION - """ - Location adjacent to a field. - """ - FIELD - """ - Location adjacent to a fragment definition. - """ - FRAGMENT_DEFINITION - """ - Location adjacent to a fragment spread. - """ - FRAGMENT_SPREAD - """ - Location adjacent to an inline fragment. - """ - INLINE_FRAGMENT - """ - Location adjacent to a variable definition. - """ - VARIABLE_DEFINITION - """ - Location adjacent to a schema definition. - """ - SCHEMA - """ - Location adjacent to a scalar definition. - """ - SCALAR - """ - Location adjacent to an object type definition. - """ - OBJECT - """ - Location adjacent to a field definition. - """ - FIELD_DEFINITION - """ - Location adjacent to an argument definition. - """ - ARGUMENT_DEFINITION - """ - Location adjacent to an interface definition. - """ - INTERFACE - """ - Location adjacent to a union definition. - """ - UNION - """ - Location adjacent to an enum definition. - """ - ENUM - """ - Location adjacent to an enum value definition. - """ - ENUM_VALUE - """ - Location adjacent to an input object type definition. - """ - INPUT_OBJECT - """ - Location adjacent to an input object field definition. - """ - INPUT_FIELD_DEFINITION -} -directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT +directive @skip("Skipped when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT """ Directs the executor to include this field or fragment only when the `if` argument is true. """ directive @include("Included when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT """ -Directs the executor to skip this field or fragment when the `if` argument is true. -""" -directive @skip("Skipped when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -""" Marks an element of a GraphQL schema as no longer supported. """ directive @deprecated("Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/)." reason: String = "No longer supported") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE @@ -5965,6 +5986,7 @@ directive @deprecated("Explains why this element was deprecated, usually also in Exposes a URL that specifies the behavior of this scalar. """ directive @specifiedBy("The URL that specifies the behavior of this scalar." url: String!) on SCALAR +directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT schema { query: Query mutation: Mutation diff --git a/app/feature/feature-payin-account/src/main/graphql/RemovePayinMethodMutation.graphql b/app/feature/feature-payin-account/src/main/graphql/RemovePayinMethodMutation.graphql new file mode 100644 index 0000000000..e7512b3ea5 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/graphql/RemovePayinMethodMutation.graphql @@ -0,0 +1,5 @@ +mutation RemovePayinMethod($provider: MemberPaymentProvider!) { + paymentMethodRemoveMethod(provider: $provider) { + message + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt index 902599498d..99558e1d09 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/RemoveMethodUseCase.kt @@ -1,12 +1,20 @@ package com.hedvig.android.feature.payin.account.data import arrow.core.Either -import arrow.core.right +import arrow.core.raise.context.either +import arrow.core.raise.context.raise +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.ErrorMessage +import com.hedvig.android.apollo.NetworkCacheManager +import com.hedvig.android.apollo.safeExecuteAllowingPartialResponses import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn +import octopus.RemovePayinMethodMutation import octopus.type.MemberPaymentProvider internal interface RemoveMethodUseCase { @@ -16,9 +24,32 @@ internal interface RemoveMethodUseCase { @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) @Inject -internal class RemoveMethodUseCaseImpl : RemoveMethodUseCase { - override suspend fun invoke(provider: MemberPaymentProvider): Either { - // TODO: run the remove-payin-method mutation once the backend exposes one. - return Unit.right() +internal class RemoveMethodUseCaseImpl( + private val apolloClient: ApolloClient, + private val networkCacheManager: NetworkCacheManager, +) : RemoveMethodUseCase { + override suspend fun invoke(provider: MemberPaymentProvider): Either = either { + apolloClient + .mutation(RemovePayinMethodMutation(provider)) + .safeExecuteAllowingPartialResponses() + .fold( + fa = { error -> + logcat(LogPriority.ERROR) { "RemovePayinMethodMutation error: $error" } + raise(ErrorMessage()) + }, + fb = { result -> + val userError = result.paymentMethodRemoveMethod?.message + if (userError != null) { + logcat(LogPriority.ERROR) { "RemovePayinMethodMutation user error: $userError" } + raise(ErrorMessage(userError)) + } + // The removed method is still in every cached payment query until the next fetch. + networkCacheManager.clearCache() + }, + fab = { errors, _ -> + logcat(LogPriority.ERROR) { "RemovePayinMethodMutation data with errors: $errors" } + raise(ErrorMessage()) + }, + ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 3e0e7dfc13..be075d4501 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -69,6 +69,7 @@ fun EntryProviderScope.payinAccountEntries( PayinMethodDetailsDestination( viewModel = viewModel, navigateUp = backstack::navigateUp, + navigateBack = backstack::popBackstack, onChangeMethod = dropUnlessResumed { method: PayinAccount -> when (method) { is PayinAccount.Trustly -> { diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt index af4e06bf66..83e221394b 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -62,6 +63,7 @@ import hedvig.resources.PROFILE_PAYMENT_CHANGE_BANK_ACCOUNT import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION import hedvig.resources.Res import hedvig.resources.general_close_button +import hedvig.resources.something_went_wrong import hedvig.resources.swish import org.jetbrains.compose.resources.stringResource @@ -69,9 +71,14 @@ import org.jetbrains.compose.resources.stringResource internal fun PayinMethodDetailsDestination( viewModel: PayinMethodDetailsViewModel, navigateUp: () -> Unit, + navigateBack: () -> Unit, onChangeMethod: (PayinAccount) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val hasRemovedMethod = (uiState as? PayinMethodDetailsUiState.Content)?.hasRemovedMethod == true + LaunchedEffect(hasRemovedMethod) { + if (hasRemovedMethod) navigateBack() + } when (val state = uiState) { PayinMethodDetailsUiState.Loading -> { HedvigScaffold( @@ -104,6 +111,9 @@ internal fun PayinMethodDetailsDestination( method = state.method, chargingDay = state.chargingDay, isRemoving = state.isRemoving, + removeErrorMessage = state.removeError?.let { + it.message ?: stringResource(Res.string.something_went_wrong) + }, navigateUp = navigateUp, onChangeMethod = { onChangeMethod(state.method) }, onConfirmRemoveMethod = { viewModel.emit(PayinMethodDetailsEvent.RemoveMethod) }, @@ -117,6 +127,7 @@ private fun PayinMethodDetailsScreen( method: PayinAccount, chargingDay: Int?, isRemoving: Boolean, + removeErrorMessage: String?, navigateUp: () -> Unit, onChangeMethod: () -> Unit, onConfirmRemoveMethod: () -> Unit, @@ -127,6 +138,7 @@ private fun PayinMethodDetailsScreen( RemovePayinMethodBottomSheet( sheetState = removeSheetState, isRemoving = isRemoving, + errorMessage = removeErrorMessage, onConfirmRemove = onConfirmRemoveMethod, ) HedvigScaffold( @@ -343,6 +355,7 @@ private fun PreviewPayinMethodDetailsDestination( method = method, chargingDay = 27, isRemoving = false, + removeErrorMessage = null, navigateUp = {}, onChangeMethod = {}, onConfirmRemoveMethod = {}, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt index b37765bfd2..f4ad209bd7 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase @@ -47,6 +48,9 @@ internal sealed interface PayinMethodDetailsUiState { val method: PayinAccount, val chargingDay: Int?, val isRemoving: Boolean = false, + val removeError: ErrorMessage? = null, + /** The method is gone, so this screen has nothing left to show and the caller pops it. */ + val hasRemovedMethod: Boolean = false, ) : PayinMethodDetailsUiState } @@ -82,11 +86,18 @@ internal class PayinMethodDetailsPresenter( LaunchedEffect(methodToRemove) { val method = methodToRemove ?: return@LaunchedEffect val content = uiState as? PayinMethodDetailsUiState.Content ?: return@LaunchedEffect - uiState = content.copy(isRemoving = true) - removeMethodUseCase.invoke(method.provider) - // TODO: reload the methods on success and surface the failure once the API exists. - uiState = content.copy(isRemoving = false) - methodToRemove = null + uiState = content.copy(isRemoving = true, removeError = null) + removeMethodUseCase.invoke(method.provider).fold( + ifLeft = { error -> + methodToRemove = null + uiState = content.copy(isRemoving = false, removeError = error) + }, + ifRight = { + // Stays in the removing state until the screen is popped, so the button does not flash + // back to idle over a method that no longer exists. + uiState = content.copy(isRemoving = true, hasRemovedMethod = true) + }, + ) } CollectEvents { event -> diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index fd0da70db2..23400f79ab 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -14,11 +14,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigTheme.colorScheme +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize import com.hedvig.android.design.system.hedvig.PaymentMethodTile import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge @@ -43,12 +45,14 @@ import org.jetbrains.compose.resources.stringResource internal fun RemovePayinMethodBottomSheet( sheetState: HedvigBottomSheetState, isRemoving: Boolean, + errorMessage: String?, onConfirmRemove: () -> Unit, ) { HedvigBottomSheet(sheetState) { method -> RemovePayinMethodBottomSheetContent( method = method, isRemoving = isRemoving, + errorMessage = errorMessage, onConfirmRemove = onConfirmRemove, onDismiss = { sheetState.dismiss() }, ) @@ -59,6 +63,7 @@ internal fun RemovePayinMethodBottomSheet( private fun RemovePayinMethodBottomSheetContent( method: PayinAccount, isRemoving: Boolean, + errorMessage: String?, onConfirmRemove: () -> Unit, onDismiss: () -> Unit, ) { @@ -90,6 +95,14 @@ private fun RemovePayinMethodBottomSheetContent( mark = { PayinMethodMark(method, Modifier.size(PaymentMethodMarkSize)) }, ) } + if (errorMessage != null) { + HedvigNotificationCard( + message = errorMessage, + priority = NotificationPriority.Error, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + } val option = method.toRadioOption() RadioGroup( options = listOf(option), @@ -128,6 +141,7 @@ private fun PreviewRemovePayinMethodBottomSheetContent() { RemovePayinMethodBottomSheetContent( method = PayinAccount.SwishPayin("0709901232", isPending = false, isDefault = false), isRemoving = false, + errorMessage = null, onConfirmRemove = {}, onDismiss = {}, ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index a5c3cc510e..87f494521d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -13,10 +13,14 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.ButtonDefaults @@ -54,6 +58,15 @@ internal fun PayinAccountOverviewDestination( navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + var hasResumedOnce by rememberSaveable { mutableStateOf(false) } + LifecycleResumeEffect(Unit) { + if (hasResumedOnce) { + viewModel.emit(PayinAccountOverviewEvent.Refresh) + } else { + hasResumedOnce = true + } + onPauseOrDispose {} + } PayinAccountOverviewScreen( uiState = uiState, onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index 952a32fdcc..b6d76da11a 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -28,6 +28,8 @@ internal class PayinAccountOverviewViewModel( internal sealed interface PayinAccountOverviewEvent { data object Retry : PayinAccountOverviewEvent + + data object Refresh : PayinAccountOverviewEvent } internal sealed interface PayinAccountOverviewUiState { @@ -49,6 +51,7 @@ internal class PayinAccountOverviewPresenter( lastState: PayinAccountOverviewUiState, ): PayinAccountOverviewUiState { var loadIteration by remember { mutableIntStateOf(0) } + var refreshIteration by remember { mutableIntStateOf(0) } var uiState by remember { mutableStateOf(lastState) } LaunchedEffect(loadIteration) { @@ -64,9 +67,23 @@ internal class PayinAccountOverviewPresenter( ) } + // Picks up methods connected or removed further down the flow. It refreshes in place and keeps + // what is on screen if the refetch fails, so returning here never flashes to loading or to an + // error over a list that is still perfectly usable. + LaunchedEffect(refreshIteration) { + if (refreshIteration == 0) return@LaunchedEffect + getPayinAccountUseCase.invoke().onRight { data -> + uiState = PayinAccountOverviewUiState.Content( + currentMethods = data.currentMethods, + availablePayinMethods = data.availablePayinMethods, + ) + } + } + CollectEvents { event -> when (event) { PayinAccountOverviewEvent.Retry -> loadIteration++ + PayinAccountOverviewEvent.Refresh -> refreshIteration++ } } From 27558c1133edf2bc6a65f99adbc1ca70303a3134 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 11 Sep 2026 15:42:18 +0200 Subject: [PATCH 64/75] visibility logic --- .../androidMain/res/values-sv-rSE/strings.xml | 12 +++-- .../src/androidMain/res/values/strings.xml | 12 +++-- .../values-sv-rSE/strings.xml | 12 +++-- .../composeResources/values/strings.xml | 12 +++-- .../PayinMethodDetailsDestination.kt | 16 +++---- .../PayinMethodDetailsViewModel.kt | 4 -- .../PayinAccountOverviewDestination.kt | 24 +++++----- .../SelectPrimaryPayinMethodDestination.kt | 45 ++++++++++++------- 8 files changed, 83 insertions(+), 54 deletions(-) diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 87cb56080b..94a87150e3 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -729,6 +729,7 @@ Giltig till %1$s Lägg till betalningsmetod Välj förvald metod + Din nästa betalning dras från %1$s. Utbetalningar skickas också till detta konto. Lägg till en ny betalningsmetod för din försäkring Koppla betalning Betalningshistorik @@ -745,11 +746,14 @@ Välj betalningsmetod Du kan lägga till den igen när som helst Ta bort betalningsmetod - Godkänn i Swish-appen + Godkänn kopplingen i Swish Byt telefonnummer - How does recurring Swish work? - Lägg till ett betalsätt för att aktivera din försäkring - Koppla Swish till det här numret + Så fungerar återkommande betalningar med Swish + Connection failed + Vi skickar kopplingen till Swish på det här numret + Charged automatically each month + Swish is connected + Bekräfta ditt Swish-nummer Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index bca4ca2a21..d9f52869a4 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -729,6 +729,7 @@ Valid until %1$s Add payment method Change primary method + Your next payment will be drawn from %1$s. Claims payouts will also be sent to this account. Add a new payment method for your insurance Connect payment Payment history @@ -745,11 +746,14 @@ Choose payment method You can add it back any time Remove payment method - Approve in the Swish app + Approve the connection in Swish Change phone number - How does recurring Swish work? - Add a payment method to activate your insurance - Connect Swish to this number + How recurring payments with Swish work + Connection failed + We\'ll send the connection request to Swish on this number + Charged automatically each month + Swish is connected + Confirm your Swish number Add payout method Direct payment via Invoice Instant payout with Swish diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 8f44b4b0cc..68238d4c9c 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -728,6 +728,7 @@ Giltig till %1$s Lägg till betalningsmetod Välj förvald metod + Din nästa betalning dras från %1$s. Utbetalningar skickas också till detta konto. Lägg till en ny betalningsmetod för din försäkring Koppla betalning Betalningshistorik @@ -744,11 +745,14 @@ Välj betalningsmetod Du kan lägga till den igen när som helst Ta bort betalningsmetod - Godkänn i Swish-appen + Godkänn kopplingen i Swish Byt telefonnummer - How does recurring Swish work? - Lägg till ett betalsätt för att aktivera din försäkring - Koppla Swish till det här numret + Så fungerar återkommande betalningar med Swish + Connection failed + Vi skickar kopplingen till Swish på det här numret + Charged automatically each month + Swish is connected + Bekräfta ditt Swish-nummer Lägg till utbetalningsmetod Direktutbetalning via Faktura Snabb utbetalning med Swish diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index 5bf3426bb1..b3ca4adff6 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -728,6 +728,7 @@ Valid until %1$s Add payment method Change primary method + Your next payment will be drawn from %1$s. Claims payouts will also be sent to this account. Add a new payment method for your insurance Connect payment Payment history @@ -744,11 +745,14 @@ Choose payment method You can add it back any time Remove payment method - Approve in the Swish app + Approve the connection in Swish Change phone number - How does recurring Swish work? - Add a payment method to activate your insurance - Connect Swish to this number + How recurring payments with Swish work + Connection failed + We'll send the connection request to Swish on this number + Charged automatically each month + Swish is connected + Confirm your Swish number Add payout method Direct payment via Invoice Instant payout with Swish diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt index 83e221394b..ebc89deca0 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -235,15 +235,13 @@ private fun PayinMethodDetailsScreen( ) Spacer(Modifier.height(8.dp)) } - if (!method.isDefault) { - HedvigRedTextButton( - text = stringResource(Res.string.GENERAL_REMOVE), - onClick = { removeSheetState.show(method) }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - } + HedvigRedTextButton( + text = stringResource(Res.string.GENERAL_REMOVE), + onClick = { removeSheetState.show(method) }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) Spacer(Modifier.height(16.dp)) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt index f4ad209bd7..38a8a3a458 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -49,7 +49,6 @@ internal sealed interface PayinMethodDetailsUiState { val chargingDay: Int?, val isRemoving: Boolean = false, val removeError: ErrorMessage? = null, - /** The method is gone, so this screen has nothing left to show and the caller pops it. */ val hasRemovedMethod: Boolean = false, ) : PayinMethodDetailsUiState } @@ -73,7 +72,6 @@ internal class PayinMethodDetailsPresenter( ifLeft = { uiState = PayinMethodDetailsUiState.Error }, ifRight = { data -> val method = data.currentMethods.firstOrNull { it.id == methodId } - // The method can be gone if it was disconnected on another device between listing and opening it. uiState = if (method == null) { PayinMethodDetailsUiState.Error } else { @@ -93,8 +91,6 @@ internal class PayinMethodDetailsPresenter( uiState = content.copy(isRemoving = false, removeError = error) }, ifRight = { - // Stays in the removing state until the screen is popped, so the button does not flash - // back to idle over a method that no longer exists. uiState = content.copy(isRemoving = true, hasRemovedMethod = true) }, ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index 87f494521d..bd3a07581c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -177,7 +177,9 @@ private fun PayoutAccountContent( .padding(horizontal = 16.dp), ) } - if (currentMethods.size > 1) { + if (currentMethods + .filter { !it.isPending } + .size > 1) { Spacer(Modifier.height(8.dp)) HedvigTextButton( text = stringResource(Res.string.PAYMENT_CHOOSE_PRIMARY_BUTTON), @@ -194,7 +196,7 @@ private fun PayoutAccountContent( @Composable private fun CurrentPayinMethodRow(method: PayinAccount, onClick: () -> Unit, modifier: Modifier = Modifier) { HedvigCard( - onClick = onClick, + onClick = if (method.isPending) null else onClick, shape = HedvigTheme.shapes.cornerLarge, modifier = modifier.fillMaxWidth(), ) { @@ -205,11 +207,13 @@ private fun CurrentPayinMethodRow(method: PayinAccount, onClick: () -> Unit, mod if (method.isDefault) { PrimaryMethodLabel() } - Icon( - imageVector = HedvigIcons.ChevronRight, - contentDescription = EmptyContentDescription, - modifier = Modifier.size(24.dp), - ) + if (!method.isPending) { + Icon( + imageVector = HedvigIcons.ChevronRight, + contentDescription = EmptyContentDescription, + modifier = Modifier.size(24.dp), + ) + } }, ) } @@ -272,13 +276,13 @@ private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterPr clearingNumber = "****", accountNumber = "*45678", bankName = "Swedbank", - isPending = false, - isDefault = true, + isPending = true, + isDefault = false, ), PayinAccount.SwishPayin( phoneNumber = "0701234567", isPending = false, - isDefault = false, + isDefault = true, ), ), availablePayinMethods = listOf(MemberPaymentProvider.SWISH), diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index f1b2320e3a..946d16afb4 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold @@ -60,6 +61,7 @@ import com.hedvig.android.feature.payin.account.ui.components.payinMethodTitle import com.hedvig.android.feature.payin.account.ui.components.toRadioOption import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.ConfirmSelectedMethod import com.hedvig.android.feature.payin.account.ui.primary.SelectPrimaryPayinMethodEvent.SelectMethod +import hedvig.resources.PAYMENT_CONFIRM_PRIMARY_WARNING import hedvig.resources.PAYMENT_CONNECT_SUBTITLE import hedvig.resources.PAYMENT_CONNECT_TITLE import hedvig.resources.PAYMENT_PRIMARY_CONFIRM_TITLE @@ -123,13 +125,27 @@ private fun SelectPrimaryPayinMethodScreen( .align(Alignment.CenterHorizontally), ) Spacer(Modifier.weight(1f)) + val currentDefault = uiState.methods.firstOrNull { it.isDefault } + if (currentDefault != null) { + HedvigCard( + shape = HedvigTheme.shapes.cornerLarge, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + PayinMethodRow( + method = currentDefault, + modifier = Modifier.fillMaxWidth(), + endSlot = { PrimaryMethodLabel() }, + ) + } + Spacer(Modifier.height(4.dp)) + } RadioGroup( options = uiState.methods - .sortedByDescending { it.isDefault } + .filter { !it.isPending } + .filter { !it.isDefault } .map { it.toRadioOption() }, - disabledOptions = uiState.methods - .filter { it.isDefault } - .map { it.toRadioOption().id }, selectedOption = uiState.selectedMethod?.let { RadioOptionId(it.provider.rawValue) }, onRadioOptionSelected = { id -> val method = uiState.methods.firstOrNull { it.provider.rawValue == id.id } @@ -155,7 +171,8 @@ private fun SelectPrimaryPayinMethodScreen( val method = uiState.selectedMethod if (method != null) confirmationSheetState.show(method) }, - enabled = uiState.selectedMethod != null, + enabled = uiState.selectedMethod != null && + !uiState.selectedMethod.isDefault, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -206,16 +223,14 @@ private fun ConfirmPrimaryPayinMethodBottomSheetContent( modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(24.dp)) - HedvigNotificationCard( - // TODO: Add "Your next payment will be drawn from {method}. Claims payouts will also be sent to this - // account." / "Din nästa betalning dras från {method}. Skadeutbetalningar skickas också till detta konto." - // to Lokalise - message = "Your next payment will be drawn from ${payinMethodTitle(method)}. " + - "Claims payouts will also be sent to this account.", - priority = Info, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(16.dp)) + if (method.provider != MemberPaymentProvider.INVOICE) { + HedvigNotificationCard( + message = stringResource(Res.string.PAYMENT_CONFIRM_PRIMARY_WARNING, payinMethodTitle(method)), + priority = Info, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) + } Surface( shape = HedvigTheme.shapes.cornerLarge, color = colorScheme.surfacePrimary, From 9708f08926f9aec6a7208e1bd1b1437958aa1580 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 11 Sep 2026 16:28:27 +0200 Subject: [PATCH 65/75] SwishPayinStatus --- .../androidMain/res/values-sv-rSE/strings.xml | 1 + .../src/androidMain/res/values/strings.xml | 1 + .../values-sv-rSE/strings.xml | 1 + .../composeResources/values/strings.xml | 1 + .../graphql/PaymentMethodSetupStatus.graphql | 8 + .../graphql/SetupSwishPayinMutation.graphql | 1 + .../data/GetSwishPayinSetupStatusUseCase.kt | 68 ++++++ .../account/data/SetupSwishPayinUseCase.kt | 35 ++- .../navigation/PayinAccountDestination.kt | 11 + .../account/navigation/PayinAccountEntries.kt | 36 ++- .../setupswish/SetupSwishPayinDestination.kt | 84 ++----- .../ui/setupswish/SetupSwishPayinViewModel.kt | 34 +-- .../setupswish/SwishPayinStatusDestination.kt | 225 ++++++++++++++++++ .../setupswish/SwishPayinStatusViewModel.kt | 132 ++++++++++ 14 files changed, 534 insertions(+), 104 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/graphql/PaymentMethodSetupStatus.graphql create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetSwishPayinSetupStatusUseCase.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt diff --git a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml index 94a87150e3..d5d60cced2 100644 --- a/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values-sv-rSE/strings.xml @@ -728,6 +728,7 @@ Kommande betalning Giltig till %1$s Lägg till betalningsmetod + Change payment method Välj förvald metod Din nästa betalning dras från %1$s. Utbetalningar skickas också till detta konto. Lägg till en ny betalningsmetod för din försäkring diff --git a/app/core/core-resources/src/androidMain/res/values/strings.xml b/app/core/core-resources/src/androidMain/res/values/strings.xml index d9f52869a4..618e7114ee 100644 --- a/app/core/core-resources/src/androidMain/res/values/strings.xml +++ b/app/core/core-resources/src/androidMain/res/values/strings.xml @@ -728,6 +728,7 @@ Upcoming payment Valid until %1$s Add payment method + Change payment method Change primary method Your next payment will be drawn from %1$s. Claims payouts will also be sent to this account. Add a new payment method for your insurance diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 68238d4c9c..deb691d598 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -727,6 +727,7 @@ Kommande betalning Giltig till %1$s Lägg till betalningsmetod + Change payment method Välj förvald metod Din nästa betalning dras från %1$s. Utbetalningar skickas också till detta konto. Lägg till en ny betalningsmetod för din försäkring diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index b3ca4adff6..f63e4976f7 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -727,6 +727,7 @@ Upcoming payment Valid until %1$s Add payment method + Change payment method Change primary method Your next payment will be drawn from %1$s. Claims payouts will also be sent to this account. Add a new payment method for your insurance diff --git a/app/feature/feature-payin-account/src/main/graphql/PaymentMethodSetupStatus.graphql b/app/feature/feature-payin-account/src/main/graphql/PaymentMethodSetupStatus.graphql new file mode 100644 index 0000000000..ead02da730 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/graphql/PaymentMethodSetupStatus.graphql @@ -0,0 +1,8 @@ +query PaymentMethodSetupStatus($orderId: ID!) { + paymentMethodSetupStatus(orderId: $orderId) { + status + error { + message + } + } +} diff --git a/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql b/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql index 200394d827..4bdf9f3c25 100644 --- a/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql +++ b/app/feature/feature-payin-account/src/main/graphql/SetupSwishPayinMutation.graphql @@ -5,5 +5,6 @@ mutation SetupSwishPayin($input: PaymentMethodSetupSwishInput!) { } status url + orderId } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetSwishPayinSetupStatusUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetSwishPayinSetupStatusUseCase.kt new file mode 100644 index 0000000000..7685864afc --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetSwishPayinSetupStatusUseCase.kt @@ -0,0 +1,68 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +import arrow.core.raise.either +import com.apollographql.apollo.ApolloClient +import com.apollographql.cache.normalized.FetchPolicy +import com.apollographql.cache.normalized.fetchPolicy +import com.hedvig.android.apollo.ErrorMessage +import com.hedvig.android.apollo.NetworkCacheManager +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import octopus.PaymentMethodSetupStatusQuery +import octopus.type.PaymentMethodSetupStatus + +/** Where a setup order the member is approving in the Swish app has got to. */ +internal sealed interface SwishPayinSetupStatus { + data object Active : SwishPayinSetupStatus + + data object Pending : SwishPayinSetupStatus + + data class Failed(val message: String?) : SwishPayinSetupStatus +} + +internal interface GetSwishPayinSetupStatusUseCase { + suspend fun invoke(orderId: String): Either +} + +@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +@Inject +internal class GetSwishPayinSetupStatusUseCaseImpl( + private val apolloClient: ApolloClient, + private val networkCacheManager: NetworkCacheManager, +) : GetSwishPayinSetupStatusUseCase { + override suspend fun invoke(orderId: String): Either = either { + val output = apolloClient + .query(PaymentMethodSetupStatusQuery(orderId)) + .fetchPolicy(FetchPolicy.NetworkOnly) + .safeExecute(::ErrorMessage) + .bind() + .paymentMethodSetupStatus + + when (output?.status) { + PaymentMethodSetupStatus.ACTIVE -> { + // The new method is absent from every cached payment query until the next fetch. + networkCacheManager.clearCache() + SwishPayinSetupStatus.Active + } + + PaymentMethodSetupStatus.FAILED -> { + SwishPayinSetupStatus.Failed(output.error?.message) + } + + // A null output is an order the backend does not know about yet, which is indistinguishable + // from one still being processed, so both keep the caller waiting rather than failing it. + PaymentMethodSetupStatus.PENDING, + PaymentMethodSetupStatus.UNKNOWN__, + null, + -> { + SwishPayinSetupStatus.Pending + } + } + } +} diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt index d5f114f4c8..a79fc49f0c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -42,23 +42,23 @@ internal class SetupSwishPayinUseCaseImpl( when (output.status) { PaymentMethodSetupStatus.ACTIVE -> { logcat { - "Mariia: SetupSwishPayinMutation ACTIVE url: $output.url" + "SetupSwishPayinMutation ACTIVE, url present: ${output.url != null}" } networkCacheManager.clearCache() - SetupSwishResponse.Success(output.url) + SetupSwishResponse.Success(output.url, output.orderId) } PaymentMethodSetupStatus.PENDING -> { logcat { - "Mariia: SetupSwishPayinMutation PENDING url: $output.url" + "SetupSwishPayinMutation PENDING, url present: ${output.url != null}" } networkCacheManager.clearCache() - SetupSwishResponse.Pending(output.url) + SetupSwishResponse.Pending(output.url, output.orderId) } PaymentMethodSetupStatus.FAILED, PaymentMethodSetupStatus.UNKNOWN__ -> { logcat { - "SetupSwishPayinMutation failed with: output.error?.message" + "SetupSwishPayinMutation FAILED: ${output.error?.message}" } val userMessage = output.error?.message SetupSwishResponse.Failure(ErrorMessage(userMessage)) @@ -66,7 +66,7 @@ internal class SetupSwishPayinUseCaseImpl( } }, fab = { errors, _ -> - logcat { "SetupSwishPayinMutation dataa with errors: $errors" } + logcat { "SetupSwishPayinMutation data with errors: $errors" } raise(ErrorMessage()) }, ) @@ -76,7 +76,26 @@ internal class SetupSwishPayinUseCaseImpl( internal sealed interface SetupSwishResponse { data class Failure(val error: ErrorMessage) : SetupSwishResponse - data class Success(val url: String?) : SetupSwishResponse + data class Success(val url: String?, val orderId: String?) : SetupSwishResponse - data class Pending(val url: String?) : SetupSwishResponse + data class Pending(val url: String?, val orderId: String?) : SetupSwishResponse +} + +/** + * A setup the member still has to approve in the Swish app: [successUrl] hands them over to it and + * [orderId] is what the approval is polled with. Null until the backend returns both, which it does + * for every setup that actually needs approving. + */ +internal data class SwishSetupOrder(val successUrl: String, val orderId: String) + +internal val SetupSwishResponse.order: SwishSetupOrder? + get() = when (this) { + is SetupSwishResponse.Success -> swishSetupOrder(url, orderId) + is SetupSwishResponse.Pending -> swishSetupOrder(url, orderId) + is SetupSwishResponse.Failure -> null + } + +private fun swishSetupOrder(url: String?, orderId: String?): SwishSetupOrder? { + if (url == null || orderId == null) return null + return SwishSetupOrder(url, orderId) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 365d723005..7448f45997 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -10,6 +10,17 @@ internal data class SelectPayinMethodKey( val currentProviders: List, ) : HedvigNavKey +/** + * Waiting on a Swish setup the member approves in the Swish app. Carries the phone number so a + * failed attempt can be retried with it, without sending them back to re-enter it. + */ +@Serializable +internal data class SwishPayinStatusKey( + val successUrl: String, + val orderId: String, + val phoneNumber: String, +) : HedvigNavKey + @Serializable internal data object SetupInvoicePayinKey : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index be075d4501..2164fccaf9 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -5,6 +5,7 @@ import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.feature.payin.account.data.SwishSetupOrder import com.hedvig.android.feature.payin.account.data.id import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsDestination @@ -23,11 +24,14 @@ import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayi import com.hedvig.android.feature.payin.account.ui.setupinvoice.SetupInvoicePayinViewModel import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinDestination import com.hedvig.android.feature.payin.account.ui.setupswish.SetupSwishPayinViewModel +import com.hedvig.android.feature.payin.account.ui.setupswish.SwishPayinStatusDestination +import com.hedvig.android.feature.payin.account.ui.setupswish.SwishPayinStatusViewModel +import com.hedvig.android.feature.payin.account.ui.setupswish.SwishPayinStatusViewModelFactory import com.hedvig.android.navigation.common.HedvigNavKey import com.hedvig.android.navigation.compose.Backstack import com.hedvig.android.navigation.compose.add -import com.hedvig.android.navigation.compose.navigateAndPopUpTo import com.hedvig.android.navigation.compose.popUpTo +import com.hedvig.android.navigation.compose.removeAllOf import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel import dev.zacsweers.metrox.viewmodel.metroViewModel @@ -112,22 +116,44 @@ fun EntryProviderScope.payinAccountEntries( backstack.popUpTo(inclusive = true) navigateToConnectPayment() }, - onSwishSelected = dropUnlessResumed { - backstack.navigateAndPopUpTo(SetupSwishPayinKey, inclusive = true) - }, + onSwishSelected = dropUnlessResumed { backstack.add(SetupSwishPayinKey) }, onInvoiceSelected = dropUnlessResumed { backstack.add(SetupInvoicePayinKey) }, navigateUp = backstack::navigateUp, ) } + // Leaves the whole Swish flow behind: both of its screens, plus the method picker when the member + // came through one, so a connected member never lands back inside the flow they just finished. + val finishSwishSetup: () -> Unit = { + backstack.popUpTo(inclusive = true) + backstack.removeAllOf() + } + entry { val viewModel: SetupSwishPayinViewModel = metroViewModel() SetupSwishPayinDestination( viewModel = viewModel, globalSnackBarState = globalSnackBarState, - onSuccessfullyConnected = backstack::popBackstack, + onSuccessfullyConnected = finishSwishSetup, + navigateToApproval = dropUnlessResumed { order: SwishSetupOrder, phoneNumber: String -> + backstack.add(SwishPayinStatusKey(order.successUrl, order.orderId, phoneNumber)) + }, + navigateUp = backstack::navigateUp, + ) + } + + entry { key -> + val viewModel: SwishPayinStatusViewModel = + assistedMetroViewModel { + create(key.successUrl, key.orderId, key.phoneNumber) + } + SwishPayinStatusDestination( + viewModel = viewModel, navigateUp = backstack::navigateUp, navigateBack = backstack::popBackstack, + finishSwishSetup = finishSwishSetup, + // Back to the picker, leaving the number behind with the attempt that failed. + changePaymentMethod = { backstack.popUpTo(inclusive = true) }, openUrl = openUrl, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt index 77ac4b3be2..c5c81fc84b 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -45,32 +45,27 @@ import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize import com.hedvig.android.design.system.hedvig.PaymentMethodTile -import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.a11y.FlowHeading -import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.colored.Swish import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.design.system.hedvig.show +import com.hedvig.android.feature.payin.account.data.SwishSetupOrder import com.hedvig.android.feature.payin.account.ui.components.formatSwishPhoneNumber import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.GENERAL_CONFIRM import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL -import hedvig.resources.PAYMENT_OPEN_SWISH_BUTTON -import hedvig.resources.PAYMENT_SWISH_APPROVE_TITLE import hedvig.resources.PAYMENT_SWISH_EXPLANATION_BUTTON import hedvig.resources.PAYMENT_SWISH_SUBTITLE import hedvig.resources.PAYMENT_SWISH_TITLE import hedvig.resources.Res -import hedvig.resources.general_cancel_button import hedvig.resources.something_went_wrong import org.jetbrains.compose.resources.stringResource @@ -79,11 +74,14 @@ internal fun SetupSwishPayinDestination( viewModel: SetupSwishPayinViewModel, globalSnackBarState: GlobalSnackBarState, onSuccessfullyConnected: () -> Unit, + navigateToApproval: (order: SwishSetupOrder, phoneNumber: String) -> Unit, navigateUp: () -> Unit, - navigateBack: () -> Unit, - openUrl: (String) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val orderToApprove = uiState.orderToApprove + LaunchedEffect(orderToApprove) { + if (orderToApprove != null) navigateToApproval(orderToApprove, uiState.phoneNumber) + } SetupSwishPayinScreen( uiState = uiState, globalSnackBarState = globalSnackBarState, @@ -93,16 +91,12 @@ internal fun SetupSwishPayinDestination( onSuccessfullyConnected() }, navigateUp = navigateUp, - navigateBack = navigateBack, - openUrl = openUrl, updateText = { viewModel.emit(SetupSwishPayoutEvent.UpdateText(it)) }, ) } -// todo fetch payment methods continuously to see if it already not in pending state - @Composable private fun SetupSwishPayinScreen( uiState: SetupSwishPayoutUiState, @@ -110,8 +104,6 @@ private fun SetupSwishPayinScreen( onSave: () -> Unit, showedSnackBar: () -> Unit, navigateUp: () -> Unit, - navigateBack: () -> Unit, - openUrl: (String) -> Unit, updateText: (String) -> Unit, ) { val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) @@ -129,7 +121,6 @@ private fun SetupSwishPayinScreen( }, ) - val successUrl = uiState.successUrl HedvigScaffold( topAppBarText = null, navigateUp = navigateUp, @@ -137,33 +128,14 @@ private fun SetupSwishPayinScreen( ) { Spacer(Modifier.height(8.dp)) FlowHeading( - title = if (successUrl == null) { - stringResource(Res.string.PAYMENT_SWISH_TITLE) - } else { - stringResource(Res.string.PAYMENT_SWISH_APPROVE_TITLE) - }, - description = if (successUrl == null) { - stringResource(Res.string.PAYMENT_SWISH_SUBTITLE) - } else { - null - }, + title = stringResource(Res.string.PAYMENT_SWISH_TITLE), + description = stringResource(Res.string.PAYMENT_SWISH_SUBTITLE), baseStyle = HedvigTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp), ) Spacer(Modifier.weight(1f)) PaymentMethodTile( modifier = Modifier.align(Alignment.CenterHorizontally), - badge = if (successUrl == null) { - null - } else { - { - PaymentMethodTileBadge( - icon = HedvigIcons.ArrowNorthEast, - containerColor = HedvigTheme.colorScheme.signalBlueElement, - contentColor = HedvigTheme.colorScheme.fillWhite, - ) - } - }, mark = { Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(PaymentMethodMarkSize)) }, @@ -179,31 +151,12 @@ private fun SetupSwishPayinScreen( ) Spacer(Modifier.height(8.dp)) } - if (successUrl == null) { - EnterPhoneNumberSection( - uiState = uiState, - onSave = onSave, - onLearnMoreAboutRecurringSwish = { explanationSheetState.show() }, - updateText = updateText, - ) - } else { - HedvigButton( - text = stringResource(Res.string.PAYMENT_OPEN_SWISH_BUTTON), - onClick = { openUrl(successUrl) }, - enabled = true, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - Spacer(Modifier.height(8.dp)) - HedvigTextButton( - text = stringResource(Res.string.general_cancel_button), - onClick = navigateBack, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - } + EnterPhoneNumberSection( + uiState = uiState, + onSave = onSave, + onLearnMoreAboutRecurringSwish = { explanationSheetState.show() }, + updateText = updateText, + ) Spacer(Modifier.height(16.dp)) } } @@ -320,8 +273,6 @@ private fun PreviewSetupSwishPayinScreen( onSave = {}, showedSnackBar = {}, navigateUp = {}, - navigateBack = {}, - openUrl = {}, updateText = {}, ) } @@ -348,12 +299,5 @@ private class SetupSwishPayinUiStateProvider : CollectionPreviewParameterProvide error = null, showSuccessSnackBar = false, ), - SetupSwishPayoutUiState( - phoneNumber = "83728644428", - isLoading = false, - error = null, - showSuccessSnackBar = false, - successUrl = "hwdjhew", - ), ), ) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt index 6b0100f4b5..21e4910b9e 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -1,6 +1,5 @@ package com.hedvig.android.feature.payin.account.ui.setupswish -import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -12,6 +11,8 @@ import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase import com.hedvig.android.feature.payin.account.data.SetupSwishResponse +import com.hedvig.android.feature.payin.account.data.SwishSetupOrder +import com.hedvig.android.feature.payin.account.data.order import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel @@ -27,7 +28,7 @@ internal class SetupSwishPayinViewModel( isLoading = false, error = null, showSuccessSnackBar = false, - successUrl = null, + orderToApprove = null, ), SetupSwishPayoutPresenter(setupSwishPayoutUseCase), ) @@ -42,11 +43,11 @@ internal sealed interface SetupSwishPayoutEvent { internal data class SetupSwishPayoutUiState( val showSuccessSnackBar: Boolean, - val successUrl: String? = null, + /** Set once the setup needs approving in the Swish app, which the caller moves on to. */ + val orderToApprove: SwishSetupOrder? = null, val phoneNumber: String, val isLoading: Boolean, val error: ErrorMessage?, - val resultIsPending: Boolean = false, ) internal class SetupSwishPayoutPresenter( @@ -60,17 +61,14 @@ internal class SetupSwishPayoutPresenter( var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } var showSuccessSnackBar by remember { mutableStateOf(false) } - var resultIsPending by remember { mutableStateOf(false) } var saveIteration by remember { mutableStateOf(null) } - var urlToRedirect by remember { mutableStateOf(null) } - var uiState by remember { mutableStateOf(lastState) } + var orderToApprove by remember { mutableStateOf(null) } val currentSave = saveIteration if (currentSave != null) { LaunchedEffect(currentSave) { isLoading = true errorMessage = null - resultIsPending = false setupSwishPayoutUseCase.invoke(phoneNumberState).fold( ifLeft = { isLoading = false @@ -80,20 +78,14 @@ internal class SetupSwishPayoutPresenter( ifRight = { result -> isLoading = false saveIteration = null - when (result) { - is SetupSwishResponse.Failure -> { - errorMessage = result.error - } + val order = result.order + when { + order != null -> orderToApprove = order - is SetupSwishResponse.Pending -> { - urlToRedirect = result.url - resultIsPending = true - } + // A setup that needs no approving is already done, so there is nothing to wait on. + result is SetupSwishResponse.Success -> showSuccessSnackBar = true - is SetupSwishResponse.Success -> { - showSuccessSnackBar = true - urlToRedirect = result.url - } + else -> errorMessage = (result as? SetupSwishResponse.Failure)?.error ?: ErrorMessage() } }, ) @@ -123,7 +115,7 @@ internal class SetupSwishPayoutPresenter( isLoading = isLoading, error = errorMessage, showSuccessSnackBar = showSuccessSnackBar, - successUrl = urlToRedirect, + orderToApprove = orderToApprove, ) } } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt new file mode 100644 index 0000000000..6d51d396aa --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt @@ -0,0 +1,225 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.PaymentMethodHandoverIllustration +import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize +import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.a11y.FlowHeading +import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast +import com.hedvig.android.design.system.hedvig.icon.Checkmark +import com.hedvig.android.design.system.hedvig.icon.Close +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import hedvig.resources.GENERAL_RETRY +import hedvig.resources.PAYMENT_CHANGE_METHOD_BUTTON +import hedvig.resources.PAYMENT_OPEN_SWISH_BUTTON +import hedvig.resources.PAYMENT_SWISH_APPROVE_TITLE +import hedvig.resources.PAYMENT_SWISH_FAILURE_TITLE +import hedvig.resources.PAYMENT_SWISH_SUCCESS_SUBTITLE +import hedvig.resources.PAYMENT_SWISH_SUCCESS_TITLE +import hedvig.resources.Res +import hedvig.resources.general_cancel_button +import hedvig.resources.general_continue_button +import hedvig.resources.something_went_wrong +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun SwishPayinStatusDestination( + viewModel: SwishPayinStatusViewModel, + navigateUp: () -> Unit, + navigateBack: () -> Unit, + finishSwishSetup: () -> Unit, + changePaymentMethod: () -> Unit, + openUrl: (String) -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + SwishPayinStatusScreen( + uiState = uiState, + navigateUp = navigateUp, + onCancel = navigateBack, + onContinue = finishSwishSetup, + onRetry = { viewModel.emit(SwishPayinStatusEvent.Retry) }, + onChangePaymentMethod = changePaymentMethod, + openUrl = openUrl, + ) +} + +@Composable +private fun SwishPayinStatusScreen( + uiState: SwishPayinStatusUiState, + navigateUp: () -> Unit, + onCancel: () -> Unit, + onContinue: () -> Unit, + onRetry: () -> Unit, + onChangePaymentMethod: () -> Unit, + openUrl: (String) -> Unit, +) { + HedvigScaffold( + topAppBarText = null, + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + Spacer(Modifier.height(8.dp)) + FlowHeading( + title = when (uiState) { + is SwishPayinStatusUiState.PendingApproval -> stringResource(Res.string.PAYMENT_SWISH_APPROVE_TITLE) + SwishPayinStatusUiState.Connected -> stringResource(Res.string.PAYMENT_SWISH_SUCCESS_TITLE) + is SwishPayinStatusUiState.Failed -> stringResource(Res.string.PAYMENT_SWISH_FAILURE_TITLE) + }, + description = when (uiState) { + is SwishPayinStatusUiState.PendingApproval -> null + SwishPayinStatusUiState.Connected -> stringResource(Res.string.PAYMENT_SWISH_SUCCESS_SUBTITLE) + is SwishPayinStatusUiState.Failed -> uiState.message ?: stringResource(Res.string.something_went_wrong) + }, + baseStyle = HedvigTheme.typography.bodySmall, + modifier = Modifier.padding(horizontal = 16.dp), + ) + Spacer(Modifier.weight(1f)) + PaymentMethodHandoverIllustration( + modifier = Modifier.align(Alignment.CenterHorizontally), + destinationBadge = { SetupStatusBadge(uiState) }, + mark = { Image(HedvigIcons.Swish, EmptyContentDescription, Modifier.size(PaymentMethodMarkSize)) }, + ) + Spacer(Modifier.weight(1f)) + when (uiState) { + is SwishPayinStatusUiState.PendingApproval -> { + HedvigButton( + text = stringResource(Res.string.PAYMENT_OPEN_SWISH_BUTTON), + onClick = { openUrl(uiState.redirectUrl) }, + enabled = true, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_cancel_button), + onClick = onCancel, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + SwishPayinStatusUiState.Connected -> { + HedvigText( + // TODO: Add "You can change payment method later" / "Du kan byta betalningsmetod senare" + // to Lokalise + text = "You can change payment method later", + style = HedvigTheme.typography.label, + color = HedvigTheme.colorScheme.textSecondaryTranslucent, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_continue_button), + onClick = onContinue, + enabled = true, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + is SwishPayinStatusUiState.Failed -> { + HedvigButton( + text = stringResource(Res.string.GENERAL_RETRY), + onClick = onRetry, + enabled = !uiState.isRetrying, + isLoading = uiState.isRetrying, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(Res.string.PAYMENT_CHANGE_METHOD_BUTTON), + onClick = onChangePaymentMethod, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + } + Spacer(Modifier.height(16.dp)) + } +} + +/** Marks the Hedvig symbol with where the setup has got to: handed over, landed, or rejected. */ +@Composable +private fun SetupStatusBadge(uiState: SwishPayinStatusUiState) { + when (uiState) { + is SwishPayinStatusUiState.PendingApproval -> PaymentMethodTileBadge( + icon = HedvigIcons.ArrowNorthEast, + containerColor = HedvigTheme.colorScheme.signalBlueElement, + contentColor = HedvigTheme.colorScheme.fillWhite, + ) + + SwishPayinStatusUiState.Connected -> PaymentMethodTileBadge( + icon = HedvigIcons.Checkmark, + containerColor = HedvigTheme.colorScheme.signalGreenElement, + contentColor = HedvigTheme.colorScheme.fillWhite, + ) + + is SwishPayinStatusUiState.Failed -> PaymentMethodTileBadge( + icon = HedvigIcons.Close, + containerColor = HedvigTheme.colorScheme.signalRedElement, + contentColor = HedvigTheme.colorScheme.fillWhite, + ) + } +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewSwishPayinStatusScreen( + @PreviewParameter(SwishPayinStatusUiStateProvider::class) uiState: SwishPayinStatusUiState, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SwishPayinStatusScreen( + uiState = uiState, + navigateUp = {}, + onCancel = {}, + onContinue = {}, + onRetry = {}, + onChangePaymentMethod = {}, + openUrl = {}, + ) + } + } +} + +private class SwishPayinStatusUiStateProvider : CollectionPreviewParameterProvider( + listOf( + SwishPayinStatusUiState.PendingApproval("https://swish"), + SwishPayinStatusUiState.Connected, + SwishPayinStatusUiState.Failed(null), + SwishPayinStatusUiState.Failed("The connection was declined in the Swish app", isRetrying = true), + ), +) diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt new file mode 100644 index 0000000000..b24b298e22 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt @@ -0,0 +1,132 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import com.hedvig.android.core.common.di.ActivityRetainedScope +import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.feature.payin.account.data.GetSwishPayinSetupStatusUseCase +import com.hedvig.android.feature.payin.account.data.SetupSwishPayinUseCase +import com.hedvig.android.feature.payin.account.data.SetupSwishResponse +import com.hedvig.android.feature.payin.account.data.SwishPayinSetupStatus +import com.hedvig.android.feature.payin.account.data.SwishSetupOrder +import com.hedvig.android.feature.payin.account.data.order +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive + +private val PollInterval = 2.seconds + +@AssistedInject +@HedvigViewModel(ActivityRetainedScope::class) +internal class SwishPayinStatusViewModel( + @Assisted successUrl: String, + @Assisted orderId: String, + @Assisted phoneNumber: String, + getSwishPayinSetupStatusUseCase: GetSwishPayinSetupStatusUseCase, + setupSwishPayinUseCase: SetupSwishPayinUseCase, +) : MoleculeViewModel( + initialState = SwishPayinStatusUiState.PendingApproval(successUrl), + presenter = SwishPayinStatusPresenter( + initialOrder = SwishSetupOrder(successUrl, orderId), + phoneNumber = phoneNumber, + getSwishPayinSetupStatusUseCase = getSwishPayinSetupStatusUseCase, + setupSwishPayinUseCase = setupSwishPayinUseCase, + ), + ) + +internal sealed interface SwishPayinStatusEvent { + data object Retry : SwishPayinStatusEvent +} + +internal sealed interface SwishPayinStatusUiState { + data class PendingApproval(val redirectUrl: String) : SwishPayinStatusUiState + + data object Connected : SwishPayinStatusUiState + + data class Failed(val message: String?, val isRetrying: Boolean = false) : SwishPayinStatusUiState +} + +internal class SwishPayinStatusPresenter( + private val initialOrder: SwishSetupOrder, + private val phoneNumber: String, + private val getSwishPayinSetupStatusUseCase: GetSwishPayinSetupStatusUseCase, + private val setupSwishPayinUseCase: SetupSwishPayinUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: SwishPayinStatusUiState, + ): SwishPayinStatusUiState { + var order by remember { mutableStateOf(initialOrder) } + var uiState by remember { mutableStateOf(lastState) } + var retryOnFailIteration by remember { mutableIntStateOf(0) } + + // Keyed on the order, so a retry's new order restarts the polling against it. + LaunchedEffect(order) { + uiState = SwishPayinStatusUiState.PendingApproval(order.successUrl) + while (isActive) { + when (val status = getSwishPayinSetupStatusUseCase.invoke(order.orderId).getOrNull()) { + SwishPayinSetupStatus.Active -> { + uiState = SwishPayinStatusUiState.Connected + break + } + + is SwishPayinSetupStatus.Failed -> { + uiState = SwishPayinStatusUiState.Failed(status.message) + break + } + + // Still pending, or a transient failure to reach the backend; either way keep waiting, + // since the member can leave the screen themselves. + else -> { + delay(PollInterval) + } + } + } + } + + LaunchedEffect(retryOnFailIteration) { + if (retryOnFailIteration == 0) return@LaunchedEffect + uiState = SwishPayinStatusUiState.Failed(currentFailureMessage(uiState), isRetrying = true) + setupSwishPayinUseCase.invoke(phoneNumber).fold( + ifLeft = { error -> + uiState = SwishPayinStatusUiState.Failed(error.message) + }, + ifRight = { response -> + val newOrder = response.order + if (newOrder != null) { + order = newOrder + } else { + val message = (response as? SetupSwishResponse.Failure)?.error?.message + uiState = SwishPayinStatusUiState.Failed(message) + } + }, + ) + } + + CollectEvents { event -> + when (event) { + SwishPayinStatusEvent.Retry -> { + val state = uiState + if (state is SwishPayinStatusUiState.Failed && !state.isRetrying) { + retryOnFailIteration++ + } + } + } + } + + return uiState + } +} + +private fun currentFailureMessage(state: SwishPayinStatusUiState): String? = + (state as? SwishPayinStatusUiState.Failed)?.message From 832cc6cbebecfbf2190932080eed9cb5cd0abacc Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 10:54:17 +0200 Subject: [PATCH 66/75] auto-fire swish app --- .../app/navigation/HedvigEntryProvider.kt | 3 + .../src/main/AndroidManifest.xml | 14 ++- .../account/navigation/PayinAccountEntries.kt | 4 + .../account/ui/setupswish/SwishAppHandover.kt | 90 +++++++++++++++++++ .../setupswish/SwishPayinStatusDestination.kt | 16 +++- .../setupswish/SwishPayinStatusViewModel.kt | 16 +++- 6 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishAppHandover.kt diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index a5d05ff248..c8cfdfc513 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -162,6 +162,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( addPaymentsEntries( backstack = backstack, globalSnackBarState = globalSnackBarState, + hedvigBuildConstants = hedvigBuildConstants, openUrl = openUrl, navigateToConnectPayment = navigateToConnectPayment, navigateToPayinAccount = navigateToPayinAccount, @@ -464,6 +465,7 @@ private fun EntryProviderScope.addInsuranceEntries( private fun EntryProviderScope.addPaymentsEntries( backstack: BackstackController, globalSnackBarState: GlobalSnackBarState, + hedvigBuildConstants: HedvigBuildConstants, openUrl: (String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToPayinAccount: () -> Unit, @@ -486,6 +488,7 @@ private fun EntryProviderScope.addPaymentsEntries( payinAccountEntries( backstack = backstack, globalSnackBarState = globalSnackBarState, + hedvigBuildConstants = hedvigBuildConstants, navigateToConnectPayment = navigateToConnectPayment, openUrl = openUrl, ) diff --git a/app/feature/feature-payin-account/src/main/AndroidManifest.xml b/app/feature/feature-payin-account/src/main/AndroidManifest.xml index 568741e54f..4a14d916af 100644 --- a/app/feature/feature-payin-account/src/main/AndroidManifest.xml +++ b/app/feature/feature-payin-account/src/main/AndroidManifest.xml @@ -1,2 +1,14 @@ - \ No newline at end of file + + + + + + + + + diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 2164fccaf9..a0a5342eec 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -3,6 +3,7 @@ package com.hedvig.android.feature.payin.account.navigation import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed +import com.hedvig.android.core.buildconstants.HedvigBuildConstants import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.SwishSetupOrder @@ -38,6 +39,7 @@ import dev.zacsweers.metrox.viewmodel.metroViewModel fun EntryProviderScope.payinAccountEntries( backstack: Backstack, globalSnackBarState: GlobalSnackBarState, + hedvigBuildConstants: HedvigBuildConstants, navigateToConnectPayment: () -> Unit, openUrl: (String) -> Unit, ) { @@ -149,6 +151,8 @@ fun EntryProviderScope.payinAccountEntries( } SwishPayinStatusDestination( viewModel = viewModel, + // Staging orders can only be approved in the Swish sandbox app, never the real one. + allowSandboxSwishApp = !hedvigBuildConstants.isProduction, navigateUp = backstack::navigateUp, navigateBack = backstack::popBackstack, finishSwishSetup = finishSwishSetup, diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishAppHandover.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishAppHandover.kt new file mode 100644 index 0000000000..e192746acd --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishAppHandover.kt @@ -0,0 +1,90 @@ +package com.hedvig.android.feature.payin.account.ui.setupswish + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.core.net.toUri +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat + +/** + * Hands the member over to the Swish app to approve a payin setup, following + * https://developer.swish.nu/documentation/guides/trigger-the-swish-app. + * + * [isSwishInstalled] is what the approval screen consults before handing over on its own. [open] is + * safe to call either way: with no Swish app on the device it falls back to opening the url. + */ +@Stable +internal interface SwishAppHandover { + val isSwishInstalled: Boolean + + fun open(approvalUrl: String) +} + +/** + * @param allowSandboxApp whether the Swish sandbox app counts as installed. Only it can approve the + * orders the staging backend issues, so builds pointing there accept it in place of the real app. + */ +@Composable +internal fun rememberSwishAppHandover(allowSandboxApp: Boolean, openUrl: (String) -> Unit): SwishAppHandover { + val context = LocalContext.current + return remember(context, allowSandboxApp, openUrl) { + SwishAppHandoverImpl(context, allowSandboxApp, openUrl) + } +} + +@Stable +private class SwishAppHandoverImpl( + private val context: Context, + allowSandboxApp: Boolean, + private val openUrl: (String) -> Unit, +) : SwishAppHandover { + private val installedPackage: String? = listOfNotNull( + SwishPackageName, + SwishSandboxPackageName.takeIf { allowSandboxApp }, + ).firstOrNull { context.isPackageInstalled(it) }.also { + logcat { "Resolved Swish app for payin approval: ${it ?: "none installed"}" } + } + + override val isSwishInstalled: Boolean + get() = installedPackage != null + + override fun open(approvalUrl: String) { + val packageName = installedPackage + if (packageName == null) { + openUrl(approvalUrl) + return + } + // Addressing the intent at Swish keeps the approval out of a browser or an app chooser. + val intent = Intent(Intent.ACTION_VIEW, approvalUrl.toUri()).setPackage(packageName) + try { + context.startActivity(intent) + } catch (e: ActivityNotFoundException) { + logcat(LogPriority.WARN, e) { "$packageName would not take the Swish approval url" } + openUrl(approvalUrl) + } + } +} + +private fun Context.isPackageInstalled(packageName: String): Boolean { + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)) + } else { + @Suppress("DEPRECATION") + packageManager.getPackageInfo(packageName, 0) + } + true + } catch (e: PackageManager.NameNotFoundException) { + false + } +} + +private const val SwishPackageName = "se.bankgirot.swish" +private const val SwishSandboxPackageName = "se.bankgirot.swish.sandbox" diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt index 6d51d396aa..11f07bb198 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusDestination.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -49,6 +50,7 @@ import org.jetbrains.compose.resources.stringResource @Composable internal fun SwishPayinStatusDestination( viewModel: SwishPayinStatusViewModel, + allowSandboxSwishApp: Boolean, navigateUp: () -> Unit, navigateBack: () -> Unit, finishSwishSetup: () -> Unit, @@ -56,6 +58,18 @@ internal fun SwishPayinStatusDestination( openUrl: (String) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val swishAppHandover = rememberSwishAppHandover(allowSandboxSwishApp, openUrl) + + val pendingApproval = uiState as? SwishPayinStatusUiState.PendingApproval + val urlToAutoOpen = pendingApproval?.redirectUrl?.takeIf { pendingApproval.allowAutoOpen } + LaunchedEffect(urlToAutoOpen, swishAppHandover) { + if (urlToAutoOpen == null) return@LaunchedEffect + // Without the app there is nothing to hand over to, so the member gets the button instead. + if (!swishAppHandover.isSwishInstalled) return@LaunchedEffect + viewModel.emit(SwishPayinStatusEvent.DidOpenSwishApp) + swishAppHandover.open(urlToAutoOpen) + } + SwishPayinStatusScreen( uiState = uiState, navigateUp = navigateUp, @@ -63,7 +77,7 @@ internal fun SwishPayinStatusDestination( onContinue = finishSwishSetup, onRetry = { viewModel.emit(SwishPayinStatusEvent.Retry) }, onChangePaymentMethod = changePaymentMethod, - openUrl = openUrl, + openUrl = swishAppHandover::open, ) } diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt index b24b298e22..630e34eba8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SwishPayinStatusViewModel.kt @@ -46,10 +46,17 @@ internal class SwishPayinStatusViewModel( internal sealed interface SwishPayinStatusEvent { data object Retry : SwishPayinStatusEvent + + data object DidOpenSwishApp : SwishPayinStatusEvent } internal sealed interface SwishPayinStatusUiState { - data class PendingApproval(val redirectUrl: String) : SwishPayinStatusUiState + /** + * @param allowAutoOpen whether the screen should still hand the member over to the Swish app + * without being asked. Each new order starts out allowing it; it is spent on the first handover + * so that coming back from Swish, or a rotation, does not bounce the member out again. + */ + data class PendingApproval(val redirectUrl: String, val allowAutoOpen: Boolean = true) : SwishPayinStatusUiState data object Connected : SwishPayinStatusUiState @@ -115,6 +122,13 @@ internal class SwishPayinStatusPresenter( CollectEvents { event -> when (event) { + SwishPayinStatusEvent.DidOpenSwishApp -> { + val state = uiState + if (state is SwishPayinStatusUiState.PendingApproval) { + uiState = state.copy(allowAutoOpen = false) + } + } + SwishPayinStatusEvent.Retry -> { val state = uiState if (state is SwishPayinStatusUiState.Failed && !state.isRetrying) { From ead46db919c3885eb3274a61ee11cab1c54f3a83 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 11:17:27 +0200 Subject: [PATCH 67/75] remove mock, set new version --- .../src/main/graphql/GetPayinMethods.graphql | 2 +- .../payin/account/data/GetPayinAccountUseCase.kt | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql index ec071b2a44..7a59f39c24 100644 --- a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql +++ b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql @@ -1,6 +1,6 @@ query GetPayinMethods { currentMember { - paymentMethods { + paymentMethods(version: 2) { chargingDay payinMethods { provider diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 98fddc4ca8..c2613e8bd9 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -82,14 +82,9 @@ internal class GetPayinAccountUseCase( } } } - logcat { "availablePayinMethods: backend returned ${paymentMethods.availableMethods}" } - // TODO: the backend does not list Swish or Trustly as available payin methods yet, so the connect flow is - // driven off a mock. Restore the commented-out filter below and delete `availablePayinMethods` once it does. - // val availablePayinMethods = paymentMethods.availableMethods - // .filter { it.supportsPayin } - // .map { it.provider } - val availablePayinMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY) - logcat { "availablePayinMethods: using mocked $availablePayinMethods" } + val availablePayinMethods = paymentMethods.availableMethods + .filter { it.supportsPayin } + .map { it.provider } PayinAccountData( currentMethods = currentMethods, availablePayinMethods = availablePayinMethods, From 05c904b503261df595886de114e30a09f277c322 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 12:01:22 +0200 Subject: [PATCH 68/75] move PayinAccount to data module and use in manual charging --- .../FragmentMemberPaymentMethod.graphql | 0 app/data/data-paying-member/build.gradle.kts | 2 + .../data/paying/member/PayinAccount.kt | 108 ++++++++++++++++ .../feature-payin-account/build.gradle.kts | 1 + .../src/main/graphql/GetPayinMethods.graphql | 17 +-- .../account/data/GetPayinAccountUseCase.kt | 116 +----------------- .../navigation/PayinAccountDestination.kt | 2 +- .../account/navigation/PayinAccountEntries.kt | 2 +- .../account/ui/components/PayinMethodRow.kt | 13 +- .../PayinMethodDetailsDestination.kt | 6 +- .../PayinMethodDetailsViewModel.kt | 2 +- .../RemovePayinMethodBottomSheet.kt | 2 +- .../PayinAccountOverviewDestination.kt | 7 +- .../overview/PayinAccountOverviewViewModel.kt | 2 +- .../SelectPrimaryPayinMethodDestination.kt | 10 +- .../SelectPrimaryPayinMethodViewModel.kt | 2 +- ...tPrimaryPayinMethodKeySerializationTest.kt | 4 +- .../graphql/QueryManualChargeInfo.graphql | 9 +- .../data/GetManualChargeInfoUseCase.kt | 11 +- .../manualcharge/ManualChargeDestination.kt | 87 ++++++++++--- 20 files changed, 216 insertions(+), 187 deletions(-) rename app/{feature/feature-payments/src/main => apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus}/graphql/FragmentMemberPaymentMethod.graphql (100%) create mode 100644 app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt diff --git a/app/feature/feature-payments/src/main/graphql/FragmentMemberPaymentMethod.graphql b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/graphql/FragmentMemberPaymentMethod.graphql similarity index 100% rename from app/feature/feature-payments/src/main/graphql/FragmentMemberPaymentMethod.graphql rename to app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/graphql/FragmentMemberPaymentMethod.graphql diff --git a/app/data/data-paying-member/build.gradle.kts b/app/data/data-paying-member/build.gradle.kts index 5d9bd6ff91..8f6acdbb70 100644 --- a/app/data/data-paying-member/build.gradle.kts +++ b/app/data/data-paying-member/build.gradle.kts @@ -5,10 +5,12 @@ plugins { hedvig { apollo("octopus") + serialization() } dependencies { implementation(libs.arrow.core) + implementation(libs.kotlinx.serialization.core) implementation(projects.apolloCore) implementation(projects.apolloOctopusPublic) implementation(projects.coreCommonPublic) diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt new file mode 100644 index 0000000000..8619003125 --- /dev/null +++ b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt @@ -0,0 +1,108 @@ +package com.hedvig.android.data.paying.member + +import kotlinx.serialization.Serializable +import octopus.fragment.MemberPaymentMethodFragment +import octopus.fragment.MemberPaymentMethodFragment.Details.Companion.asPaymentMethodBankAccountDetails +import octopus.fragment.MemberPaymentMethodFragment.Details.Companion.asPaymentMethodInvoiceDetails +import octopus.fragment.MemberPaymentMethodFragment.Details.Companion.asPaymentMethodSwishDetails +import octopus.type.MemberPaymentMethodStatus +import octopus.type.MemberPaymentProvider +import octopus.type.PaymentMethodInvoiceDelivery + +/** Serializable so that a chosen set of methods can be carried in a nav key across process death. */ +@Serializable +sealed interface PayinAccount { + val isPending: Boolean + val isDefault: Boolean + + @Serializable + data class Trustly( + val clearingNumber: String?, + val accountNumber: String?, + val bankName: String?, + override val isPending: Boolean, + override val isDefault: Boolean, + ) : PayinAccount + + @Serializable + data class SwishPayin( + val phoneNumber: String?, + override val isPending: Boolean, + override val isDefault: Boolean, + ) : PayinAccount + + @Serializable + data class Invoice( + val delivery: InvoiceDelivery?, + val email: String?, + override val isPending: Boolean, + override val isDefault: Boolean, + ) : PayinAccount +} + +@Serializable +enum class InvoiceDelivery { + Kivra, + Mail, +} + +/** Null for a provider the app has no screen for, which callers drop from the methods they show. */ +fun MemberPaymentMethodFragment.toPayinAccount(): PayinAccount? { + val isPending = status == MemberPaymentMethodStatus.PENDING + // The backend also marks a pending method default once the member picks it during setup, but + // nothing is charged to it until it activates, so it is not the default the app shows. + val isDefaultAndActive = !isPending && isDefault + return when (provider) { + MemberPaymentProvider.SWISH -> { + PayinAccount.SwishPayin( + phoneNumber = details?.asPaymentMethodSwishDetails()?.phoneNumber, + isPending = isPending, + isDefault = isDefaultAndActive, + ) + } + + MemberPaymentProvider.TRUSTLY -> { + val bankAccountDetails = details?.asPaymentMethodBankAccountDetails() + val account = bankAccountDetails?.account + val dashIndex = account?.indexOf('-') ?: -1 + PayinAccount.Trustly( + clearingNumber = if (dashIndex >= 0) account?.substring(0, dashIndex) else account, + accountNumber = if (dashIndex >= 0) account?.substring(dashIndex + 1) else null, + bankName = bankAccountDetails?.bank, + isPending = isPending, + isDefault = isDefaultAndActive, + ) + } + + MemberPaymentProvider.INVOICE -> { + val invoiceDetails = details?.asPaymentMethodInvoiceDetails() + PayinAccount.Invoice( + delivery = invoiceDetails?.delivery.toInvoiceDelivery(), + email = invoiceDetails?.email, + isPending = isPending, + isDefault = isDefaultAndActive, + ) + } + + else -> { + null + } + } +} + +/** The last four digits of the account, the only part of it we ever put on screen. */ +fun PayinAccount.Trustly.maskedAccountNumber(): String? { + val clearing = clearingNumber?.takeIf { it.isNotBlank() }.orEmpty() + val account = accountNumber?.takeIf { it.isNotBlank() }.orEmpty() + val whole = clearing + account + if (whole.length <= 8) return null + return "**** ${whole.takeLast(4)}" +} + +private fun PaymentMethodInvoiceDelivery?.toInvoiceDelivery(): InvoiceDelivery? { + return when (this) { + PaymentMethodInvoiceDelivery.KIVRA -> InvoiceDelivery.Kivra + PaymentMethodInvoiceDelivery.MAIL -> InvoiceDelivery.Mail + else -> null + } +} diff --git a/app/feature/feature-payin-account/build.gradle.kts b/app/feature/feature-payin-account/build.gradle.kts index ebac7f62fa..65b787ecb9 100644 --- a/app/feature/feature-payin-account/build.gradle.kts +++ b/app/feature/feature-payin-account/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(projects.composeUi) implementation(projects.coreCommonPublic) implementation(projects.coreResources) + implementation(projects.dataPayingMember) implementation(projects.designSystemHedvig) implementation(projects.featurePayinAccountNavigation) implementation(projects.moleculePublic) diff --git a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql index 7a59f39c24..96166e967e 100644 --- a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql +++ b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql @@ -3,22 +3,7 @@ query GetPayinMethods { paymentMethods(version: 2) { chargingDay payinMethods { - provider - status - isDefault - details { - ... on PaymentMethodBankAccountDetails { - account - bank - } - ... on PaymentMethodSwishDetails { - phoneNumber - } - ... on PaymentMethodInvoiceDetails { - delivery - email - } - } + ...MemberPaymentMethodFragment } availableMethods { provider diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index c2613e8bd9..907105bd74 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -9,18 +9,14 @@ import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.data.paying.member.InvoiceDelivery +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.toPayinAccount import com.hedvig.android.feature.payin.account.navigation.PayinMethodId -import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn -import kotlinx.serialization.Serializable import octopus.GetPayinMethodsQuery -import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodBankAccountDetails -import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodInvoiceDetails -import octopus.GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod.Details.Companion.asPaymentMethodSwishDetails -import octopus.type.MemberPaymentMethodStatus import octopus.type.MemberPaymentProvider -import octopus.type.PaymentMethodInvoiceDelivery internal data class PayinAccountData( val currentMethods: List, @@ -43,45 +39,7 @@ internal class GetPayinAccountUseCase( val paymentMethods = result.currentMember.paymentMethods - val currentMethods: List = paymentMethods.payinMethods.mapNotNull { method -> - val isPending = method.status == MemberPaymentMethodStatus.PENDING - val isDefault = !isPending && method.isDefault - when (method.provider) { - MemberPaymentProvider.SWISH -> { - val phoneNumber = method.details?.asPaymentMethodSwishDetails()?.phoneNumber - PayinAccount.SwishPayin( - phoneNumber = phoneNumber, - isPending = isPending, - isDefault = isDefault, - ) - } - - MemberPaymentProvider.TRUSTLY -> { - val (clearingNumber, accountNumber, bankName) = parseBankAccountDetails(method) - PayinAccount.Trustly( - clearingNumber = clearingNumber, - accountNumber = accountNumber, - bankName = bankName, - isPending = isPending, - isDefault = isDefault, - ) - } - - MemberPaymentProvider.INVOICE -> { - val invoiceDetails = method.details?.asPaymentMethodInvoiceDetails() - PayinAccount.Invoice( - delivery = invoiceDetails?.delivery.toInvoiceDelivery(), - email = invoiceDetails?.email, - isPending = isPending, - isDefault = isDefault, - ) - } - - else -> { - null - } - } - } + val currentMethods = paymentMethods.payinMethods.mapNotNull { it.toPayinAccount() } val availablePayinMethods = paymentMethods.availableMethods .filter { it.supportsPayin } .map { it.provider } @@ -93,58 +51,6 @@ internal class GetPayinAccountUseCase( } } -private data class ParsedBankAccountDetails( - val clearingNumber: String?, - val accountNumber: String?, - val bankName: String?, -) - -private fun parseBankAccountDetails( - method: GetPayinMethodsQuery.Data.CurrentMember.PaymentMethods.PayinMethod, -): ParsedBankAccountDetails { - val bankAccountDetails = method.details?.asPaymentMethodBankAccountDetails() - val account = bankAccountDetails?.account - val dashIndex = account?.indexOf('-') ?: -1 - val clearingNumber = if (dashIndex >= 0) account?.substring(0, dashIndex) else account - val accountNumber = if (dashIndex >= 0) account?.substring(dashIndex + 1) else null - return ParsedBankAccountDetails( - clearingNumber = clearingNumber, - accountNumber = accountNumber, - bankName = bankAccountDetails?.bank, - ) -} - -/** Serializable so that a chosen set of methods can be carried in a nav key across process death. */ -@Serializable -internal sealed interface PayinAccount { - val isPending: Boolean - val isDefault: Boolean - - @Serializable - data class Trustly( - val clearingNumber: String?, - val accountNumber: String?, - val bankName: String?, - override val isPending: Boolean, - override val isDefault: Boolean, - ) : PayinAccount - - @Serializable - data class SwishPayin( - val phoneNumber: String?, - override val isPending: Boolean, - override val isDefault: Boolean, - ) : PayinAccount - - @Serializable - data class Invoice( - val delivery: InvoiceDelivery?, - val email: String?, - override val isPending: Boolean, - override val isDefault: Boolean, - ) : PayinAccount -} - internal val PayinAccount.id: PayinMethodId get() = when (this) { is PayinAccount.Trustly -> PayinMethodId.Trustly @@ -159,20 +65,6 @@ internal val PayinAccount.provider: MemberPaymentProvider is PayinAccount.Invoice -> MemberPaymentProvider.INVOICE } -@Serializable -internal enum class InvoiceDelivery { - Kivra, - Mail, -} - -private fun PaymentMethodInvoiceDelivery?.toInvoiceDelivery(): InvoiceDelivery? { - return when (this) { - PaymentMethodInvoiceDelivery.KIVRA -> InvoiceDelivery.Kivra - PaymentMethodInvoiceDelivery.MAIL -> InvoiceDelivery.Mail - else -> null - } -} - internal fun InvoiceDelivery?.toDeliveryString(): String? { return when (this) { // TODO: Add "Kivra" / "Kivra" to Lokalise diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 7448f45997..710b49906d 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -1,6 +1,6 @@ package com.hedvig.android.feature.payin.account.navigation -import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.navigation.common.HedvigNavKey import kotlinx.serialization.Serializable diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index a0a5342eec..44b2e7d4e7 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.core.buildconstants.HedvigBuildConstants +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.design.system.hedvig.GlobalSnackBarState -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.SwishSetupOrder import com.hedvig.android.feature.payin.account.data.id import com.hedvig.android.feature.payin.account.data.provider diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index ca71a97752..a7d4c86557 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -18,6 +18,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.data.paying.member.InvoiceDelivery +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText @@ -34,8 +36,6 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish -import com.hedvig.android.feature.payin.account.data.InvoiceDelivery -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.data.toDeliveryString import hedvig.resources.PAYMENTS_BANK_LABEL @@ -175,15 +175,6 @@ private fun PayinAccount.Trustly.maskedAccount(): String? { return "$bank ···· $lastFour" } -internal fun PayinAccount.Trustly.maskedAccountNumber(): String? { - val whole = "${clearingNumber?.takeIf{ it.isNotBlank() } ?: ""}${accountNumber?.takeIf{ it.isNotBlank() } ?: ""}" - if (whole.length > 8) { - val lastFour = whole.takeLast(4).takeIf { it.isNotBlank() } ?: return null - return "**** $lastFour" - } else return null - -} - internal fun formatSwishPhoneNumber(phoneNumber: String): String { val digits = phoneNumber.take(15) val sb = StringBuilder() diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt index ebc89deca0..de445c930f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsDestination.kt @@ -19,6 +19,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.data.paying.member.InvoiceDelivery +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.maskedAccountNumber import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton @@ -41,12 +44,9 @@ import com.hedvig.android.design.system.hedvig.datepicker.getLocale import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.InfoFilled import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.feature.payin.account.data.InvoiceDelivery -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.toDeliveryString import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel -import com.hedvig.android.feature.payin.account.ui.components.maskedAccountNumber import hedvig.resources.GENERAL_REMOVE import hedvig.resources.KIVRA_PAYMENT_INFO import hedvig.resources.PAYMENTS_ACCOUNT diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt index 38a8a3a458..59ca11808f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -10,8 +10,8 @@ import androidx.compose.runtime.setValue import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.RemoveMethodUseCase import com.hedvig.android.feature.payin.account.data.id import com.hedvig.android.feature.payin.account.data.provider diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index 23400f79ab..e7a5f0eb55 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard @@ -29,7 +30,6 @@ import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Minus -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark import com.hedvig.android.feature.payin.account.ui.components.PayinProviderPillow diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt index bd3a07581c..51e7117231 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -23,6 +23,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.data.paying.member.InvoiceDelivery +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.EmptyState import com.hedvig.android.design.system.hedvig.EmptyStateDefaults @@ -38,8 +40,6 @@ import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.icon.ChevronRight import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.feature.payin.account.data.InvoiceDelivery -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PrimaryMethodLabel import hedvig.resources.PAYMENT_ADD_METHOD_BUTTON @@ -179,7 +179,8 @@ private fun PayoutAccountContent( } if (currentMethods .filter { !it.isPending } - .size > 1) { + .size > 1 + ) { Spacer(Modifier.height(8.dp)) HedvigTextButton( text = stringResource(Res.string.PAYMENT_CHOOSE_PRIMARY_BUTTON), diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt index b6d76da11a..1762a194c6 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -9,8 +9,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase -import com.hedvig.android.feature.payin.account.data.PayinAccount import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index 946d16afb4..afadc963fd 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -22,6 +22,11 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.data.paying.member.InvoiceDelivery +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.PayinAccount.Invoice +import com.hedvig.android.data.paying.member.PayinAccount.SwishPayin +import com.hedvig.android.data.paying.member.PayinAccount.Trustly import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard @@ -47,11 +52,6 @@ import com.hedvig.android.design.system.hedvig.icon.HelipadFilled import com.hedvig.android.design.system.hedvig.icon.HelipadOutline import com.hedvig.android.design.system.hedvig.icon.Plus import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.feature.payin.account.data.InvoiceDelivery -import com.hedvig.android.feature.payin.account.data.PayinAccount -import com.hedvig.android.feature.payin.account.data.PayinAccount.Invoice -import com.hedvig.android.feature.payin.account.data.PayinAccount.SwishPayin -import com.hedvig.android.feature.payin.account.data.PayinAccount.Trustly import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt index 8dae50f42a..e7bfbfc6aa 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt @@ -8,7 +8,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel -import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.molecule.public.MoleculePresenter diff --git a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt index 8297a95604..3aa68aada6 100644 --- a/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt +++ b/app/feature/feature-payin-account/src/test/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKeySerializationTest.kt @@ -2,8 +2,8 @@ package com.hedvig.android.feature.payin.account.navigation import assertk.assertThat import assertk.assertions.isEqualTo -import com.hedvig.android.feature.payin.account.data.InvoiceDelivery -import com.hedvig.android.feature.payin.account.data.PayinAccount +import com.hedvig.android.data.paying.member.InvoiceDelivery +import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.navigation.common.HedvigNavKey import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.json.Json diff --git a/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql b/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql index da34fe73c5..782d8b2ee4 100644 --- a/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql +++ b/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql @@ -11,12 +11,9 @@ query ManualChargeInfo { } } } - paymentInformation { - status - chargeMethod { - displayName - descriptor - paymentMethod + paymentMethods(version: 2) { + payinMethods { + ...MemberPaymentMethodFragment } } activeContracts { diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt index b693ee0128..bece4f2e3e 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt @@ -12,6 +12,8 @@ import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.toPayinAccount import com.hedvig.android.logger.logcat import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject @@ -59,8 +61,9 @@ internal class GetManualChargeInfoUseCaseImpl( chargeId = latestFailedPastCharge.id, missedDueDate = latestFailedPastCharge.date, amountDue = UiMoney.fromMoneyFragment(latestFailedPastCharge.net), - bankAccountDisplayValue = currentMember.paymentInformation.chargeMethod?.displayName, - bankDescriptor = currentMember.paymentInformation.chargeMethod?.descriptor, + primaryPayinMethod = currentMember.paymentMethods.payinMethods + .firstOrNull { it.isDefault } + ?.toPayinAccount(), showCancellationWarning = showCancellationWarning, ) } @@ -70,7 +73,7 @@ internal data class ManualChargeInfo( val chargeId: String?, val missedDueDate: LocalDate, val amountDue: UiMoney, - val bankDescriptor: String?, - val bankAccountDisplayValue: String?, + /** The method the member is charged on, absent when none is connected or it is one we cannot show. */ + val primaryPayinMethod: PayinAccount?, val showCancellationWarning: Boolean, ) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt index ffda4db0cc..9cdb78789c 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt @@ -24,6 +24,8 @@ import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterPr import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.maskedAccountNumber import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigErrorSection @@ -43,10 +45,14 @@ import com.hedvig.android.design.system.hedvig.icon.WarningFilled import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter import com.hedvig.android.design.system.hedvig.rememberHedvigMonthDateTimeFormatter import com.hedvig.android.feature.payments.data.ManualChargeInfo -import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE import hedvig.resources.GENERAL_ERROR_BODY import hedvig.resources.GENERAL_RETRY import hedvig.resources.MANUAL_CHARGE_CANCELLATION_WARNING +import hedvig.resources.PAYMENTS_ACCOUNT +import hedvig.resources.PAYMENTS_AUTOGIRO_LABEL +import hedvig.resources.PAYMENTS_BANK_LABEL +import hedvig.resources.PAYMENTS_INVOICE +import hedvig.resources.PAYMENTS_PAYMENT_METHOD import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_BODY import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_DUE_DATE import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_FINE_PRINT @@ -54,11 +60,13 @@ import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_PAY import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_SINCE import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_VIEW_DETAILS import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_TITLE +import hedvig.resources.PAYMENTS_SWISH_NUMBER import hedvig.resources.Res import hedvig.resources.SELF_MANUAL_CHARGE_CHANGES_BEEN_MADE_TITLE import hedvig.resources.claim_status_detail_chat_button_description import hedvig.resources.payment_details_receipt_card_total import hedvig.resources.something_went_wrong +import hedvig.resources.swish import kotlinx.datetime.LocalDate import org.jetbrains.compose.resources.stringResource @@ -233,22 +241,36 @@ private fun ManualChargeSuccessScreen( style = HedvigTheme.typography.label, ) } - if (uiState.manualChargeInfo.bankDescriptor != null) { - Spacer(Modifier.height(10.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - HedvigText( - text = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), - color = HedvigTheme.colorScheme.textSecondary, - style = HedvigTheme.typography.label, - ) - HedvigText( - text = uiState.manualChargeInfo.bankDescriptor, - color = HedvigTheme.colorScheme.textSecondary, - style = HedvigTheme.typography.label, - ) + val method = uiState.manualChargeInfo.primaryPayinMethod + if (method != null) { + DetailRow( + label = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + value = when (method) { + is PayinAccount.Trustly -> stringResource(Res.string.PAYMENTS_AUTOGIRO_LABEL) + is PayinAccount.SwishPayin -> stringResource(Res.string.swish) + is PayinAccount.Invoice -> stringResource(Res.string.PAYMENTS_INVOICE) + }, + ) + when (method) { + is PayinAccount.SwishPayin -> { + DetailRow( + label = stringResource(Res.string.PAYMENTS_SWISH_NUMBER), + value = method.phoneNumber.orEmpty(), + ) + } + + is PayinAccount.Trustly -> { + DetailRow( + label = stringResource(Res.string.PAYMENTS_ACCOUNT), + value = method.maskedAccountNumber().orEmpty(), + ) + DetailRow( + label = stringResource(Res.string.PAYMENTS_BANK_LABEL), + value = method.bankName.orEmpty(), + ) + } + + is PayinAccount.Invoice -> {} } } } @@ -306,6 +328,28 @@ private fun ManualChargeSuccessScreen( } } +@Composable +private fun DetailRow(label: String, value: String, modifier: Modifier = Modifier) { + Column(modifier) { + Spacer(Modifier.height(10.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + HedvigText( + text = label, + color = HedvigTheme.colorScheme.textSecondary, + style = HedvigTheme.typography.label, + ) + HedvigText( + text = value, + color = HedvigTheme.colorScheme.textSecondary, + style = HedvigTheme.typography.label, + ) + } + } +} + @Composable @Preview @HedvigPreview @@ -322,8 +366,13 @@ private fun ManualChargeScreenSuccessPreview( missedDueDate = LocalDate(2026, 1, 15), amountDue = UiMoney(100.0, UiCurrencyCode.SEK), chargeId = "chargeId", - bankDescriptor = "**** 8324", - bankAccountDisplayValue = "Swedbank", + primaryPayinMethod = PayinAccount.Trustly( + clearingNumber = "8327", + accountNumber = "91234124", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ), showCancellationWarning = showCancellationWarning, ), ), From 44782dc6098dad4a058f9cbd6d2c8991dc0abfe6 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 12:02:23 +0200 Subject: [PATCH 69/75] move PayinAccount to data module and use in manual charging --- .../src/main/graphql/GetPayinMethods.graphql | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql index 96166e967e..4ba076ac32 100644 --- a/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql +++ b/app/feature/feature-payin-account/src/main/graphql/GetPayinMethods.graphql @@ -9,19 +9,6 @@ query GetPayinMethods { provider supportsPayin isActive - details { - ... on PaymentMethodBankAccountDetails { - account - bank - } - ... on PaymentMethodSwishDetails { - phoneNumber - } - ... on PaymentMethodInvoiceDetails { - delivery - email - } - } } } } From 115ee5a287351382df4118836cfbf340685524a4 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 13:39:43 +0200 Subject: [PATCH 70/75] add entrypoint to set primary and add new payment methods from manual charge overview --- .../app/navigation/HedvigEntryProvider.kt | 8 + .../data/paying/member/PayinAccount.kt | 8 + .../build.gradle.kts | 3 + .../navigation/SelectPrimaryPayinMethodKey.kt | 14 ++ .../account/navigation/PayinAccountKey.kt | 7 + .../account/data/GetPayinAccountUseCase.kt | 7 - .../navigation/PayinAccountDestination.kt | 12 -- .../account/navigation/PayinAccountEntries.kt | 2 +- .../account/ui/components/PayinMethodRow.kt | 2 +- .../PayinMethodDetailsViewModel.kt | 2 +- .../RemovePayinMethodBottomSheet.kt | 2 +- .../SelectPrimaryPayinMethodDestination.kt | 2 +- .../SelectPrimaryPayinMethodViewModel.kt | 2 +- .../graphql/QueryManualChargeInfo.graphql | 4 + .../data/GetManualChargeInfoUseCase.kt | 13 +- .../payments/navigation/PaymentsEntries.kt | 16 ++ .../manualcharge/ManualChargeDestination.kt | 170 ++++++++++++++---- .../ui/manualcharge/ManualChargeViewModel.kt | 21 +++ 18 files changed, 229 insertions(+), 66 deletions(-) create mode 100644 app/feature/feature-payin-account-navigation/src/androidMain/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKey.kt diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index c8cfdfc513..e84d6fb560 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -54,6 +54,8 @@ import com.hedvig.android.feature.onboarding.data.ResetOnboardingSeenUseCase import com.hedvig.android.feature.onboarding.navigation.onboardingEntries import com.hedvig.android.feature.payin.account.navigation.PayinAccountKey import com.hedvig.android.feature.payin.account.navigation.PayinMethodDetailsKey +import com.hedvig.android.feature.payin.account.navigation.SelectPayinMethodKey +import com.hedvig.android.feature.payin.account.navigation.SelectPrimaryPayinMethodKey import com.hedvig.android.feature.payin.account.navigation.payinAccountEntries import com.hedvig.android.feature.payments.navigation.paymentsEntries import com.hedvig.android.feature.payoutaccount.navigation.PayoutAccountKey @@ -478,6 +480,12 @@ private fun EntryProviderScope.addPaymentsEntries( navigateToPayinAccount = navigateToPayinAccount, navigateToPayinMethodDetails = { method -> backstack.add(PayinMethodDetailsKey(method)) }, navigateToPayoutAccount = navigateToPayoutAccount, + navigateToSelectPayinMethod = { availableProviders, currentProviders -> + backstack.add(SelectPayinMethodKey(availableProviders, currentProviders)) + }, + navigateToSelectPrimaryPayinMethod = { currentMethods -> + backstack.add(SelectPrimaryPayinMethodKey(currentMethods)) + }, openConversation = navigateToNewConversation, ) payoutAccountEntries( diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt index 8619003125..582c9d4baf 100644 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt +++ b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/PayinAccount.kt @@ -90,6 +90,14 @@ fun MemberPaymentMethodFragment.toPayinAccount(): PayinAccount? { } } +/** Identifies the method to the backend: a member has at most one method per provider. */ +val PayinAccount.provider: MemberPaymentProvider + get() = when (this) { + is PayinAccount.Trustly -> MemberPaymentProvider.TRUSTLY + is PayinAccount.SwishPayin -> MemberPaymentProvider.SWISH + is PayinAccount.Invoice -> MemberPaymentProvider.INVOICE + } + /** The last four digits of the account, the only part of it we ever put on screen. */ fun PayinAccount.Trustly.maskedAccountNumber(): String? { val clearing = clearingNumber?.takeIf { it.isNotBlank() }.orEmpty() diff --git a/app/feature/feature-payin-account-navigation/build.gradle.kts b/app/feature/feature-payin-account-navigation/build.gradle.kts index 288520f5a2..c2442b5824 100644 --- a/app/feature/feature-payin-account-navigation/build.gradle.kts +++ b/app/feature/feature-payin-account-navigation/build.gradle.kts @@ -16,5 +16,8 @@ kotlin { implementation(projects.coreCommonPublic) implementation(projects.navigationCommon) } + androidMain.dependencies { + implementation(projects.dataPayingMember) + } } } diff --git a/app/feature/feature-payin-account-navigation/src/androidMain/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKey.kt b/app/feature/feature-payin-account-navigation/src/androidMain/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKey.kt new file mode 100644 index 0000000000..f3e901a77e --- /dev/null +++ b/app/feature/feature-payin-account-navigation/src/androidMain/kotlin/com/hedvig/android/feature/payin/account/navigation/SelectPrimaryPayinMethodKey.kt @@ -0,0 +1,14 @@ +package com.hedvig.android.feature.payin.account.navigation + +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.navigation.common.HedvigNavKey +import kotlinx.serialization.Serializable + +/** + * The picker for which connected method the member is charged on. Android-only because it carries + * [PayinAccount], which comes from the JVM-only `:data-paying-member`. + */ +@Serializable +data class SelectPrimaryPayinMethodKey( + val currentMethods: List, +) : HedvigNavKey diff --git a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt index 43f20dabe5..6dd7b68e3f 100644 --- a/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt +++ b/app/feature/feature-payin-account-navigation/src/commonMain/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountKey.kt @@ -25,3 +25,10 @@ data class PayinMethodDetailsKey( /** The Swish payin setup screen, reachable both from the payin flow and from onboarding. */ @Serializable data object SetupSwishPayinKey : HedvigNavKey + +/** The picker for connecting a new payin method, seeded with what the member can and already has. */ +@Serializable +data class SelectPayinMethodKey( + val availableProviders: List, + val currentProviders: List, +) : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt index 907105bd74..bc4ccf577c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -58,13 +58,6 @@ internal val PayinAccount.id: PayinMethodId is PayinAccount.Invoice -> PayinMethodId.Invoice } -internal val PayinAccount.provider: MemberPaymentProvider - get() = when (this) { - is PayinAccount.Trustly -> MemberPaymentProvider.TRUSTLY - is PayinAccount.SwishPayin -> MemberPaymentProvider.SWISH - is PayinAccount.Invoice -> MemberPaymentProvider.INVOICE - } - internal fun InvoiceDelivery?.toDeliveryString(): String? { return when (this) { // TODO: Add "Kivra" / "Kivra" to Lokalise diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt index 710b49906d..f64787bbbf 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -1,15 +1,8 @@ package com.hedvig.android.feature.payin.account.navigation -import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.navigation.common.HedvigNavKey import kotlinx.serialization.Serializable -@Serializable -internal data class SelectPayinMethodKey( - val availableProviders: List, - val currentProviders: List, -) : HedvigNavKey - /** * Waiting on a Swish setup the member approves in the Swish app. Carries the phone number so a * failed attempt can be retried with it, without sending them back to re-enter it. @@ -23,8 +16,3 @@ internal data class SwishPayinStatusKey( @Serializable internal data object SetupInvoicePayinKey : HedvigNavKey - -@Serializable -internal data class SelectPrimaryPayinMethodKey( - val currentMethods: List, -) : HedvigNavKey diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt index 44b2e7d4e7..f39691ae30 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountEntries.kt @@ -5,10 +5,10 @@ import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed import com.hedvig.android.core.buildconstants.HedvigBuildConstants import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payin.account.data.SwishSetupOrder import com.hedvig.android.feature.payin.account.data.id -import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsDestination import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsViewModel import com.hedvig.android.feature.payin.account.ui.methoddetails.PayinMethodDetailsViewModelFactory diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt index a7d4c86557..c0464772af 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/components/PayinMethodRow.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp import com.hedvig.android.compose.ui.EmptyContentDescription import com.hedvig.android.data.paying.member.InvoiceDelivery import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText @@ -36,7 +37,6 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Trustly import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish -import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.data.toDeliveryString import hedvig.resources.PAYMENTS_BANK_LABEL import hedvig.resources.PAYMENT_PRIMARY_LABEL diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt index 59ca11808f..f797002632 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/PayinMethodDetailsViewModel.kt @@ -11,10 +11,10 @@ import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.feature.payin.account.data.GetPayinAccountUseCase import com.hedvig.android.feature.payin.account.data.RemoveMethodUseCase import com.hedvig.android.feature.payin.account.data.id -import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt index e7a5f0eb55..400471b80c 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/methoddetails/RemovePayinMethodBottomSheet.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard @@ -30,7 +31,6 @@ import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.Minus -import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodMark import com.hedvig.android.feature.payin.account.ui.components.PayinProviderPillow import com.hedvig.android.feature.payin.account.ui.components.toRadioOption diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt index afadc963fd..081c44c53f 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodDestination.kt @@ -27,6 +27,7 @@ import com.hedvig.android.data.paying.member.PayinAccount import com.hedvig.android.data.paying.member.PayinAccount.Invoice import com.hedvig.android.data.paying.member.PayinAccount.SwishPayin import com.hedvig.android.data.paying.member.PayinAccount.Trustly +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard @@ -52,7 +53,6 @@ import com.hedvig.android.design.system.hedvig.icon.HelipadFilled import com.hedvig.android.design.system.hedvig.icon.HelipadOutline import com.hedvig.android.design.system.hedvig.icon.Plus import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.feature.payin.account.ui.components.PayinMethodHandoverIllustration import com.hedvig.android.feature.payin.account.ui.components.PayinMethodRow import com.hedvig.android.feature.payin.account.ui.components.PayinProviderPillow diff --git a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt index e7bfbfc6aa..c506438dd8 100644 --- a/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/primary/SelectPrimaryPayinMethodViewModel.kt @@ -9,8 +9,8 @@ import androidx.compose.runtime.setValue import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.feature.payin.account.data.SetAsDefaultUseCase -import com.hedvig.android.feature.payin.account.data.provider import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import com.hedvig.android.molecule.public.MoleculeViewModel diff --git a/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql b/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql index 782d8b2ee4..b5f15e22b8 100644 --- a/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql +++ b/app/feature/feature-payments/src/main/graphql/QueryManualChargeInfo.graphql @@ -15,6 +15,10 @@ query ManualChargeInfo { payinMethods { ...MemberPaymentMethodFragment } + availableMethods { + provider + supportsPayin + } } activeContracts { terminationDueToMissedPayments diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt index bece4f2e3e..e2c84d0089 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt @@ -20,6 +20,7 @@ import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import kotlinx.datetime.LocalDate import octopus.ManualChargeInfoQuery +import octopus.type.MemberPaymentProvider internal interface GetManualChargeInfoUseCase { suspend fun invoke(): Either @@ -57,13 +58,17 @@ internal class GetManualChargeInfoUseCaseImpl( raise(ErrorMessage()) } + val currentMethods = currentMember.paymentMethods.payinMethods.mapNotNull { it.toPayinAccount() } + ManualChargeInfo( chargeId = latestFailedPastCharge.id, missedDueDate = latestFailedPastCharge.date, amountDue = UiMoney.fromMoneyFragment(latestFailedPastCharge.net), - primaryPayinMethod = currentMember.paymentMethods.payinMethods - .firstOrNull { it.isDefault } - ?.toPayinAccount(), + currentMethods = currentMethods, + availablePayinMethods = currentMember.paymentMethods.availableMethods + .filter { it.supportsPayin } + .map { it.provider }, + primaryPayinMethod = currentMethods.firstOrNull { it.isDefault }, showCancellationWarning = showCancellationWarning, ) } @@ -73,6 +78,8 @@ internal data class ManualChargeInfo( val chargeId: String?, val missedDueDate: LocalDate, val amountDue: UiMoney, + val currentMethods: List, + val availablePayinMethods: List, /** The method the member is charged on, absent when none is connected or it is one we cannot show. */ val primaryPayinMethod: PayinAccount?, val showCancellationWarning: Boolean, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt index 5ba66d81c5..eb77154011 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/navigation/PaymentsEntries.kt @@ -3,6 +3,8 @@ package com.hedvig.android.feature.payments.navigation import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope import com.hedvig.android.compose.ui.dropUnlessResumed +import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.provider import com.hedvig.android.feature.forever.navigation.InviteFriendsKey import com.hedvig.android.feature.payin.account.navigation.PayinMethodId import com.hedvig.android.feature.payments.ui.details.PaymentDetailExplanationContent @@ -15,6 +17,7 @@ import com.hedvig.android.feature.payments.ui.history.PaymentHistoryDestination import com.hedvig.android.feature.payments.ui.history.PaymentHistoryViewModel import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeDestination import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeSuccessDestination +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeViewModel import com.hedvig.android.feature.payments.ui.payments.PaymentsDestination import com.hedvig.android.feature.payments.ui.payments.PaymentsViewModel @@ -32,6 +35,8 @@ fun EntryProviderScope.paymentsEntries( navigateToPayinAccount: () -> Unit, navigateToPayinMethodDetails: (PayinMethodId) -> Unit, navigateToPayoutAccount: () -> Unit, + navigateToSelectPayinMethod: (availableProviders: List, currentProviders: List) -> Unit, + navigateToSelectPrimaryPayinMethod: (currentMethods: List) -> Unit, openConversation: () -> Unit, ) { entry(metadata = NavSuiteSceneDecoratorStrategy.showNavBar()) { @@ -64,6 +69,17 @@ fun EntryProviderScope.paymentsEntries( onNavigateToPaymentDetails = dropUnlessResumed { chargeId: String -> backstack.add(PaymentDetailsKey(chargeId)) }, + onConnectPayinMethodClicked = dropUnlessResumed { + val info = (viewModel.uiState.value as? ManualChargeUiState.Success)?.manualChargeInfo + navigateToSelectPayinMethod( + info?.availablePayinMethods?.map { it.rawValue } ?: emptyList(), + info?.currentMethods?.map { it.provider.rawValue } ?: emptyList(), + ) + }, + onChoosePrimaryMethodClicked = dropUnlessResumed { + val info = (viewModel.uiState.value as? ManualChargeUiState.Success)?.manualChargeInfo + navigateToSelectPrimaryPayinMethod(info?.currentMethods ?: emptyList()) + }, openConversation = openConversation, ) } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt index 9cdb78789c..b0502cbc8f 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt @@ -12,6 +12,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -19,14 +23,20 @@ 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 androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.data.paying.member.PayinAccount +import com.hedvig.android.data.paying.member.PayinAccount.Invoice +import com.hedvig.android.data.paying.member.PayinAccount.SwishPayin +import com.hedvig.android.data.paying.member.PayinAccount.Trustly import com.hedvig.android.data.paying.member.maskedAccountNumber import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize +import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonStyle import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress @@ -34,10 +44,12 @@ import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HorizontalDivider import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.NotificationDefaults +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.AttentionRound import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.HedvigIcons @@ -45,6 +57,12 @@ import com.hedvig.android.design.system.hedvig.icon.WarningFilled import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter import com.hedvig.android.design.system.hedvig.rememberHedvigMonthDateTimeFormatter import com.hedvig.android.feature.payments.data.ManualChargeInfo +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeEvent.Refresh +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeEvent.Retry +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeEvent.TriggerCharge +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.Failure +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.Loading +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.Success import hedvig.resources.GENERAL_ERROR_BODY import hedvig.resources.GENERAL_RETRY import hedvig.resources.MANUAL_CHARGE_CANCELLATION_WARNING @@ -61,13 +79,17 @@ import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_SINCE import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_DETAILS_VIEW_DETAILS import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_TITLE import hedvig.resources.PAYMENTS_SWISH_NUMBER +import hedvig.resources.PAYMENT_ADD_METHOD_BUTTON +import hedvig.resources.PAYMENT_CHOOSE_PRIMARY_BUTTON import hedvig.resources.Res +import hedvig.resources.Res.string import hedvig.resources.SELF_MANUAL_CHARGE_CHANGES_BEEN_MADE_TITLE import hedvig.resources.claim_status_detail_chat_button_description import hedvig.resources.payment_details_receipt_card_total import hedvig.resources.something_went_wrong import hedvig.resources.swish import kotlinx.datetime.LocalDate +import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @Composable @@ -75,18 +97,31 @@ internal fun ManualChargeDestination( viewModel: ManualChargeViewModel, navigateUp: () -> Unit, onNavigateToPaymentDetails: (chargeId: String) -> Unit, + onConnectPayinMethodClicked: () -> Unit, + onChoosePrimaryMethodClicked: () -> Unit, openConversation: () -> Unit, ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle() + var hasResumedOnce by rememberSaveable { mutableStateOf(false) } + LifecycleResumeEffect(Unit) { + if (hasResumedOnce) { + viewModel.emit(Refresh) + } else { + hasResumedOnce = true + } + onPauseOrDispose {} + } ManualChargeScreen( uiState = uiState.value, navigateUp = navigateUp, - reload = { viewModel.emit(ManualChargeEvent.Retry) }, + reload = { viewModel.emit(Retry) }, onNavigateToPaymentDetails = onNavigateToPaymentDetails, onTriggerPayment = { - viewModel.emit(ManualChargeEvent.TriggerCharge) + viewModel.emit(TriggerCharge) }, + onConnectPayinMethodClicked = onConnectPayinMethodClicked, + onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, openConversation = openConversation, ) } @@ -99,27 +134,29 @@ private fun ManualChargeScreen( openConversation: () -> Unit, onNavigateToPaymentDetails: (chargeId: String) -> Unit, onTriggerPayment: () -> Unit, + onConnectPayinMethodClicked: () -> Unit, + onChoosePrimaryMethodClicked: () -> Unit, ) { HedvigScaffold( navigateUp = navigateUp, - topAppBarText = stringResource(Res.string.PAYMENTS_PAYMENT_OVERDUE_TITLE), + topAppBarText = stringResource(string.PAYMENTS_PAYMENT_OVERDUE_TITLE), ) { when (uiState) { - is ManualChargeUiState.Failure -> { + is Failure -> { val title = if (uiState.error.message != null) { - stringResource(Res.string.SELF_MANUAL_CHARGE_CHANGES_BEEN_MADE_TITLE) + stringResource(string.SELF_MANUAL_CHARGE_CHANGES_BEEN_MADE_TITLE) } else { - stringResource(Res.string.something_went_wrong) + stringResource(string.something_went_wrong) } val subTitle = if (uiState.error.message != null) { uiState.error.message } else { - stringResource(Res.string.GENERAL_ERROR_BODY) + stringResource(string.GENERAL_ERROR_BODY) } val buttonText = if (uiState.error.message != null) { - stringResource(Res.string.claim_status_detail_chat_button_description) + stringResource(string.claim_status_detail_chat_button_description) } else { - stringResource(Res.string.GENERAL_RETRY) + stringResource(string.GENERAL_RETRY) } val onButtonClick = if (uiState.error.message != null) openConversation else reload @@ -134,17 +171,19 @@ private fun ManualChargeScreen( ) } - ManualChargeUiState.Loading -> { + Loading -> { HedvigFullScreenCenterAlignedProgress( modifier = Modifier.weight(1f), ) } - is ManualChargeUiState.Success -> { + is Success -> { ManualChargeSuccessScreen( uiState, onNavigateToPaymentDetails = onNavigateToPaymentDetails, onTriggerPayment = onTriggerPayment, + onConnectPayinMethodClicked = onConnectPayinMethodClicked, + onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, ) } } @@ -153,10 +192,14 @@ private fun ManualChargeScreen( @Composable private fun ManualChargeSuccessScreen( - uiState: ManualChargeUiState.Success, + uiState: Success, onNavigateToPaymentDetails: (chargeId: String) -> Unit, onTriggerPayment: () -> Unit, + onConnectPayinMethodClicked: () -> Unit, + onChoosePrimaryMethodClicked: () -> Unit, ) { + val currentMethods = uiState.manualChargeInfo.currentMethods + val availablePayinMethods = uiState.manualChargeInfo.availablePayinMethods val dateTimeFormatter = rememberHedvigMonthDateTimeFormatter() val dateTimeFormatterWithYear = rememberHedvigDateTimeFormatter() Column { @@ -198,26 +241,26 @@ private fun ManualChargeSuccessScreen( Column(modifier = Modifier.weight(1f)) { HedvigText( text = stringResource( - Res.string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_SINCE, + string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_SINCE, dateTimeFormatter.format(uiState.manualChargeInfo.missedDueDate), ), ) HedvigText( - text = stringResource(Res.string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_BODY), + text = stringResource(string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_BODY), color = HedvigTheme.colorScheme.textSecondary, ) } } if (uiState.manualChargeInfo.chargeId != null) { HedvigButton( - text = stringResource(Res.string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_VIEW_DETAILS), + text = stringResource(string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_VIEW_DETAILS), onClick = { onNavigateToPaymentDetails(uiState.manualChargeInfo.chargeId) }, enabled = true, modifier = Modifier.fillMaxWidth(), - buttonStyle = ButtonDefaults.ButtonStyle.Ghost, - buttonSize = ButtonDefaults.ButtonSize.Medium, + buttonStyle = ButtonStyle.Ghost, + buttonSize = ButtonSize.Medium, border = HedvigTheme.colorScheme.borderPrimary, ) } @@ -231,7 +274,7 @@ private fun ManualChargeSuccessScreen( horizontalArrangement = Arrangement.SpaceBetween, ) { HedvigText( - text = stringResource(Res.string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_DUE_DATE), + text = stringResource(string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_DUE_DATE), color = HedvigTheme.colorScheme.textSecondary, style = HedvigTheme.typography.label, ) @@ -244,33 +287,33 @@ private fun ManualChargeSuccessScreen( val method = uiState.manualChargeInfo.primaryPayinMethod if (method != null) { DetailRow( - label = stringResource(Res.string.PAYMENTS_PAYMENT_METHOD), + label = stringResource(string.PAYMENTS_PAYMENT_METHOD), value = when (method) { - is PayinAccount.Trustly -> stringResource(Res.string.PAYMENTS_AUTOGIRO_LABEL) - is PayinAccount.SwishPayin -> stringResource(Res.string.swish) - is PayinAccount.Invoice -> stringResource(Res.string.PAYMENTS_INVOICE) + is Trustly -> stringResource(string.PAYMENTS_AUTOGIRO_LABEL) + is SwishPayin -> stringResource(string.swish) + is Invoice -> stringResource(string.PAYMENTS_INVOICE) }, ) when (method) { - is PayinAccount.SwishPayin -> { + is SwishPayin -> { DetailRow( - label = stringResource(Res.string.PAYMENTS_SWISH_NUMBER), + label = stringResource(string.PAYMENTS_SWISH_NUMBER), value = method.phoneNumber.orEmpty(), ) } - is PayinAccount.Trustly -> { + is Trustly -> { DetailRow( - label = stringResource(Res.string.PAYMENTS_ACCOUNT), + label = stringResource(string.PAYMENTS_ACCOUNT), value = method.maskedAccountNumber().orEmpty(), ) DetailRow( - label = stringResource(Res.string.PAYMENTS_BANK_LABEL), + label = stringResource(string.PAYMENTS_BANK_LABEL), value = method.bankName.orEmpty(), ) } - is PayinAccount.Invoice -> {} + is Invoice -> {} } } } @@ -287,7 +330,7 @@ private fun ManualChargeSuccessScreen( verticalAlignment = Alignment.CenterVertically, ) { HedvigText( - text = stringResource(Res.string.payment_details_receipt_card_total), + text = stringResource(string.payment_details_receipt_card_total), ) HedvigText( text = uiState.manualChargeInfo.amountDue.toString(), @@ -296,17 +339,17 @@ private fun ManualChargeSuccessScreen( } HedvigButton( - text = stringResource(Res.string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_PAY, uiState.manualChargeInfo.amountDue), + text = stringResource(string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_PAY, uiState.manualChargeInfo.amountDue), onClick = onTriggerPayment, enabled = !uiState.payButtonLoading, - buttonSize = ButtonDefaults.ButtonSize.Medium, + buttonSize = ButtonSize.Medium, modifier = Modifier.fillMaxWidth(), isLoading = uiState.payButtonLoading, ) Spacer(modifier = Modifier.height(8.dp)) HedvigText( - text = stringResource(Res.string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_FINE_PRINT), + text = stringResource(string.PAYMENTS_PAYMENT_OVERDUE_DETAILS_FINE_PRINT), color = HedvigTheme.colorScheme.textSecondaryTranslucent, textAlign = TextAlign.Center, style = HedvigTheme.typography.label, @@ -317,14 +360,41 @@ private fun ManualChargeSuccessScreen( } if (uiState.manualChargeInfo.showCancellationWarning) { HedvigNotificationCard( - message = stringResource(Res.string.MANUAL_CHARGE_CANCELLATION_WARNING), - priority = NotificationDefaults.NotificationPriority.AttentionRound, + message = stringResource(string.MANUAL_CHARGE_CANCELLATION_WARNING), + priority = AttentionRound, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), ) Spacer(Modifier.height(16.dp)) } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.height(16.dp)) + if (availablePayinMethods.isNotEmpty()) { + HedvigButton( + text = stringResource(string.PAYMENT_ADD_METHOD_BUTTON), + onClick = onConnectPayinMethodClicked, + enabled = true, + buttonStyle = ButtonStyle.Secondary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + if (currentMethods + .filter { !it.isPending } + .size > 1 + ) { + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + text = stringResource(string.PAYMENT_CHOOSE_PRIMARY_BUTTON), + onClick = onChoosePrimaryMethodClicked, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + Spacer(Modifier.height(16.dp)) } } @@ -361,12 +431,30 @@ private fun ManualChargeScreenSuccessPreview( HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { ManualChargeScreen( - uiState = ManualChargeUiState.Success( + uiState = Success( ManualChargeInfo( missedDueDate = LocalDate(2026, 1, 15), amountDue = UiMoney(100.0, UiCurrencyCode.SEK), chargeId = "chargeId", - primaryPayinMethod = PayinAccount.Trustly( + currentMethods = listOf( + SwishPayin( + "123456", + isPending = false, + isDefault = true + ), + Trustly( + clearingNumber = "8327", + accountNumber = "91234124", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ) + ), + availablePayinMethods = listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.TRUSTLY + ), + primaryPayinMethod = Trustly( clearingNumber = "8327", accountNumber = "91234124", bankName = "Swedbank", @@ -381,6 +469,8 @@ private fun ManualChargeScreenSuccessPreview( {}, {}, {}, + {}, + {}, ) } } @@ -393,12 +483,14 @@ private fun ManualChargeScreenLoadingPreview() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { ManualChargeScreen( - uiState = ManualChargeUiState.Loading, + uiState = Loading, navigateUp = {}, reload = {}, {}, {}, {}, + {}, + {}, ) } } @@ -415,7 +507,7 @@ private fun ManualChargeScreenFailurePreview( HedvigTheme { Surface { ManualChargeScreen( - uiState = ManualChargeUiState.Failure( + uiState = Failure( ErrorMessage( message = if (hasUserError) { "Cannot charge the failed payment since there have been some changes. " + @@ -430,6 +522,8 @@ private fun ManualChargeScreenFailurePreview( {}, {}, {}, + {}, + {}, ) } } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt index 0f9dd48dc5..b72816ee98 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt @@ -41,6 +41,7 @@ private class ManualChargePresenter( @Composable override fun MoleculePresenterScope.present(lastState: ManualChargeUiState): ManualChargeUiState { var dataLoadIteration by remember { mutableIntStateOf(0) } + var refreshIteration by remember { mutableIntStateOf(0) } var screenState by remember { mutableStateOf(lastState) } var triggerChargeIteration by remember { mutableIntStateOf(0) } @@ -53,6 +54,10 @@ private class ManualChargePresenter( ManualChargeEvent.TriggerCharge -> { triggerChargeIteration++ } + + ManualChargeEvent.Refresh -> { + refreshIteration++ + } } } @@ -89,6 +94,20 @@ private class ManualChargePresenter( }, ) } + + // Picks up a payin method connected or made primary further down the flow. It refreshes in + // place and keeps what is on screen if the refetch fails, so coming back here never flashes to + // loading or to an error over details the member can still act on. + LaunchedEffect(refreshIteration) { + if (refreshIteration == 0) return@LaunchedEffect + getManualChargeInfoUseCase.invoke().onRight { manualChargeInfo -> + val currentState = screenState as? ManualChargeUiState.Success + screenState = ManualChargeUiState.Success( + manualChargeInfo = manualChargeInfo, + payButtonLoading = currentState?.payButtonLoading == true, + ) + } + } return screenState } } @@ -109,5 +128,7 @@ internal sealed interface ManualChargeUiState { internal sealed interface ManualChargeEvent { data object Retry : ManualChargeEvent + data object Refresh : ManualChargeEvent + data object TriggerCharge : ManualChargeEvent } From f7279815125449522f4282ed71919be48779c16b Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 13:52:41 +0200 Subject: [PATCH 71/75] fix spacers and scrolling --- .../manualcharge/ManualChargeDestination.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt index b0502cbc8f..812aebc8b6 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt @@ -6,11 +6,14 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -43,6 +46,7 @@ import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProg import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme @@ -184,6 +188,7 @@ private fun ManualChargeScreen( onTriggerPayment = onTriggerPayment, onConnectPayinMethodClicked = onConnectPayinMethodClicked, onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, + modifier = Modifier.weight(1f) ) } } @@ -197,12 +202,17 @@ private fun ManualChargeSuccessScreen( onTriggerPayment: () -> Unit, onConnectPayinMethodClicked: () -> Unit, onChoosePrimaryMethodClicked: () -> Unit, + modifier: Modifier = Modifier ) { val currentMethods = uiState.manualChargeInfo.currentMethods val availablePayinMethods = uiState.manualChargeInfo.availablePayinMethods val dateTimeFormatter = rememberHedvigMonthDateTimeFormatter() val dateTimeFormatterWithYear = rememberHedvigDateTimeFormatter() - Column { + Column( + modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { Column( modifier = Modifier .padding( @@ -422,7 +432,7 @@ private fun DetailRow(label: String, value: String, modifier: Modifier = Modifie @Composable @Preview -@HedvigPreview +@HedvigShortMultiScreenPreview private fun ManualChargeScreenSuccessPreview( @PreviewParameter( BooleanCollectionPreviewParameterProvider::class, @@ -440,7 +450,7 @@ private fun ManualChargeScreenSuccessPreview( SwishPayin( "123456", isPending = false, - isDefault = true + isDefault = true, ), Trustly( clearingNumber = "8327", @@ -448,11 +458,11 @@ private fun ManualChargeScreenSuccessPreview( bankName = "Swedbank", isPending = false, isDefault = true, - ) + ), ), availablePayinMethods = listOf( MemberPaymentProvider.SWISH, - MemberPaymentProvider.TRUSTLY + MemberPaymentProvider.TRUSTLY, ), primaryPayinMethod = Trustly( clearingNumber = "8327", From 92cb52dee712e1e4aab171588567b1882f9da2bb Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 14:27:24 +0200 Subject: [PATCH 72/75] check for missedChargeIdToChargeManually being null --- .../data/GetManualChargeInfoUseCase.kt | 45 +++++++++++++------ .../manualcharge/ManualChargeDestination.kt | 17 ++++++- .../ui/manualcharge/ManualChargeViewModel.kt | 36 +++++++++------ 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt index e2c84d0089..2e7b278174 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetManualChargeInfoUseCase.kt @@ -23,7 +23,22 @@ import octopus.ManualChargeInfoQuery import octopus.type.MemberPaymentProvider internal interface GetManualChargeInfoUseCase { - suspend fun invoke(): Either + suspend fun invoke(): Either +} + +/** + * Separates "we could not load it" (the [Either] left) from the backend's own answer that there is + * nothing here to charge, so a caller can keep a usable screen through a network blip without also + * keeping one whose charge has gone away. + */ +internal sealed interface ManualChargeInfoResult { + data class Chargeable(val info: ManualChargeInfo) : ManualChargeInfoResult + + /** + * `missedChargeIdToChargeManually` came back null, which per the schema means the latest charge + * either succeeded or the member may no longer settle it themselves. + */ + data object NoLongerChargeable : ManualChargeInfoResult } @ContributesBinding(AppScope::class) @@ -32,7 +47,7 @@ internal interface GetManualChargeInfoUseCase { internal class GetManualChargeInfoUseCaseImpl( private val apolloClient: ApolloClient, ) : GetManualChargeInfoUseCase { - override suspend fun invoke(): Either = either { + override suspend fun invoke(): Either = either { val currentMember = apolloClient.query(ManualChargeInfoQuery()) .fetchPolicy(FetchPolicy.NetworkOnly) .safeExecute(::ErrorMessage) @@ -47,12 +62,14 @@ internal class GetManualChargeInfoUseCaseImpl( if (showManualCharge == null) { logcat { "GetManualChargeInfoUseCaseImpl: missedChargeIdToChargeManually is null" } - raise(ErrorMessage()) + return@either ManualChargeInfoResult.NoLongerChargeable } val latestFailedPastCharge = currentMember.pastCharges .firstOrNull { it.id == showManualCharge } + // The backend named a charge to settle but did not return it, which is not a state the member + // can act on either way, so it stays a plain failure they can retry out of. if (latestFailedPastCharge == null) { logcat { "GetManualChargeInfoUseCaseImpl: latestFailedPastCharge is null" } raise(ErrorMessage()) @@ -60,16 +77,18 @@ internal class GetManualChargeInfoUseCaseImpl( val currentMethods = currentMember.paymentMethods.payinMethods.mapNotNull { it.toPayinAccount() } - ManualChargeInfo( - chargeId = latestFailedPastCharge.id, - missedDueDate = latestFailedPastCharge.date, - amountDue = UiMoney.fromMoneyFragment(latestFailedPastCharge.net), - currentMethods = currentMethods, - availablePayinMethods = currentMember.paymentMethods.availableMethods - .filter { it.supportsPayin } - .map { it.provider }, - primaryPayinMethod = currentMethods.firstOrNull { it.isDefault }, - showCancellationWarning = showCancellationWarning, + ManualChargeInfoResult.Chargeable( + ManualChargeInfo( + chargeId = latestFailedPastCharge.id, + missedDueDate = latestFailedPastCharge.date, + amountDue = UiMoney.fromMoneyFragment(latestFailedPastCharge.net), + currentMethods = currentMethods, + availablePayinMethods = currentMember.paymentMethods.availableMethods + .filter { it.supportsPayin } + .map { it.provider }, + primaryPayinMethod = currentMethods.firstOrNull { it.isDefault }, + showCancellationWarning = showCancellationWarning, + ), ) } } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt index 812aebc8b6..c5807cc8c3 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeDestination.kt @@ -66,6 +66,7 @@ import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeEvent.Ret import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeEvent.TriggerCharge import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.Failure import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.Loading +import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.NoLongerChargeable import com.hedvig.android.feature.payments.ui.manualcharge.ManualChargeUiState.Success import hedvig.resources.GENERAL_ERROR_BODY import hedvig.resources.GENERAL_RETRY @@ -175,6 +176,18 @@ private fun ManualChargeScreen( ) } + NoLongerChargeable -> { + HedvigErrorSection( + onButtonClick = openConversation, + Modifier + .weight(1f) + .fillMaxWidth(), + title = stringResource(string.SELF_MANUAL_CHARGE_CHANGES_BEEN_MADE_TITLE), + subTitle = null, + buttonText = stringResource(string.claim_status_detail_chat_button_description), + ) + } + Loading -> { HedvigFullScreenCenterAlignedProgress( modifier = Modifier.weight(1f), @@ -188,7 +201,7 @@ private fun ManualChargeScreen( onTriggerPayment = onTriggerPayment, onConnectPayinMethodClicked = onConnectPayinMethodClicked, onChoosePrimaryMethodClicked = onChoosePrimaryMethodClicked, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) } } @@ -202,7 +215,7 @@ private fun ManualChargeSuccessScreen( onTriggerPayment: () -> Unit, onConnectPayinMethodClicked: () -> Unit, onChoosePrimaryMethodClicked: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val currentMethods = uiState.manualChargeInfo.currentMethods val availablePayinMethods = uiState.manualChargeInfo.availablePayinMethods diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt index b72816ee98..6217d7f955 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/manualcharge/ManualChargeViewModel.kt @@ -12,6 +12,7 @@ import com.hedvig.android.core.common.di.ActivityRetainedScope import com.hedvig.android.core.common.di.HedvigViewModel import com.hedvig.android.feature.payments.data.GetManualChargeInfoUseCase import com.hedvig.android.feature.payments.data.ManualChargeInfo +import com.hedvig.android.feature.payments.data.ManualChargeInfoResult import com.hedvig.android.feature.payments.data.TriggerManualChargeUseCase import com.hedvig.android.feature.payments.navigation.ManualChargeKey import com.hedvig.android.feature.payments.navigation.ManualChargeSuccessKey @@ -84,10 +85,11 @@ private class ManualChargePresenter( LaunchedEffect(dataLoadIteration) { screenState = ManualChargeUiState.Loading getManualChargeInfoUseCase.invoke().fold( - ifRight = { manualChargeInfo -> - screenState = ManualChargeUiState.Success( - manualChargeInfo = manualChargeInfo, - ) + ifRight = { result -> + screenState = when (result) { + is ManualChargeInfoResult.Chargeable -> ManualChargeUiState.Success(result.info) + ManualChargeInfoResult.NoLongerChargeable -> ManualChargeUiState.NoLongerChargeable + } }, ifLeft = { failure -> screenState = ManualChargeUiState.Failure(failure) @@ -95,17 +97,22 @@ private class ManualChargePresenter( ) } - // Picks up a payin method connected or made primary further down the flow. It refreshes in - // place and keeps what is on screen if the refetch fails, so coming back here never flashes to - // loading or to an error over details the member can still act on. + // Picks up a payin method connected or made primary further down the flow. Only a failure to + // reach the backend is ignored, so coming back never flashes to loading or to an error over + // details the member can still act on. The backend withdrawing the charge is not ignored: those + // details are no longer payable, so leaving them on screen would offer a charge that cannot go + // through. LaunchedEffect(refreshIteration) { if (refreshIteration == 0) return@LaunchedEffect - getManualChargeInfoUseCase.invoke().onRight { manualChargeInfo -> - val currentState = screenState as? ManualChargeUiState.Success - screenState = ManualChargeUiState.Success( - manualChargeInfo = manualChargeInfo, - payButtonLoading = currentState?.payButtonLoading == true, - ) + getManualChargeInfoUseCase.invoke().onRight { result -> + screenState = when (result) { + is ManualChargeInfoResult.Chargeable -> ManualChargeUiState.Success( + manualChargeInfo = result.info, + payButtonLoading = (screenState as? ManualChargeUiState.Success)?.payButtonLoading == true, + ) + + ManualChargeInfoResult.NoLongerChargeable -> ManualChargeUiState.NoLongerChargeable + } } } return screenState @@ -115,6 +122,9 @@ private class ManualChargePresenter( internal sealed interface ManualChargeUiState { data object Loading : ManualChargeUiState + /** Nothing left for the member to settle here, so the screen offers a way to ask about it instead. */ + data object NoLongerChargeable : ManualChargeUiState + data class Failure( val error: ErrorMessage, ) : ManualChargeUiState From 93488984ddeab974385926414c4e81903d6fe83d Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 15:27:49 +0200 Subject: [PATCH 73/75] change payout UI --- .../src/main/graphql/GetPayoutMethods.graphql | 2 +- .../PayoutAccountOverviewDestination.kt | 106 ++++++++++------ .../SelectPayoutMethodDestination.kt | 119 +++++++++++------- 3 files changed, 142 insertions(+), 85 deletions(-) diff --git a/app/feature/feature-payout-account/src/main/graphql/GetPayoutMethods.graphql b/app/feature/feature-payout-account/src/main/graphql/GetPayoutMethods.graphql index 944ae5d726..2525f7c348 100644 --- a/app/feature/feature-payout-account/src/main/graphql/GetPayoutMethods.graphql +++ b/app/feature/feature-payout-account/src/main/graphql/GetPayoutMethods.graphql @@ -1,6 +1,6 @@ query GetPayoutMethods { currentMember { - paymentMethods { + paymentMethods(version: 2) { payoutMethods { provider status diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt index 8146ff910b..7733d6b69c 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.hedvig.android.design.system.hedvig.EmptyState import com.hedvig.android.design.system.hedvig.EmptyStateDefaults +import com.hedvig.android.design.system.hedvig.EmptyStateDefaults.EmptyStateIconStyle import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced @@ -27,11 +28,21 @@ import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults +import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults.TextFieldSize import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.feature.payoutaccount.data.PayoutAccount +import com.hedvig.android.feature.payoutaccount.data.PayoutAccount.BankAccount +import com.hedvig.android.feature.payoutaccount.data.PayoutAccount.SwishPayout +import com.hedvig.android.feature.payoutaccount.data.PayoutAccount.Trustly +import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewEvent.Retry import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Content +import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Error +import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Loading +import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.NoPayoutOptions +import com.hedvig.android.feature.payoutaccount.ui.selectmethod.PayoutMethodRow import hedvig.resources.CHANGE_PAYOUT_METHOD_BUTTON_LABEL import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE import hedvig.resources.PAYMENTS_ACCOUNT @@ -43,6 +54,7 @@ import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD import hedvig.resources.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON import hedvig.resources.REFERRAL_PENDING_STATUS_LABEL import hedvig.resources.Res +import hedvig.resources.Res.string import hedvig.resources.swish import hedvig.resources.trustly import octopus.type.MemberPaymentProvider @@ -60,7 +72,7 @@ internal fun PayoutAccountOverviewDestination( uiState = uiState, onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, navigateToConnectPayment = navigateToConnectPayment, - onRetry = { viewModel.emit(PayoutAccountOverviewEvent.Retry) }, + onRetry = { viewModel.emit(Retry) }, navigateUp = navigateUp, ) } @@ -74,12 +86,12 @@ private fun PayoutAccountOverviewScreen( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = stringResource(Res.string.PAYOUT_PAGE_HEADING), + topAppBarText = stringResource(string.PAYOUT_PAGE_HEADING), navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { when (uiState) { - PayoutAccountOverviewUiState.Loading -> { + Loading -> { HedvigFullScreenCenterAlignedProgressDebounced( Modifier .weight(1f) @@ -87,7 +99,7 @@ private fun PayoutAccountOverviewScreen( ) } - PayoutAccountOverviewUiState.Error -> { + Error -> { HedvigErrorSection( onButtonClick = onRetry, modifier = Modifier @@ -96,11 +108,11 @@ private fun PayoutAccountOverviewScreen( ) } - PayoutAccountOverviewUiState.NoPayoutOptions -> { + NoPayoutOptions -> { HedvigInformationSection( - title = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_TITLE), - subTitle = stringResource(Res.string.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE), - buttonText = stringResource(Res.string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON), + title = stringResource(string.PAYOUT_NO_PAYOUT_OPTIONS_TITLE), + subTitle = stringResource(string.PAYOUT_NO_PAYOUT_OPTIONS_SUBTITLE), + buttonText = stringResource(string.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_BUTTON), onButtonClick = navigateToConnectPayment, modifier = Modifier .weight(1f) @@ -134,46 +146,58 @@ private fun PayoutAccountContent( if (availablePayoutMethods.isNotEmpty()) { Spacer(Modifier.weight(1f)) EmptyState( - text = stringResource(Res.string.PAYOUT_MISSING_INFO), + text = stringResource(string.PAYOUT_MISSING_INFO), description = null, - iconStyle = EmptyStateDefaults.EmptyStateIconStyle.INFO, + iconStyle = EmptyStateIconStyle.INFO, ) } } - is PayoutAccount.SwishPayout -> { + is SwishPayout -> { val phoneNumber = currentMethod.phoneNumber.orEmpty() - PayoutAccountReadOnlyTextField( - label = stringResource(Res.string.swish), - text = if (currentMethod.isPending && phoneNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + PayoutMethodRow( + provider = MemberPaymentProvider.SWISH, + title = stringResource(string.swish), + subtitle = if (currentMethod.isPending && phoneNumber.isBlank()) { + stringResource(string.REFERRAL_PENDING_STATUS_LABEL) } else { phoneNumber }, + onClick = {}, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + isLocked = true ) } - is PayoutAccount.Trustly -> { + is Trustly -> { val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayoutAccountReadOnlyTextField( - label = formatBankAccountLabel(stringResource(Res.string.trustly), currentMethod.bankName), - text = if (currentMethod.isPending && accountNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + PayoutMethodRow( + title = formatBankAccountLabel(stringResource(string.trustly), currentMethod.bankName), + subtitle = if (currentMethod.isPending && accountNumber.isBlank()) { + stringResource(string.REFERRAL_PENDING_STATUS_LABEL) } else { accountNumber }, + onClick = {}, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + isLocked = true, + provider = MemberPaymentProvider.TRUSTLY ) } - is PayoutAccount.BankAccount -> { + is BankAccount -> { val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayoutAccountReadOnlyTextField( - label = formatBankAccountLabel(stringResource(Res.string.PAYMENTS_ACCOUNT), currentMethod.bankName), - text = if (currentMethod.isPending && accountNumber.isBlank()) { - stringResource(Res.string.REFERRAL_PENDING_STATUS_LABEL) + PayoutMethodRow( + title = formatBankAccountLabel(stringResource(string.PAYMENTS_ACCOUNT), currentMethod.bankName), + subtitle = if (currentMethod.isPending && accountNumber.isBlank()) { + stringResource(string.REFERRAL_PENDING_STATUS_LABEL) } else { accountNumber }, + onClick = {}, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + isLocked = true, + provider = MemberPaymentProvider.NORDEA ) } } @@ -181,8 +205,8 @@ private fun PayoutAccountContent( Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { if (currentMethod?.isPending == true) { HedvigNotificationCard( - message = stringResource(Res.string.MY_PAYMENT_UPDATING_MESSAGE), - priority = NotificationPriority.Info, + message = stringResource(string.MY_PAYMENT_UPDATING_MESSAGE), + priority = Info, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -191,9 +215,9 @@ private fun PayoutAccountContent( if (availablePayoutMethods.isNotEmpty()) { HedvigButton( text = if (currentMethod == null) { - stringResource(Res.string.PAYOUT_SELECT_PAYOUT_METHOD) + stringResource(string.PAYOUT_SELECT_PAYOUT_METHOD) } else { - stringResource(Res.string.CHANGE_PAYOUT_METHOD_BUTTON_LABEL) + stringResource(string.CHANGE_PAYOUT_METHOD_BUTTON_LABEL) }, onClick = onConnectPayoutMethodClicked, enabled = true, @@ -213,7 +237,7 @@ private fun PayoutAccountReadOnlyTextField(label: String, text: String, modifier text = text, onValueChange = {}, labelText = label, - textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, + textFieldSize = TextFieldSize.Medium, readOnly = true, modifier = modifier .fillMaxWidth() @@ -252,31 +276,31 @@ private fun PreviewPayoutAccountOverviewScreen( private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( listOf( - PayoutAccountOverviewUiState.Loading, - PayoutAccountOverviewUiState.Error, - PayoutAccountOverviewUiState.NoPayoutOptions, + Loading, + Error, + NoPayoutOptions, Content( currentMethod = null, availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), ), Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = false), + currentMethod = SwishPayout(phoneNumber = "070-123 45 67", isPending = false), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), ), Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = false), + currentMethod = SwishPayout(phoneNumber = "070-123 45 67", isPending = false), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), ), Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = null, isPending = true), + currentMethod = SwishPayout(phoneNumber = null, isPending = true), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), ), Content( - currentMethod = PayoutAccount.SwishPayout(phoneNumber = "070-123 45 67", isPending = true), + currentMethod = SwishPayout(phoneNumber = "070-123 45 67", isPending = true), availablePayoutMethods = listOf(MemberPaymentProvider.SWISH), ), Content( - currentMethod = PayoutAccount.Trustly( + currentMethod = Trustly( clearingNumber = "8327", accountNumber = "12345678", bankName = "Mock Swedbank", @@ -285,7 +309,7 @@ private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterP availablePayoutMethods = listOf(MemberPaymentProvider.TRUSTLY), ), Content( - currentMethod = PayoutAccount.BankAccount( + currentMethod = BankAccount( clearingNumber = "3300", accountNumber = "1234567", bankName = "Nordea", @@ -294,7 +318,7 @@ private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterP availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), ), Content( - currentMethod = PayoutAccount.BankAccount( + currentMethod = BankAccount( clearingNumber = null, accountNumber = null, bankName = null, @@ -303,7 +327,7 @@ private class PayoutAccountOverviewUiStateProvider : CollectionPreviewParameterP availablePayoutMethods = listOf(MemberPaymentProvider.NORDEA), ), Content( - currentMethod = PayoutAccount.BankAccount( + currentMethod = BankAccount( clearingNumber = "3300", accountNumber = "1234567", bankName = "Nordea", diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 1e590569aa..5efb13e74a 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -2,32 +2,35 @@ package com.hedvig.android.feature.payoutaccount.ui.selectmethod import androidx.compose.foundation.Image 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.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider import com.hedvig.android.design.system.hedvig.HedvigCard -import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.icon.BankAccount import com.hedvig.android.design.system.hedvig.icon.Card import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.Link +import com.hedvig.android.design.system.hedvig.icon.Lock import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Kivra import com.hedvig.android.design.system.hedvig.icon.colored.Swish import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE @@ -35,6 +38,7 @@ import hedvig.resources.PAYOUT_METHOD_SWISH_DESCRIPTION import hedvig.resources.PAYOUT_METHOD_TRUSTLY_DESCRIPTION import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD import hedvig.resources.Res +import hedvig.resources.Res.string import hedvig.resources.swish import hedvig.resources.trustly import octopus.type.MemberPaymentProvider @@ -49,7 +53,7 @@ internal fun SelectPayoutMethodDestination( navigateUp: () -> Unit, ) { HedvigScaffold( - topAppBarText = stringResource(Res.string.PAYOUT_SELECT_PAYOUT_METHOD), + topAppBarText = stringResource(string.PAYOUT_SELECT_PAYOUT_METHOD), navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { @@ -59,8 +63,8 @@ internal fun SelectPayoutMethodDestination( when (provider) { MemberPaymentProvider.TRUSTLY -> { PayoutMethodRow( - title = stringResource(Res.string.trustly), - subtitle = stringResource(Res.string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), + title = stringResource(string.trustly), + subtitle = stringResource(string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), onClick = onTrustlySelected, provider = provider, ) @@ -68,8 +72,8 @@ internal fun SelectPayoutMethodDestination( MemberPaymentProvider.NORDEA -> { PayoutMethodRow( - title = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), - subtitle = "Payout to a bank account", // todo: removed BANK_PAYOUT_METHOD_CARD_DESCRIPTION for demo + title = stringResource(string.BANK_PAYOUT_METHOD_CARD_TITLE), + subtitle = stringResource(string.BANK_PAYOUT_METHOD_CARD_DESCRIPTION), onClick = onNordeaSelected, provider = provider, ) @@ -77,8 +81,8 @@ internal fun SelectPayoutMethodDestination( MemberPaymentProvider.SWISH -> { PayoutMethodRow( - title = stringResource(Res.string.swish), - subtitle = stringResource(Res.string.PAYOUT_METHOD_SWISH_DESCRIPTION), + title = stringResource(string.swish), + subtitle = stringResource(string.PAYOUT_METHOD_SWISH_DESCRIPTION), onClick = onSwishSelected, provider = provider, ) @@ -93,57 +97,67 @@ internal fun SelectPayoutMethodDestination( } @Composable -private fun PayoutMethodRow( +internal fun PayoutMethodRow( provider: MemberPaymentProvider, title: String, subtitle: String, onClick: () -> Unit, modifier: Modifier = Modifier, + isLocked: Boolean = false, ) { HedvigCard( - onClick = onClick, + onClick = if (isLocked) null else onClick, modifier = modifier.fillMaxWidth(), ) { Row( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier + .heightIn(min = 64.dp) + .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, ) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - Icon( - HedvigIcons.Trustly, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - MemberPaymentProvider.SWISH -> { - Image( - HedvigIcons.Swish, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - MemberPaymentProvider.NORDEA -> { - Icon( - HedvigIcons.Card, - null, // todo - modifier = Modifier.size(32.dp), - ) - } - - else -> {} - } - - Spacer(Modifier.width(16.dp)) - Column(Modifier.padding(horizontal = 16.dp, vertical = 12.dp)) { + PayoutMethodPillow(provider) + Spacer(Modifier.width(10.dp)) + Column(Modifier.padding(vertical = 8.dp)) { HedvigText(text = title) HedvigText( text = subtitle, color = HedvigTheme.colorScheme.textSecondary, ) } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.width(4.dp)) + if (isLocked) { + Icon( + HedvigIcons.Lock, + null, + modifier = Modifier.size(28.dp), //todo: get icon from design! + ) + } + } + } +} + +@Composable +private fun PayoutMethodPillow(provider: MemberPaymentProvider) { + val onDarkTile = provider == MemberPaymentProvider.TRUSTLY + Surface( + shape = HedvigTheme.shapes.cornerSmall, + color = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + modifier = Modifier.size(40.dp), + ) { + Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> Icon(HedvigIcons.Trustly, null, Modifier.size(28.dp)) + MemberPaymentProvider.SWISH -> Image(HedvigIcons.Swish, null, Modifier.size(28.dp)) + MemberPaymentProvider.INVOICE -> Image(HedvigIcons.Kivra, null, Modifier.size(28.dp)) + MemberPaymentProvider.NORDEA -> Icon( + HedvigIcons.Card, + null, + modifier = Modifier.size(28.dp), //todo: get icon from design! + ) + MemberPaymentProvider.UNKNOWN__ -> {} + } } } } @@ -167,3 +181,22 @@ private fun PreviewSelectPayoutMethodScreen() { } } } + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewPayoutMethodRow( + @PreviewParameter(BooleanCollectionPreviewParameterProvider::class) isLocked: Boolean, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary, + modifier =Modifier.padding(16.dp)) { + PayoutMethodRow( + provider = MemberPaymentProvider.SWISH, + title = stringResource(string.swish), + subtitle = "123456789", + onClick = {}, + isLocked = isLocked + ) + } + } +} From cd2c59fab2ddb37a773d132dcbb4c8c4c4d9fcdc Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 16:13:37 +0200 Subject: [PATCH 74/75] add alpha --- .../SelectPayoutMethodDestination.kt | 182 ++++++++++++------ 1 file changed, 128 insertions(+), 54 deletions(-) diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 5efb13e74a..37305ffba7 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -37,7 +37,6 @@ import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE import hedvig.resources.PAYOUT_METHOD_SWISH_DESCRIPTION import hedvig.resources.PAYOUT_METHOD_TRUSTLY_DESCRIPTION import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD -import hedvig.resources.Res import hedvig.resources.Res.string import hedvig.resources.swish import hedvig.resources.trustly @@ -105,58 +104,113 @@ internal fun PayoutMethodRow( modifier: Modifier = Modifier, isLocked: Boolean = false, ) { - HedvigCard( - onClick = if (isLocked) null else onClick, - modifier = modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier - .heightIn(min = 64.dp) - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, + Box { + HedvigCard( + onClick = if (isLocked) null else onClick, + modifier = modifier.fillMaxWidth(), ) { - PayoutMethodPillow(provider) - Spacer(Modifier.width(10.dp)) - Column(Modifier.padding(vertical = 8.dp)) { - HedvigText(text = title) - HedvigText( - text = subtitle, - color = HedvigTheme.colorScheme.textSecondary, - ) - } - Spacer(Modifier.weight(1f)) - Spacer(Modifier.width(4.dp)) - if (isLocked) { - Icon( - HedvigIcons.Lock, - null, - modifier = Modifier.size(28.dp), //todo: get icon from design! - ) + Row( + modifier = Modifier + .heightIn(min = 64.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + PayoutMethodPillow(provider, isLocked) + Spacer(Modifier.width(10.dp)) + Column(Modifier.padding(vertical = 8.dp)) { + HedvigText( + text = title, + color = if (!isLocked) { + HedvigTheme.colorScheme.textPrimary + } else { + HedvigTheme.colorScheme.textSecondaryTranslucent + }, + ) + HedvigText( + text = subtitle, + color = if (!isLocked) { + HedvigTheme.colorScheme.textSecondary + } else { + HedvigTheme.colorScheme.textDisabledTranslucent + }, + ) + } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.width(4.dp)) + if (isLocked) { + Icon( + HedvigIcons.Lock, + null, + tint = HedvigTheme.colorScheme.fillDisabledTransparent, + modifier = Modifier.size(28.dp), // todo: get icon from design! + ) + } } } } } @Composable -private fun PayoutMethodPillow(provider: MemberPaymentProvider) { +private fun PayoutMethodPillow( + provider: MemberPaymentProvider, + isLocked: Boolean = false) { val onDarkTile = provider == MemberPaymentProvider.TRUSTLY - Surface( - shape = HedvigTheme.shapes.cornerSmall, - color = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, - contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, - modifier = Modifier.size(40.dp), - ) { - Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> Icon(HedvigIcons.Trustly, null, Modifier.size(28.dp)) - MemberPaymentProvider.SWISH -> Image(HedvigIcons.Swish, null, Modifier.size(28.dp)) - MemberPaymentProvider.INVOICE -> Image(HedvigIcons.Kivra, null, Modifier.size(28.dp)) - MemberPaymentProvider.NORDEA -> Icon( - HedvigIcons.Card, - null, - modifier = Modifier.size(28.dp), //todo: get icon from design! - ) - MemberPaymentProvider.UNKNOWN__ -> {} + Box { + Surface( + shape = HedvigTheme.shapes.cornerSmall, + color = when (isLocked) { + true -> if (onDarkTile) HedvigTheme.colorScheme.fillDisabled + else HedvigTheme.colorScheme.fillWhite + false -> { + if (onDarkTile) HedvigTheme.colorScheme.fillBlack + else HedvigTheme.colorScheme.fillWhite + } + }, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + modifier = Modifier.size(40.dp), + ) { + Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { + val alpha = if (isLocked) 0.5f else 1f + when (provider) { + MemberPaymentProvider.TRUSTLY -> { + Image( + HedvigIcons.Trustly, + null, + Modifier.size(28.dp), + alpha = alpha + ) + } + + MemberPaymentProvider.SWISH -> { + Image( + HedvigIcons.Swish, + null, + Modifier.size(28.dp), + alpha = alpha + ) + } + + MemberPaymentProvider.INVOICE -> { + Image( + HedvigIcons.Kivra, + null, + Modifier.size(28.dp), + alpha = alpha + ) + } + + MemberPaymentProvider.NORDEA -> { + Image( + HedvigIcons.Card, + null, + modifier = Modifier.size(28.dp), + alpha = alpha + // todo: get icon from design! + ) + } + + MemberPaymentProvider.UNKNOWN__ -> {} + } } } } @@ -188,15 +242,35 @@ private fun PreviewPayoutMethodRow( @PreviewParameter(BooleanCollectionPreviewParameterProvider::class) isLocked: Boolean, ) { HedvigTheme { - Surface(color = HedvigTheme.colorScheme.backgroundPrimary, - modifier =Modifier.padding(16.dp)) { - PayoutMethodRow( - provider = MemberPaymentProvider.SWISH, - title = stringResource(string.swish), - subtitle = "123456789", - onClick = {}, - isLocked = isLocked - ) + Surface( + color = HedvigTheme.colorScheme.backgroundPrimary, + modifier = Modifier.padding(16.dp), + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + PayoutMethodRow( + provider = MemberPaymentProvider.SWISH, + title = stringResource(string.swish), + subtitle = "123456789", + onClick = {}, + isLocked = isLocked, + ) + PayoutMethodRow( + provider = MemberPaymentProvider.NORDEA, + title = "Bank acc", + subtitle = "123456789", + onClick = {}, + isLocked = isLocked, + ) + PayoutMethodRow( + provider = MemberPaymentProvider.TRUSTLY, + title = "Trustly", + subtitle = "123456789", + onClick = {}, + isLocked = isLocked, + ) + } } } } From 2e4ed55c5c91d8c8f973e9ae60a06eaa558350b4 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 14 Sep 2026 16:48:39 +0200 Subject: [PATCH 75/75] change payout selection and success --- .../app/navigation/HedvigEntryProvider.kt | 1 - .../navigation/PayoutAccountEntries.kt | 9 +- .../PayoutMethodHandoverIllustration.kt | 60 ++++ .../ui/components/PayoutMethodRow.kt | 168 +++++++++ .../components/PayoutSetupSuccessContent.kt | 62 ++++ .../EditBankAccountDestination.kt | 140 ++++---- .../EditBankAccountViewModel.kt | 14 +- .../PayoutAccountOverviewDestination.kt | 20 +- .../SelectPayoutMethodDestination.kt | 322 ++++++------------ .../setupswish/SetupSwishPayoutDestination.kt | 131 ++++--- .../setupswish/SetupSwishPayoutViewModel.kt | 12 +- 11 files changed, 587 insertions(+), 352 deletions(-) create mode 100644 app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodHandoverIllustration.kt create mode 100644 app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodRow.kt create mode 100644 app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutSetupSuccessContent.kt diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index e84d6fb560..08cd31cd4e 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -490,7 +490,6 @@ private fun EntryProviderScope.addPaymentsEntries( ) payoutAccountEntries( backstack = backstack, - globalSnackBarState = globalSnackBarState, navigateToConnectPayment = navigateToConnectPayment, ) payinAccountEntries( diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt index 097984bf95..375bfd4cb8 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/navigation/PayoutAccountEntries.kt @@ -2,7 +2,6 @@ package com.hedvig.android.feature.payoutaccount.navigation import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigation3.runtime.EntryProviderScope -import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.feature.payoutaccount.ui.editbankaccount.EditBankAccountDestination import com.hedvig.android.feature.payoutaccount.ui.editbankaccount.EditBankAccountViewModel import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewDestination @@ -18,11 +17,7 @@ import com.hedvig.android.navigation.compose.popUpTo import dev.zacsweers.metrox.viewmodel.metroViewModel import octopus.type.MemberPaymentProvider -fun EntryProviderScope.payoutAccountEntries( - backstack: Backstack, - globalSnackBarState: GlobalSnackBarState, - navigateToConnectPayment: () -> Unit, -) { +fun EntryProviderScope.payoutAccountEntries(backstack: Backstack, navigateToConnectPayment: () -> Unit) { entry { val viewModel: PayoutAccountOverviewViewModel = metroViewModel() PayoutAccountOverviewDestination( @@ -60,7 +55,6 @@ fun EntryProviderScope.payoutAccountEntries( val viewModel: EditBankAccountViewModel = metroViewModel() EditBankAccountDestination( viewModel = viewModel, - globalSnackBarState = globalSnackBarState, navigateUp = backstack::navigateUp, ) } @@ -69,7 +63,6 @@ fun EntryProviderScope.payoutAccountEntries( val viewModel: SetupSwishPayoutViewModel = metroViewModel() SetupSwishPayoutDestination( viewModel = viewModel, - globalSnackBarState = globalSnackBarState, navigateUp = backstack::navigateUp, ) } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodHandoverIllustration.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodHandoverIllustration.kt new file mode 100644 index 0000000000..aeebd0d5c9 --- /dev/null +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodHandoverIllustration.kt @@ -0,0 +1,60 @@ +package com.hedvig.android.feature.payoutaccount.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigPreview +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.PaymentMethodHandoverIllustration +import com.hedvig.android.design.system.hedvig.PaymentMethodMarkSize +import com.hedvig.android.design.system.hedvig.Surface +import octopus.type.MemberPaymentProvider + +/** + * @param destinationBadge marks where the setup has got to once it has an outcome to show; absent + * while the member is still choosing. + */ +@Composable +internal fun PayoutMethodHandoverIllustration( + provider: MemberPaymentProvider?, + modifier: Modifier = Modifier, + destinationBadge: @Composable (() -> Unit)? = null, +) { + Column(modifier) { + Spacer(Modifier.height(48.dp)) + PaymentMethodHandoverIllustration( + destinationBadge = destinationBadge, + mark = { PayoutProviderMark(provider, Modifier.size(PaymentMethodMarkSize)) }, + ) + Spacer(Modifier.height(48.dp)) + } +} + +@Composable +@HedvigPreview +private fun PreviewPayoutMethodHandoverIllustration( + @PreviewParameter(PayoutPreviewProvider::class) provider: MemberPaymentProvider?, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + PayoutMethodHandoverIllustration(provider, Modifier.padding(16.dp)) + } + } +} + +private class PayoutPreviewProvider : + CollectionPreviewParameterProvider( + listOf( + null, + MemberPaymentProvider.TRUSTLY, + MemberPaymentProvider.SWISH, + MemberPaymentProvider.NORDEA, + ), + ) diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodRow.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodRow.kt new file mode 100644 index 0000000000..6c83d0e316 --- /dev/null +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutMethodRow.kt @@ -0,0 +1,168 @@ +package com.hedvig.android.feature.payoutaccount.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.EmptyContentDescription +import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigPreview +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.PaymentMethodPillow +import com.hedvig.android.design.system.hedvig.PaymentMethodPillowMarkSize +import com.hedvig.android.design.system.hedvig.PaymentMethodPlusMark +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.icon.Card +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.Lock +import com.hedvig.android.design.system.hedvig.icon.Trustly +import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import octopus.type.MemberPaymentProvider + +/** How far the brand mark fades on a locked row, where it reads as a label rather than an action. */ +private const val LockedMarkAlpha = 0.5f + +/** + * The connected payout method as the overview shows it: a lock instead of a chevron, because the + * method is changed from the flow behind "change payout method" rather than by tapping the row. + */ +@Composable +internal fun LockedPayoutMethodRow( + provider: MemberPaymentProvider, + title: String, + subtitle: String, + modifier: Modifier = Modifier, +) { + HedvigCard(modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .heightIn(min = 64.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + LockedPayoutProviderPillow(provider) + Spacer(Modifier.width(10.dp)) + Column(Modifier.padding(vertical = 8.dp)) { + HedvigText( + text = title, + color = HedvigTheme.colorScheme.textSecondaryTranslucent, + ) + HedvigText( + text = subtitle, + color = HedvigTheme.colorScheme.textDisabledTranslucent, + ) + } + Spacer(Modifier.weight(1f)) + Spacer(Modifier.width(4.dp)) + Icon( + HedvigIcons.Lock, + EmptyContentDescription, + tint = HedvigTheme.colorScheme.fillDisabledTransparent, + // todo: get icon from design! + modifier = Modifier.size(28.dp), + ) + } + } +} + +/** + * The provider's brand mark on the tile it is drawn on: white behind the full-colour marks, black + * behind the monochrome ones, which pick up the tile's content colour. + */ +@Composable +internal fun PayoutProviderPillow(provider: MemberPaymentProvider?, modifier: Modifier = Modifier) { + val onDarkTile = provider.onDarkTile + PaymentMethodPillow( + modifier = modifier, + containerColor = if (onDarkTile) HedvigTheme.colorScheme.fillBlack else HedvigTheme.colorScheme.fillWhite, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + mark = { PayoutProviderMark(provider, Modifier.size(PaymentMethodPillowMarkSize)) }, + ) +} + +@Composable +private fun LockedPayoutProviderPillow(provider: MemberPaymentProvider, modifier: Modifier = Modifier) { + val onDarkTile = provider.onDarkTile + PaymentMethodPillow( + modifier = modifier, + containerColor = if (onDarkTile) HedvigTheme.colorScheme.fillDisabled else HedvigTheme.colorScheme.fillWhite, + contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, + mark = { + PayoutProviderMark( + provider, + Modifier + .size(PaymentMethodPillowMarkSize) + .alpha(LockedMarkAlpha), + ) + }, + ) +} + +/** + * The provider's brand mark. The monochrome ones pick up the surrounding content colour; the + * full-colour ones ignore it. A provider with no mark of its own falls back to the plus, so one the + * backend adds later still renders. + */ +@Composable +internal fun PayoutProviderMark(provider: MemberPaymentProvider?, modifier: Modifier = Modifier) { + when (provider) { + MemberPaymentProvider.TRUSTLY -> Icon(HedvigIcons.Trustly, EmptyContentDescription, modifier) + + // todo: get icon from design! + MemberPaymentProvider.NORDEA -> Icon(HedvigIcons.Card, EmptyContentDescription, modifier) + + MemberPaymentProvider.SWISH -> Image(HedvigIcons.Swish, EmptyContentDescription, modifier) + + else -> PaymentMethodPlusMark(modifier) + } +} + +/** Trustly's mark is monochrome, so it reads better inverted on a dark tile than on the white one. */ +private val MemberPaymentProvider?.onDarkTile: Boolean + get() = this == MemberPaymentProvider.TRUSTLY + +@Composable +@HedvigPreview +private fun PreviewLockedPayoutMethodRow( + @PreviewParameter(PayoutProviderPreviewProvider::class) provider: MemberPaymentProvider, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + Column( + Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + LockedPayoutMethodRow( + provider = provider, + title = "Payout method", + subtitle = "123456789", + ) + PayoutProviderPillow(provider) + } + } + } +} + +private class PayoutProviderPreviewProvider : + CollectionPreviewParameterProvider( + listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.TRUSTLY, + MemberPaymentProvider.NORDEA, + ), + ) diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutSetupSuccessContent.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutSetupSuccessContent.kt new file mode 100644 index 0000000000..effc77956f --- /dev/null +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/components/PayoutSetupSuccessContent.kt @@ -0,0 +1,62 @@ +package com.hedvig.android.feature.payoutaccount.ui.components + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.PaymentMethodTileBadge +import com.hedvig.android.design.system.hedvig.a11y.FlowHeading +import com.hedvig.android.design.system.hedvig.icon.Checkmark +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import hedvig.resources.Res +import hedvig.resources.general_continue_button +import octopus.type.MemberPaymentProvider +import org.jetbrains.compose.resources.stringResource + +/** + * How every payout setup ends: the method has landed, so the handover illustration is badged with a + * checkmark and the only thing left to do is leave the flow. + */ +@Composable +internal fun ColumnScope.PayoutSetupSuccessContent( + provider: MemberPaymentProvider, + title: String, + onContinue: () -> Unit, +) { + Spacer(Modifier.height(8.dp)) + FlowHeading( + title = title, + description = null, + baseStyle = HedvigTheme.typography.bodySmall, + modifier = Modifier.padding(horizontal = 16.dp), + ) + Spacer(Modifier.weight(1f)) + PayoutMethodHandoverIllustration( + provider, + modifier = Modifier.align(Alignment.CenterHorizontally), + destinationBadge = { + PaymentMethodTileBadge( + icon = HedvigIcons.Checkmark, + containerColor = HedvigTheme.colorScheme.signalGreenElement, + contentColor = HedvigTheme.colorScheme.fillWhite, + ) + }, + ) + Spacer(Modifier.weight(1f)) + HedvigButton( + text = stringResource(Res.string.general_continue_button), + onClick = onContinue, + enabled = true, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) +} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountDestination.kt index ec456c6384..dc38788ef8 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountDestination.kt @@ -1,50 +1,48 @@ package com.hedvig.android.feature.payoutaccount.ui.editbankaccount -import android.R.attr.priority import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigScaffold -import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigTextField import com.hedvig.android.design.system.hedvig.HedvigTextFieldDefaults +import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.feature.payoutaccount.ui.components.PayoutSetupSuccessContent import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE import hedvig.resources.PAYMENTS_ACCOUNT import hedvig.resources.Res import hedvig.resources.general_save_button import hedvig.resources.something_went_wrong +import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @Composable -internal fun EditBankAccountDestination( - viewModel: EditBankAccountViewModel, - globalSnackBarState: GlobalSnackBarState, - navigateUp: () -> Unit, -) { +internal fun EditBankAccountDestination(viewModel: EditBankAccountViewModel, navigateUp: () -> Unit) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() EditBankAccountScreen( uiState = uiState, - globalSnackBarState = globalSnackBarState, onSave = { viewModel.emit(EditBankAccountEvent.Save) }, - showedSnackBar = { viewModel.emit(EditBankAccountEvent.ShowedSnackBar) }, + onFinishSetup = { viewModel.emit(EditBankAccountEvent.FinishSetup) }, navigateUp = navigateUp, ) } @@ -52,63 +50,91 @@ internal fun EditBankAccountDestination( @Composable private fun EditBankAccountScreen( uiState: EditBankAccountUiState, - globalSnackBarState: GlobalSnackBarState, onSave: () -> Unit, - showedSnackBar: () -> Unit, + onFinishSetup: () -> Unit, navigateUp: () -> Unit, ) { - LaunchedEffect(uiState.showSuccessSnackBar) { - if (!uiState.showSuccessSnackBar) return@LaunchedEffect - globalSnackBarState.show("Changes saved", NotificationPriority.Campaign) - showedSnackBar() - } - HedvigScaffold( topAppBarText = stringResource(Res.string.BANK_PAYOUT_METHOD_CARD_TITLE), navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { - Spacer(Modifier.weight(1f)) - Column(Modifier.padding(horizontal = 16.dp)) { - HedvigTextField( - state = uiState.accountNumberState, - labelText = buildString { - append(stringResource(Res.string.PAYMENTS_ACCOUNT)) - if (uiState.bankName != null) { - append(" - ") - append(uiState.bankName) - } - }, - textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, - inputTransformation = uiState.accountNumberInputTransformation, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - modifier = Modifier.fillMaxWidth(), - ) - } - AnimatedVisibility( - visible = uiState.errorMessage != null, - enter = expandVertically(), - exit = shrinkVertically(), - ) { - HedvigNotificationCard( - message = uiState.errorMessage?.ifBlank { stringResource(Res.string.something_went_wrong) }.orEmpty(), - priority = NotificationPriority.Attention, - modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 4.dp) - .fillMaxWidth(), + if (uiState.isConnected) { + PayoutSetupSuccessContent( + provider = MemberPaymentProvider.NORDEA, + // TODO: Add "Bank account is connected" / "Bankkontot är anslutet" to Lokalise + title = "Bank account is connected", + onContinue = onFinishSetup, ) + } else { + AccountNumberContent(uiState = uiState, onSave = onSave) } - Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(Res.string.general_save_button), - onClick = onSave, - enabled = uiState.canSave, - isLoading = uiState.isLoading, + } +} + +@Composable +private fun ColumnScope.AccountNumberContent(uiState: EditBankAccountUiState, onSave: () -> Unit) { + Spacer(Modifier.weight(1f)) + Column(Modifier.padding(horizontal = 16.dp)) { + HedvigTextField( + state = uiState.accountNumberState, + labelText = buildString { + append(stringResource(Res.string.PAYMENTS_ACCOUNT)) + if (uiState.bankName != null) { + append(" - ") + append(uiState.bankName) + } + }, + textFieldSize = HedvigTextFieldDefaults.TextFieldSize.Medium, + inputTransformation = uiState.accountNumberInputTransformation, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + modifier = Modifier.fillMaxWidth(), + ) + } + AnimatedVisibility( + visible = uiState.errorMessage != null, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + HedvigNotificationCard( + message = uiState.errorMessage?.ifBlank { stringResource(Res.string.something_went_wrong) }.orEmpty(), + priority = NotificationPriority.Attention, modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .padding(top = 4.dp) + .fillMaxWidth(), ) - Spacer(Modifier.height(16.dp)) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_save_button), + onClick = onSave, + enabled = uiState.canSave, + isLoading = uiState.isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewEditBankAccountScreenConnected() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + EditBankAccountScreen( + uiState = EditBankAccountUiState( + accountNumberState = TextFieldState(), + bankName = "Swedbank", + isLoading = false, + errorMessage = null, + isConnected = true, + ), + onSave = {}, + onFinishSetup = {}, + navigateUp = {}, + ) + } } } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountViewModel.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountViewModel.kt index ed997c4cdc..b9fb69463b 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountViewModel.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/editbankaccount/EditBankAccountViewModel.kt @@ -37,7 +37,7 @@ internal class EditBankAccountViewModel( bankName = null, isLoading = false, errorMessage = null, - showSuccessSnackBar = false, + isConnected = false, ), EditBankAccountPresenter(setupNordeaPayoutUseCase, backstack), ) @@ -45,7 +45,7 @@ internal class EditBankAccountViewModel( internal sealed interface EditBankAccountEvent { data object Save : EditBankAccountEvent - data object ShowedSnackBar : EditBankAccountEvent + data object FinishSetup : EditBankAccountEvent } internal data class EditBankAccountUiState( @@ -53,7 +53,7 @@ internal data class EditBankAccountUiState( val bankName: String?, val isLoading: Boolean, val errorMessage: String?, - val showSuccessSnackBar: Boolean, + val isConnected: Boolean, ) { val canSave: Boolean get() = !isLoading && accountNumberState.text.length in 10..17 @@ -74,7 +74,7 @@ internal class EditBankAccountPresenter( val bankName = bankNameForClearingNumber(accountNumberState.text.toString().take(4)) var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } - var showSuccessSnackBar by remember { mutableStateOf(false) } + var isConnected by remember { mutableStateOf(false) } var saveIteration by remember { mutableStateOf(null) } val currentSave = saveIteration @@ -90,7 +90,7 @@ internal class EditBankAccountPresenter( }, ifRight = { isLoading = false - showSuccessSnackBar = true + isConnected = true saveIteration = null }, ) @@ -111,7 +111,7 @@ internal class EditBankAccountPresenter( } } - EditBankAccountEvent.ShowedSnackBar -> { + EditBankAccountEvent.FinishSetup -> { backstack.popUpTo(inclusive = true) } } @@ -122,7 +122,7 @@ internal class EditBankAccountPresenter( bankName = bankName, isLoading = isLoading, errorMessage = errorMessage, - showSuccessSnackBar = showSuccessSnackBar, + isConnected = isConnected, ) } } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt index 7733d6b69c..f00f31e10b 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/overview/PayoutAccountOverviewDestination.kt @@ -37,12 +37,12 @@ import com.hedvig.android.feature.payoutaccount.data.PayoutAccount import com.hedvig.android.feature.payoutaccount.data.PayoutAccount.BankAccount import com.hedvig.android.feature.payoutaccount.data.PayoutAccount.SwishPayout import com.hedvig.android.feature.payoutaccount.data.PayoutAccount.Trustly +import com.hedvig.android.feature.payoutaccount.ui.components.LockedPayoutMethodRow import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewEvent.Retry import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Content import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Error import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.Loading import com.hedvig.android.feature.payoutaccount.ui.overview.PayoutAccountOverviewUiState.NoPayoutOptions -import com.hedvig.android.feature.payoutaccount.ui.selectmethod.PayoutMethodRow import hedvig.resources.CHANGE_PAYOUT_METHOD_BUTTON_LABEL import hedvig.resources.MY_PAYMENT_UPDATING_MESSAGE import hedvig.resources.PAYMENTS_ACCOUNT @@ -155,49 +155,43 @@ private fun PayoutAccountContent( is SwishPayout -> { val phoneNumber = currentMethod.phoneNumber.orEmpty() - PayoutMethodRow( - provider = MemberPaymentProvider.SWISH, + LockedPayoutMethodRow( + provider = MemberPaymentProvider.SWISH, title = stringResource(string.swish), subtitle = if (currentMethod.isPending && phoneNumber.isBlank()) { stringResource(string.REFERRAL_PENDING_STATUS_LABEL) } else { phoneNumber }, - onClick = {}, modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - isLocked = true ) } is Trustly -> { val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayoutMethodRow( + LockedPayoutMethodRow( title = formatBankAccountLabel(stringResource(string.trustly), currentMethod.bankName), subtitle = if (currentMethod.isPending && accountNumber.isBlank()) { stringResource(string.REFERRAL_PENDING_STATUS_LABEL) } else { accountNumber }, - onClick = {}, modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - isLocked = true, - provider = MemberPaymentProvider.TRUSTLY + provider = MemberPaymentProvider.TRUSTLY, ) } is BankAccount -> { val accountNumber = formatBankAccountNumber(currentMethod.clearingNumber, currentMethod.accountNumber) - PayoutMethodRow( + LockedPayoutMethodRow( title = formatBankAccountLabel(stringResource(string.PAYMENTS_ACCOUNT), currentMethod.bankName), subtitle = if (currentMethod.isPending && accountNumber.isBlank()) { stringResource(string.REFERRAL_PENDING_STATUS_LABEL) } else { accountNumber }, - onClick = {}, modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - isLocked = true, - provider = MemberPaymentProvider.NORDEA + provider = MemberPaymentProvider.NORDEA, ) } } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt index 37305ffba7..3e6fbf7fdb 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/selectmethod/SelectPayoutMethodDestination.kt @@ -1,43 +1,38 @@ package com.hedvig.android.feature.payoutaccount.ui.selectmethod -import androidx.compose.foundation.Image -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.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider -import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview -import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.RadioGroup +import com.hedvig.android.design.system.hedvig.RadioOption +import com.hedvig.android.design.system.hedvig.RadioOptionId import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.icon.Card -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.Lock -import com.hedvig.android.design.system.hedvig.icon.Trustly -import com.hedvig.android.design.system.hedvig.icon.colored.Kivra -import com.hedvig.android.design.system.hedvig.icon.colored.Swish +import com.hedvig.android.design.system.hedvig.a11y.FlowHeading +import com.hedvig.android.feature.payoutaccount.ui.components.PayoutMethodHandoverIllustration +import com.hedvig.android.feature.payoutaccount.ui.components.PayoutProviderPillow import hedvig.resources.BANK_PAYOUT_METHOD_CARD_DESCRIPTION import hedvig.resources.BANK_PAYOUT_METHOD_CARD_TITLE import hedvig.resources.PAYOUT_METHOD_SWISH_DESCRIPTION import hedvig.resources.PAYOUT_METHOD_TRUSTLY_DESCRIPTION import hedvig.resources.PAYOUT_SELECT_PAYOUT_METHOD import hedvig.resources.Res.string +import hedvig.resources.general_cancel_button +import hedvig.resources.general_continue_button import hedvig.resources.swish import hedvig.resources.trustly import octopus.type.MemberPaymentProvider @@ -51,168 +46,116 @@ internal fun SelectPayoutMethodDestination( onSwishSelected: () -> Unit, navigateUp: () -> Unit, ) { - HedvigScaffold( - topAppBarText = stringResource(string.PAYOUT_SELECT_PAYOUT_METHOD), - navigateUp = navigateUp, - modifier = Modifier.fillMaxSize(), - ) { - Spacer(Modifier.height(8.dp)) - Column(Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - for (provider in availableProviders) { - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - PayoutMethodRow( - title = stringResource(string.trustly), - subtitle = stringResource(string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), - onClick = onTrustlySelected, - provider = provider, - ) - } - - MemberPaymentProvider.NORDEA -> { - PayoutMethodRow( - title = stringResource(string.BANK_PAYOUT_METHOD_CARD_TITLE), - subtitle = stringResource(string.BANK_PAYOUT_METHOD_CARD_DESCRIPTION), - onClick = onNordeaSelected, - provider = provider, - ) - } + // Held as the raw value so the choice survives process death without a saver of its own. + var selectedRawValue by rememberSaveable { mutableStateOf(null) } + val selectedProvider = selectedRawValue?.let { MemberPaymentProvider.safeValueOf(it) } + SelectPayoutMethodScreen( + availableProviders = availableProviders, + selectedProvider = selectedProvider, + onProviderSelected = { selectedRawValue = it.rawValue }, + onSubmitSelected = { + when (selectedProvider) { + MemberPaymentProvider.TRUSTLY -> { + onTrustlySelected() + } - MemberPaymentProvider.SWISH -> { - PayoutMethodRow( - title = stringResource(string.swish), - subtitle = stringResource(string.PAYOUT_METHOD_SWISH_DESCRIPTION), - onClick = onSwishSelected, - provider = provider, - ) - } + MemberPaymentProvider.NORDEA -> { + onNordeaSelected() + } - else -> {} + MemberPaymentProvider.SWISH -> { + onSwishSelected() } + + else -> {} } - } - Spacer(Modifier.height(16.dp)) - } + }, + navigateUp = navigateUp, + ) } @Composable -internal fun PayoutMethodRow( - provider: MemberPaymentProvider, - title: String, - subtitle: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isLocked: Boolean = false, +private fun SelectPayoutMethodScreen( + availableProviders: List, + selectedProvider: MemberPaymentProvider?, + onProviderSelected: (MemberPaymentProvider) -> Unit, + onSubmitSelected: () -> Unit, + navigateUp: () -> Unit, ) { - Box { - HedvigCard( - onClick = if (isLocked) null else onClick, - modifier = modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier - .heightIn(min = 64.dp) - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - PayoutMethodPillow(provider, isLocked) - Spacer(Modifier.width(10.dp)) - Column(Modifier.padding(vertical = 8.dp)) { - HedvigText( - text = title, - color = if (!isLocked) { - HedvigTheme.colorScheme.textPrimary - } else { - HedvigTheme.colorScheme.textSecondaryTranslucent - }, - ) - HedvigText( - text = subtitle, - color = if (!isLocked) { - HedvigTheme.colorScheme.textSecondary - } else { - HedvigTheme.colorScheme.textDisabledTranslucent - }, - ) - } - Spacer(Modifier.weight(1f)) - Spacer(Modifier.width(4.dp)) - if (isLocked) { - Icon( - HedvigIcons.Lock, - null, - tint = HedvigTheme.colorScheme.fillDisabledTransparent, - modifier = Modifier.size(28.dp), // todo: get icon from design! - ) - } - } - } + HedvigScaffold( + topAppBarText = null, + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + Spacer(Modifier.height(8.dp)) + FlowHeading( + title = stringResource(string.PAYOUT_SELECT_PAYOUT_METHOD), + description = null, + baseStyle = HedvigTheme.typography.bodySmall, + modifier = Modifier.padding(horizontal = 16.dp), + ) + Spacer(Modifier.weight(1f)) + PayoutMethodHandoverIllustration( + selectedProvider, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + Spacer(Modifier.weight(1f)) + RadioGroup( + options = availableProviders.mapNotNull { it.toRadioOption() }, + selectedOption = selectedProvider?.let { RadioOptionId(it.rawValue) }, + onRadioOptionSelected = { onProviderSelected(MemberPaymentProvider.safeValueOf(it.id)) }, + optionIcon = { PayoutProviderPillow(MemberPaymentProvider.safeValueOf(it.id)) }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) + HedvigButton( + onClick = onSubmitSelected, + enabled = selectedProvider != null, + text = stringResource(string.general_continue_button), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + onClick = navigateUp, + enabled = true, + text = stringResource(string.general_cancel_button), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) } } +/** + * Null for providers this screen has no option for, so a value the backend adds later is left out + * of the group. + */ @Composable -private fun PayoutMethodPillow( - provider: MemberPaymentProvider, - isLocked: Boolean = false) { - val onDarkTile = provider == MemberPaymentProvider.TRUSTLY - Box { - Surface( - shape = HedvigTheme.shapes.cornerSmall, - color = when (isLocked) { - true -> if (onDarkTile) HedvigTheme.colorScheme.fillDisabled - else HedvigTheme.colorScheme.fillWhite - false -> { - if (onDarkTile) HedvigTheme.colorScheme.fillBlack - else HedvigTheme.colorScheme.fillWhite - } - }, - contentColor = if (onDarkTile) HedvigTheme.colorScheme.fillWhite else HedvigTheme.colorScheme.fillBlack, - modifier = Modifier.size(40.dp), - ) { - Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { - val alpha = if (isLocked) 0.5f else 1f - when (provider) { - MemberPaymentProvider.TRUSTLY -> { - Image( - HedvigIcons.Trustly, - null, - Modifier.size(28.dp), - alpha = alpha - ) - } - - MemberPaymentProvider.SWISH -> { - Image( - HedvigIcons.Swish, - null, - Modifier.size(28.dp), - alpha = alpha - ) - } +private fun MemberPaymentProvider.toRadioOption(): RadioOption? { + val id = RadioOptionId(rawValue) + return when (this) { + MemberPaymentProvider.TRUSTLY -> RadioOption( + id = id, + text = stringResource(string.trustly), + label = stringResource(string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), + ) - MemberPaymentProvider.INVOICE -> { - Image( - HedvigIcons.Kivra, - null, - Modifier.size(28.dp), - alpha = alpha - ) - } + MemberPaymentProvider.NORDEA -> RadioOption( + id = id, + text = stringResource(string.BANK_PAYOUT_METHOD_CARD_TITLE), + label = stringResource(string.BANK_PAYOUT_METHOD_CARD_DESCRIPTION), + ) - MemberPaymentProvider.NORDEA -> { - Image( - HedvigIcons.Card, - null, - modifier = Modifier.size(28.dp), - alpha = alpha - // todo: get icon from design! - ) - } + MemberPaymentProvider.SWISH -> RadioOption( + id = id, + text = stringResource(string.swish), + label = stringResource(string.PAYOUT_METHOD_SWISH_DESCRIPTION), + ) - MemberPaymentProvider.UNKNOWN__ -> {} - } - } - } + else -> null } } @@ -221,56 +164,17 @@ private fun PayoutMethodPillow( private fun PreviewSelectPayoutMethodScreen() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - SelectPayoutMethodDestination( + SelectPayoutMethodScreen( availableProviders = listOf( MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY, MemberPaymentProvider.NORDEA, ), - onTrustlySelected = {}, - onNordeaSelected = {}, - onSwishSelected = {}, + selectedProvider = MemberPaymentProvider.SWISH, + onProviderSelected = {}, + onSubmitSelected = {}, navigateUp = {}, ) } } } - -@Composable -@HedvigShortMultiScreenPreview -private fun PreviewPayoutMethodRow( - @PreviewParameter(BooleanCollectionPreviewParameterProvider::class) isLocked: Boolean, -) { - HedvigTheme { - Surface( - color = HedvigTheme.colorScheme.backgroundPrimary, - modifier = Modifier.padding(16.dp), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - PayoutMethodRow( - provider = MemberPaymentProvider.SWISH, - title = stringResource(string.swish), - subtitle = "123456789", - onClick = {}, - isLocked = isLocked, - ) - PayoutMethodRow( - provider = MemberPaymentProvider.NORDEA, - title = "Bank acc", - subtitle = "123456789", - onClick = {}, - isLocked = isLocked, - ) - PayoutMethodRow( - provider = MemberPaymentProvider.TRUSTLY, - title = "Trustly", - subtitle = "123456789", - onClick = {}, - isLocked = isLocked, - ) - } - } - } -} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutDestination.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutDestination.kt index 04b937db48..0a7d07c257 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutDestination.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutDestination.kt @@ -4,44 +4,46 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider import com.hedvig.android.core.common.validation.PhoneNumberRules -import com.hedvig.android.design.system.hedvig.GlobalSnackBarState import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigScaffold +import com.hedvig.android.design.system.hedvig.HedvigShortMultiScreenPreview +import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.feature.payoutaccount.ui.components.PayoutSetupSuccessContent import com.hedvig.android.ui.phonenumber.HedvigPhoneNumberField -import hedvig.resources.CONTACT_INFO_CHANGES_SAVED import hedvig.resources.ODYSSEY_PHONE_NUMBER_LABEL +import hedvig.resources.PAYMENT_SWISH_SUCCESS_TITLE import hedvig.resources.Res import hedvig.resources.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION import hedvig.resources.general_save_button import hedvig.resources.swish +import octopus.type.MemberPaymentProvider import org.jetbrains.compose.resources.stringResource @Composable -internal fun SetupSwishPayoutDestination( - viewModel: SetupSwishPayoutViewModel, - globalSnackBarState: GlobalSnackBarState, - navigateUp: () -> Unit, -) { +internal fun SetupSwishPayoutDestination(viewModel: SetupSwishPayoutViewModel, navigateUp: () -> Unit) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() SetupSwishPayoutScreen( uiState = uiState, - globalSnackBarState = globalSnackBarState, onSave = { viewModel.emit(SetupSwishPayoutEvent.Save) }, - showedSnackBar = { viewModel.emit(SetupSwishPayoutEvent.ShowedSnackBar) }, + onFinishSetup = { viewModel.emit(SetupSwishPayoutEvent.FinishSetup) }, navigateUp = navigateUp, ) } @@ -49,58 +51,85 @@ internal fun SetupSwishPayoutDestination( @Composable private fun SetupSwishPayoutScreen( uiState: SetupSwishPayoutUiState, - globalSnackBarState: GlobalSnackBarState, onSave: () -> Unit, - showedSnackBar: () -> Unit, + onFinishSetup: () -> Unit, navigateUp: () -> Unit, ) { - val changesSaved = stringResource(Res.string.CONTACT_INFO_CHANGES_SAVED) - LaunchedEffect(uiState.showSuccessSnackBar) { - if (!uiState.showSuccessSnackBar) return@LaunchedEffect - globalSnackBarState.show(changesSaved, NotificationPriority.Campaign) - showedSnackBar() - } - HedvigScaffold( topAppBarText = stringResource(Res.string.swish), navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { - Spacer(Modifier.weight(1f)) - Column(Modifier.padding(horizontal = 16.dp)) { - HedvigPhoneNumberField( - state = uiState.phoneNumberState, - labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), - rules = PhoneNumberRules.SwishPhoneNumber, - modifier = Modifier.fillMaxWidth(), - ) - } - AnimatedVisibility( - visible = uiState.errorMessage != null, - enter = expandVertically(), - exit = shrinkVertically(), - ) { - HedvigNotificationCard( - message = uiState.errorMessage?.message - ?: stringResource(Res.string.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION), - priority = NotificationPriority.Attention, - modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 4.dp) - .fillMaxWidth(), + if (uiState.isConnected) { + PayoutSetupSuccessContent( + provider = MemberPaymentProvider.SWISH, + title = stringResource(Res.string.PAYMENT_SWISH_SUCCESS_TITLE), + onContinue = onFinishSetup, ) + } else { + PhoneNumberContent(uiState = uiState, onSave = onSave) } - Spacer(Modifier.height(16.dp)) - HedvigButton( - text = stringResource(Res.string.general_save_button), - onClick = onSave, - enabled = !uiState.isLoading && - PhoneNumberRules.SwishPhoneNumber.hasEnoughDigits(uiState.phoneNumberState.text), - isLoading = uiState.isLoading, + } +} + +@Composable +private fun ColumnScope.PhoneNumberContent(uiState: SetupSwishPayoutUiState, onSave: () -> Unit) { + Spacer(Modifier.weight(1f)) + Column(Modifier.padding(horizontal = 16.dp)) { + HedvigPhoneNumberField( + state = uiState.phoneNumberState, + labelText = stringResource(Res.string.ODYSSEY_PHONE_NUMBER_LABEL), + rules = PhoneNumberRules.SwishPhoneNumber, + modifier = Modifier.fillMaxWidth(), + ) + } + AnimatedVisibility( + visible = uiState.errorMessage != null, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + HedvigNotificationCard( + message = uiState.errorMessage?.message + ?: stringResource(Res.string.TIER_FLOW_COMMIT_PROCESSING_ERROR_DESCRIPTION), + priority = NotificationPriority.Attention, modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .padding(top = 4.dp) + .fillMaxWidth(), ) - Spacer(Modifier.height(16.dp)) + } + Spacer(Modifier.height(16.dp)) + HedvigButton( + text = stringResource(Res.string.general_save_button), + onClick = onSave, + enabled = !uiState.isLoading && + PhoneNumberRules.SwishPhoneNumber.hasEnoughDigits(uiState.phoneNumberState.text), + isLoading = uiState.isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(Modifier.height(16.dp)) +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewSetupSwishPayoutScreenConnected( + @PreviewParameter(BooleanCollectionPreviewParameterProvider::class) isConnected: Boolean, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SetupSwishPayoutScreen( + uiState = SetupSwishPayoutUiState( + phoneNumberState = TextFieldState(), + isLoading = false, + errorMessage = null, + isConnected = isConnected, + ), + onSave = {}, + onFinishSetup = {}, + navigateUp = {}, + ) + } } } diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutViewModel.kt b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutViewModel.kt index 468c1b05c9..2a32011f5e 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutViewModel.kt +++ b/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/ui/setupswish/SetupSwishPayoutViewModel.kt @@ -32,14 +32,14 @@ internal class SetupSwishPayoutViewModel( internal sealed interface SetupSwishPayoutEvent { data object Save : SetupSwishPayoutEvent - data object ShowedSnackBar : SetupSwishPayoutEvent + data object FinishSetup : SetupSwishPayoutEvent } internal data class SetupSwishPayoutUiState( val phoneNumberState: TextFieldState, val isLoading: Boolean, val errorMessage: ErrorMessage?, - val showSuccessSnackBar: Boolean, + val isConnected: Boolean, ) internal class SetupSwishPayoutPresenter( @@ -53,7 +53,7 @@ internal class SetupSwishPayoutPresenter( val phoneNumberState = remember { lastState.phoneNumberState } var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } - var showSuccessSnackBar by remember { mutableStateOf(false) } + var isConnected by remember { mutableStateOf(false) } var saveIteration by remember { mutableStateOf(null) } val currentSave = saveIteration @@ -69,7 +69,7 @@ internal class SetupSwishPayoutPresenter( }, ifRight = { isLoading = false - showSuccessSnackBar = true + isConnected = true saveIteration = null }, ) @@ -84,7 +84,7 @@ internal class SetupSwishPayoutPresenter( } } - SetupSwishPayoutEvent.ShowedSnackBar -> { + SetupSwishPayoutEvent.FinishSetup -> { backstack.popUpTo(inclusive = true) } } @@ -94,7 +94,7 @@ internal class SetupSwishPayoutPresenter( phoneNumberState = phoneNumberState, isLoading = isLoading, errorMessage = errorMessage, - showSuccessSnackBar = showSuccessSnackBar, + isConnected = isConnected, ) } }