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 8e3186f529..4520eacc18 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 @@ -175,10 +176,21 @@ 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) - else -> error("Non compliant server") + errors != null && data != null -> { + Ior.Both(errors, data) + } + + errors != null -> { + Ior.Left(errors) + } + + data != null -> { + Ior.Right(data) + } + + else -> { + error("Non compliant server") + } } } diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 7d179d4e3a..cdceddb206 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -212,6 +212,8 @@ dependencies { implementation(projects.featureOnboarding) 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 5fa917afbf..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 @@ -52,6 +52,8 @@ 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 import com.hedvig.android.feature.payoutaccount.navigation.payoutAccountEntries @@ -105,6 +107,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())) } @@ -136,7 +139,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( navigateToNewConversation = navigateToNewConversation, navigateToConversation = navigateToConversation, navigateToInbox = navigateToInbox, - navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, navigateToTravelCertificate = navigateToTravelCertificate, navigateToAddonPurchaseFlow = navigateToAddonPurchaseFlow, @@ -158,7 +161,9 @@ internal fun EntryProviderScope.hedvigEntryProvider( addPaymentsEntries( backstack = backstack, globalSnackBarState = globalSnackBarState, + openUrl = openUrl, navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, navigateToNewConversation = navigateToNewConversation, ) @@ -169,7 +174,7 @@ internal fun EntryProviderScope.hedvigEntryProvider( languageService = languageService, externalNavigator = externalNavigator, openUrl = openUrl, - navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, navigateToNewConversation = navigateToNewConversation, onResetOnboardingForDebug = { @@ -243,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, @@ -267,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 -> @@ -458,13 +463,16 @@ private fun EntryProviderScope.addInsuranceEntries( private fun EntryProviderScope.addPaymentsEntries( backstack: BackstackController, globalSnackBarState: GlobalSnackBarState, + openUrl: (String) -> Unit, navigateToConnectPayment: () -> Unit, + navigateToPayinAccount: () -> Unit, navigateToPayoutAccount: () -> Unit, navigateToNewConversation: () -> Unit, ) { paymentsEntries( backstack = backstack, navigateToConnectPayment = navigateToConnectPayment, + navigateToPayinAccount = navigateToPayinAccount, navigateToPayoutAccount = navigateToPayoutAccount, openConversation = navigateToNewConversation, ) @@ -473,6 +481,12 @@ private fun EntryProviderScope.addPaymentsEntries( globalSnackBarState = globalSnackBarState, navigateToConnectPayment = navigateToConnectPayment, ) + payinAccountEntries( + backstack = backstack, + globalSnackBarState = globalSnackBarState, + navigateToConnectPayment = navigateToConnectPayment, + openUrl = openUrl, + ) connectPaymentEntries(backstack = backstack) } @@ -483,7 +497,7 @@ private fun EntryProviderScope.addProfileEntries( languageService: LanguageService, externalNavigator: ExternalNavigator, openUrl: (String) -> Unit, - navigateToConnectPayment: () -> Unit, + navigateToPayinAccount: () -> Unit, navigateToPayoutAccount: () -> Unit, navigateToNewConversation: () -> Unit, onResetOnboardingForDebug: () -> Unit, @@ -501,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/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 fc0fe2599a..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,6 +263,7 @@ fun HedvigButtonGhostWithBorder( enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, size: ButtonSize = ButtonSize.Medium, + isLoading: Boolean = false, ) { HedvigTextButton( text = text, @@ -275,6 +276,7 @@ fun HedvigButtonGhostWithBorder( ), buttonSize = size, interactionSource = interactionSource, + 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 ffa183ce3b..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 @@ -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)) + } } } @@ -162,6 +173,8 @@ object EmptyStateDefaults { BANK_ID, NO_ICON, 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/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..c46edc448f --- /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/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..1a9a3ee2d2 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/BankAccount.kt @@ -0,0 +1,117 @@ +package com.hedvig.android.design.system.hedvig.icon + +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 + +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/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..6cc8993f9e --- /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,332 @@ +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/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..3c77566c1c --- /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,392 @@ +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-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-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/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 } 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) } } 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 new file mode 100644 index 0000000000..e4ca94d73d --- /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() + navKeys() + viewModels() +} + +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(projects.composeUi) + implementation(projects.coreCommonPublic) + implementation(projects.coreResources) + implementation(projects.designSystemHedvig) + implementation(projects.featurePayinAccountNavigation) + 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/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-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/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-payout-account/src/main/graphql/SetupInvoicePayout.graphql b/app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql similarity index 73% rename from app/feature/feature-payout-account/src/main/graphql/SetupInvoicePayout.graphql rename to app/feature/feature-payin-account/src/main/graphql/SetupInvoicePayin.graphql index dd61ac8dcf..cb935fbfd1 100644 --- a/app/feature/feature-payout-account/src/main/graphql/SetupInvoicePayout.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/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/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..bc72885d5c --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/GetPayinAccountUseCase.kt @@ -0,0 +1,151 @@ +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.safeExecute +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.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, + val availablePayinMethods: List, +) + +@SingleIn(AppScope::class) +@Inject +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 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, + email = invoiceDetails?.email, + isPending = isPending, + isDefault = isDefault, + ) + } + + else -> { + null + } + } + } + + val availablePayinMethods = paymentMethods.availableMethods + .filter { it.supportsPayin } + .map { it.provider } + + PayinAccountData( + currentMethods = currentMethods, + availablePayinMethods = availablePayinMethods, + ) + } +} + +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 + 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 +} + +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..15a7f6c2e1 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetAsDefaultUseCase.kt @@ -0,0 +1,56 @@ +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.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 + +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, +) : SetAsDefaultUseCase { + override suspend fun invoke(provider: MemberPaymentProvider): Either { + return either { + apolloClient + .mutation(SetAsDefaultPayinMutation(provider)) + .safeExecuteAllowingPartialResponses() + .fold( + fa = { error -> + logcat { "SetAsDefaultUseCaseImpl error: $error" } + raise(ErrorMessage()) + }, + fb = { result -> + val userError = result.paymentMethodSetDefaultPayin?.message + if (userError != null) { + logcat { "SetAsDefaultUseCaseImpl userError not null: $userError" } + raise(ErrorMessage(userError)) + } + getPayinAccountUseCase.invoke().bind() + }, + fab = { errors, _ -> + logcat { "SetAsDefaultUseCaseImpl data with errors: $errors" } + raise(ErrorMessage()) + }, + ) + } + } +} diff --git a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt similarity index 79% rename from app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt rename to app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt index 6d769eba62..c9b03eb91e 100644 --- a/app/feature/feature-payout-account/src/main/kotlin/com/hedvig/android/feature/payoutaccount/data/SetupInvoicePayoutUseCase.kt +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupInvoicePayinUseCase.kt @@ -1,4 +1,4 @@ -package com.hedvig.android.feature.payoutaccount.data +package com.hedvig.android.feature.payin.account.data import arrow.core.Either import arrow.core.raise.either @@ -10,26 +10,25 @@ 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, ) { 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/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..d5f114f4c8 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/data/SetupSwishPayinUseCase.kt @@ -0,0 +1,82 @@ +package com.hedvig.android.feature.payin.account.data + +import arrow.core.Either +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.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 + +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, +) : SetupSwishPayinUseCase { + override suspend fun invoke(phoneNumber: String): Either = either { + apolloClient + .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) + } + + 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)) + } + } + }, + fab = { errors, _ -> + logcat { "SetupSwishPayinMutation dataa with errors: $errors" } + raise(ErrorMessage()) + }, + ) + } +} + +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/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/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..50956dfa1f --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/navigation/PayinAccountDestination.kt @@ -0,0 +1,15 @@ +package com.hedvig.android.feature.payin.account.navigation + +import com.hedvig.android.navigation.common.HedvigNavKey +import kotlinx.serialization.Serializable + +@Serializable +internal data class SelectPayinMethodKey( + val availableProviders: 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 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/ui/overview/PayinAccountOverviewDestination.kt b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt new file mode 100644 index 0000000000..91b5eb42e5 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewDestination.kt @@ -0,0 +1,483 @@ +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 +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.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 +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.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 +internal fun PayinAccountOverviewDestination( + viewModel: PayinAccountOverviewViewModel, + onConnectPayoutMethodClicked: () -> Unit, + navigateUp: () -> Unit, +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + PayinAccountOverviewScreen( + uiState = uiState, + onConnectPayoutMethodClicked = onConnectPayoutMethodClicked, + onRetry = { viewModel.emit(PayinAccountOverviewEvent.Retry) }, + navigateUp = navigateUp, + setAsDefaultPayinMethod = { + viewModel.emit(PayinAccountOverviewEvent.SetDefaultMethod(it)) + }, + ) +} + +@Composable +private fun PayinAccountOverviewScreen( + uiState: PayinAccountOverviewUiState, + onConnectPayoutMethodClicked: () -> Unit, + onRetry: () -> Unit, + navigateUp: () -> Unit, + setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, +) { + HedvigScaffold( + topAppBarText = "Payment account", // todo! + navigateUp = navigateUp, + modifier = Modifier.fillMaxSize(), + ) { + when (uiState) { + PayinAccountOverviewUiState.Loading -> { + HedvigFullScreenCenterAlignedProgressDebounced( + Modifier + .weight(1f) + .wrapContentHeight(), + ) + } + + PayinAccountOverviewUiState.Error -> { + HedvigErrorSection( + onButtonClick = onRetry, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .weight(1f) + .wrapContentHeight(), + ) + } + + is PayinAccountOverviewUiState.Content -> { + PayoutAccountContent( + currentMethods = uiState.currentMethods, + availablePayinMethods = uiState.availablePayinMethods, + onConnectPayinMethodClicked = onConnectPayoutMethodClicked, + modifier = Modifier.weight(1f), + setAsDefaultPayinMethod = setAsDefaultPayinMethod, + loadingDefaultProvider = uiState.loadingDefaultProvider, + setDefaultProviderError = uiState.setDefaultProviderError, + ) + } + } + } +} + +@Composable +private fun PayoutAccountContent( + currentMethods: List, + availablePayinMethods: List, + onConnectPayinMethodClicked: () -> Unit, + setAsDefaultPayinMethod: (MemberPaymentProvider) -> Unit, + loadingDefaultProvider: MemberPaymentProvider?, + setDefaultProviderError: ErrorMessage?, + modifier: Modifier = Modifier, +) { + Column(modifier + .verticalScroll(rememberScrollState())) { + Spacer(Modifier.height(8.dp)) + 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, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + } else { + HedvigText( + "Connected payment methods", // todo + modifier = Modifier.padding(horizontal = 16.dp), + ) + 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 { + 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, + ) + } + } + } + } + } + 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 { + 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! + onClick = onConnectPayinMethodClicked, + enabled = true, + 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, +) { + HedvigCard( + 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, + ) + } + } + }, + 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, + ) + } + } + } + }, + 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( + @PreviewParameter(PayinAccountOverviewUiStateProvider::class) uiState: PayinAccountOverviewUiState, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + PayinAccountOverviewScreen( + uiState = uiState, + onConnectPayoutMethodClicked = {}, + onRetry = {}, + navigateUp = {}, + {}, + ) + } + } +} + +private class PayinAccountOverviewUiStateProvider : CollectionPreviewParameterProvider( + listOf( + PayinAccountOverviewUiState.Loading, + PayinAccountOverviewUiState.Error, + PayinAccountOverviewUiState.Content( + currentMethods = emptyList(), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), + ), + PayinAccountOverviewUiState.Content( + currentMethods = listOf( + PayinAccount.SwishPayin( + phoneNumber = "0701234567", + isPending = false, + isDefault = true, + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH), + ), + PayinAccountOverviewUiState.Content( + currentMethods = listOf( + PayinAccount.SwishPayin( + phoneNumber = "0701234567", + isPending = false, + isDefault = true, + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH, MemberPaymentProvider.TRUSTLY), + ), + PayinAccountOverviewUiState.Content( + currentMethods = listOf(PayinAccount.SwishPayin(phoneNumber = null, isPending = true, isDefault = true)), + availablePayinMethods = listOf(MemberPaymentProvider.SWISH), + ), + PayinAccountOverviewUiState.Content( + currentMethods = listOf( + PayinAccount.Trustly( + clearingNumber = "****", + accountNumber = "*45678", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ), + PayinAccount.SwishPayin( + phoneNumber = "0701234567", + 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 = "0701234567", + isPending = false, + isDefault = false, + ), + PayinAccount.Invoice( + delivery = PaymentMethodInvoiceDelivery.KIVRA, + isPending = false, + isDefault = false, + email = "", + ), + ), + availablePayinMethods = listOf(MemberPaymentProvider.TRUSTLY), + ), + PayinAccountOverviewUiState.Content( + currentMethods = listOf( + PayinAccount.Trustly( + clearingNumber = "****", + accountNumber = "*45678", + bankName = "Swedbank", + isPending = false, + isDefault = true, + ), + PayinAccount.SwishPayin( + phoneNumber = "0701234567", + 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 new file mode 100644 index 0000000000..a07e8835eb --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/overview/PayinAccountOverviewViewModel.kt @@ -0,0 +1,113 @@ +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.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 +import dev.zacsweers.metro.Inject +import octopus.type.MemberPaymentProvider + +@Inject +@HedvigViewModel(ActivityRetainedScope::class) +internal class PayinAccountOverviewViewModel( + getPayinAccountUseCase: GetPayinAccountUseCase, + setAsDefaultUseCase: SetAsDefaultUseCase, +) : MoleculeViewModel( + PayinAccountOverviewUiState.Loading, + PayinAccountOverviewPresenter(getPayinAccountUseCase, setAsDefaultUseCase), + ) + +internal sealed interface PayinAccountOverviewEvent { + data object Retry : PayinAccountOverviewEvent + + data class SetDefaultMethod(val provider: MemberPaymentProvider) : PayinAccountOverviewEvent +} + +internal sealed interface PayinAccountOverviewUiState { + data object Loading : PayinAccountOverviewUiState + + data object Error : 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) { + uiState = PayinAccountOverviewUiState.Loading + getPayinAccountUseCase.invoke().fold( + ifLeft = { uiState = PayinAccountOverviewUiState.Error }, + ifRight = { data -> + uiState = PayinAccountOverviewUiState.Content( + currentMethods = data.currentMethods, + availablePayinMethods = data.availablePayinMethods, + ) + }, + ) + } + + 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 + } + } + } + + 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..fcc0d2fc62 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/selectmethod/SelectPayinMethodDestination.kt @@ -0,0 +1,179 @@ +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.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.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.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.Res +import hedvig.resources.swish +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 = "Add or change payment 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) { + 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) { + MemberPaymentProvider.TRUSTLY -> { + onTrustlySelected() + } + + MemberPaymentProvider.SWISH -> { + onSwishSelected() + } + + MemberPaymentProvider.INVOICE -> { + onInvoiceSelected() + } + + else -> {} + } + }, + modifier = modifier.fillMaxWidth(), + ) { + 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 -> "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.INVOICE -> stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION) + + else -> "" + }, + color = HedvigTheme.colorScheme.textSecondary, + ) + } + } + } +} + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewSelectPayinMethodScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SelectPayinMethodDestination( + availableProviders = listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.INVOICE, + MemberPaymentProvider.TRUSTLY, + ), + onTrustlySelected = {}, + onSwishSelected = {}, + onInvoiceSelected = {}, + navigateUp = {}, + ) + } + } +} 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 59% 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 6443ba3b0b..caeae878d6 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,8 +1,10 @@ -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 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 import androidx.compose.foundation.layout.fillMaxWidth @@ -11,38 +13,45 @@ 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 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 +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( - viewModel: SetupInvoicePayoutViewModel, +internal fun SetupInvoicePayinDestination( + viewModel: SetupInvoicePayinViewModel, globalSnackBarState: GlobalSnackBarState, navigateUp: () -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - SetupInvoicePayoutScreen( + 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, ) } @Composable -private fun SetupInvoicePayoutScreen( - uiState: SetupInvoicePayoutUiState, +private fun SetupInvoicePayinScreen( + uiState: SetupInvoicePayinUiState, globalSnackBarState: GlobalSnackBarState, onConnect: () -> Unit, showedSnackBar: () -> Unit, @@ -60,7 +69,21 @@ private fun SetupInvoicePayoutScreen( navigateUp = navigateUp, modifier = Modifier.fillMaxSize(), ) { - Spacer(Modifier.weight(1f)) + // todo: some text here?? + 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, enter = expandVertically(), @@ -77,7 +100,7 @@ private fun SetupInvoicePayoutScreen( } Spacer(Modifier.height(16.dp)) HedvigButton( - text = "Connect", + text = "Set invoice as billing method", // todo onClick = onConnect, enabled = !uiState.isLoading, isLoading = uiState.isLoading, @@ -88,3 +111,23 @@ private fun SetupInvoicePayoutScreen( Spacer(Modifier.height(16.dp)) } } + +@Composable +@HedvigShortMultiScreenPreview +private fun PreviewPayoutAccountOverviewScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SetupInvoicePayinScreen( + uiState = SetupInvoicePayinUiState( + false, + null, + showSuccessSnackBar = false, + ), + 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/SetupInvoicePayinViewModel.kt similarity index 61% 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/SetupInvoicePayinViewModel.kt index bd3830c236..17d91cbd26 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/SetupInvoicePayinViewModel.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 @@ -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.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 @@ -20,34 +20,34 @@ import dev.zacsweers.metro.Inject @Inject @HedvigViewModel(ActivityRetainedScope::class) -internal class SetupInvoicePayoutViewModel( - setupInvoicePayoutUseCase: SetupInvoicePayoutUseCase, +internal class SetupInvoicePayinViewModel( + setupInvoicePayinUseCase: SetupInvoicePayinUseCase, backstack: Backstack, -) : MoleculeViewModel( - SetupInvoicePayoutUiState(false, null, false), - SetupInvoicePayoutPresenter(setupInvoicePayoutUseCase, 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 SetupInvoicePayoutUiState( +internal data class SetupInvoicePayinUiState( val isLoading: Boolean, val errorMessage: String?, val showSuccessSnackBar: Boolean, ) -internal class SetupInvoicePayoutPresenter( - private val setupInvoicePayoutUseCase: SetupInvoicePayoutUseCase, +internal class SetupInvoicePayinPresenter( + private val setupInvoicePayinUseCase: SetupInvoicePayinUseCase, private val backstack: Backstack, -) : MoleculePresenter { +) : MoleculePresenter { @Composable - override fun MoleculePresenterScope.present( - lastState: SetupInvoicePayoutUiState, - ): SetupInvoicePayoutUiState { + override fun MoleculePresenterScope.present( + lastState: SetupInvoicePayinUiState, + ): SetupInvoicePayinUiState { var isLoading by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } var showSuccessSnackBar by remember { mutableStateOf(false) } @@ -58,7 +58,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" @@ -75,20 +75,20 @@ internal class SetupInvoicePayoutPresenter( 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) } } } - 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/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 new file mode 100644 index 0000000000..c4d276553f --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinDestination.kt @@ -0,0 +1,303 @@ +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 +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 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 +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 + +@Composable +internal fun SetupSwishPayinDestination( + viewModel: SetupSwishPayinViewModel, + globalSnackBarState: GlobalSnackBarState, + onSuccessfullyConnected: () -> Unit, + navigateUp: () -> Unit, + openUrl: (String) -> 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, + 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 SetupSwishPayoutScreen( + uiState: SetupSwishPayoutUiState, + globalSnackBarState: GlobalSnackBarState, + onSave: () -> Unit, + 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 + globalSnackBarState.show(changesSaved, NotificationPriority.Campaign) + showedSnackBar() + } + + HedvigScaffold( + topAppBarText = stringResource(Res.string.swish), + 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, + ) + } + 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)) + 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, + ) + } + } + } + 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), + ) + } + } + + 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 { + append(formatSwishPhoneNumber(trimmed)) + 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) + } +} + +@Composable +@HedvigPreview +private fun PreviewSetupSwishPayinScreen( + @PreviewParameter(SetupSwishPayinUiStateProvider::class) uiState: SetupSwishPayoutUiState, +) { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SetupSwishPayoutScreen( + uiState = uiState, + globalSnackBarState = GlobalSnackBarState(), + onSave = {}, + showedSnackBar = {}, + navigateUp = {}, + {}, + {}, + ) + } + } +} + +private class SetupSwishPayinUiStateProvider : CollectionPreviewParameterProvider( + listOf( + SetupSwishPayoutUiState( + phoneNumber = "287334432273", + isLoading = false, + error = null, + showSuccessSnackBar = false, + ), + SetupSwishPayoutUiState( + phoneNumber = "", + isLoading = false, + error = ErrorMessage(), + showSuccessSnackBar = false, + ), + SetupSwishPayoutUiState( + phoneNumber = "837286428", + isLoading = true, + 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 new file mode 100644 index 0000000000..6b0100f4b5 --- /dev/null +++ b/app/feature/feature-payin-account/src/main/kotlin/com/hedvig/android/feature/payin/account/ui/setupswish/SetupSwishPayinViewModel.kt @@ -0,0 +1,129 @@ +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.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( + SetupSwishPayoutUiState( + phoneNumber = "", + isLoading = false, + error = null, + showSuccessSnackBar = false, + successUrl = null, + ), + SetupSwishPayoutPresenter(setupSwishPayoutUseCase), + ) + +internal sealed interface SetupSwishPayoutEvent { + data object Save : SetupSwishPayoutEvent + + data object ShowedSnackBar : SetupSwishPayoutEvent + + 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( + private val setupSwishPayoutUseCase: SetupSwishPayinUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: SetupSwishPayoutUiState, + ): SetupSwishPayoutUiState { + 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) } + 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) { + LaunchedEffect(currentSave) { + isLoading = true + errorMessage = null + resultIsPending = false + setupSwishPayoutUseCase.invoke(phoneNumberState).fold( + ifLeft = { + isLoading = false + errorMessage = it + saveIteration = null + }, + ifRight = { result -> + isLoading = false + 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 + } + } + }, + ) + } + } + + CollectEvents { event -> + when (event) { + SetupSwishPayoutEvent.Save -> { + if (!isLoading) { + saveIteration = phoneNumberState + } + } + + SetupSwishPayoutEvent.ShowedSnackBar -> { + showSuccessSnackBar = false + } + + is SetupSwishPayoutEvent.UpdateText -> { + phoneNumberState = event.newText + } + } + } + + return SetupSwishPayoutUiState( + phoneNumber = phoneNumberState, + isLoading = isLoading, + error = errorMessage, + showSuccessSnackBar = showSuccessSnackBar, + successUrl = urlToRedirect, + ) + } +} 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 36de144197..e769af072c 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/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/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 a7842273ea..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 @@ -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 @@ -94,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), ) @@ -125,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( @@ -220,30 +232,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 = 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)) } @@ -360,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, 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 85cae92b55..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 @@ -15,6 +15,9 @@ 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) @@ -25,6 +28,7 @@ internal class MemberPaymentDetailsViewModel( presenter = MemberPaymentDetailsPresenter( getMemberPaymentsDetailsUseCase, ), + sharingStarted = SharingStarted.WhileSubscribed(2.seconds), ) private class MemberPaymentDetailsPresenter( 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( 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 a3b0224833..3c0ac18c8c 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 @@ -13,7 +13,6 @@ import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn 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 @@ -46,15 +45,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..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,7 +1,5 @@ package com.hedvig.android.feature.payoutaccount.data -import octopus.type.PaymentMethodInvoiceDelivery - internal sealed interface PayoutAccount { val isPending: Boolean @@ -23,10 +21,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/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 859fd97808..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 @@ -167,13 +165,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 +311,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), - ), ), ) 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..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 @@ -1,24 +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.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.colored.Swish 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 @@ -34,7 +45,6 @@ internal fun SelectPayoutMethodDestination( onTrustlySelected: () -> Unit, onNordeaSelected: () -> Unit, onSwishSelected: () -> Unit, - onInvoiceSelected: () -> Unit, navigateUp: () -> Unit, ) { HedvigScaffold( @@ -51,14 +61,16 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.trustly), subtitle = stringResource(Res.string.PAYOUT_METHOD_TRUSTLY_DESCRIPTION), onClick = onTrustlySelected, + provider = provider, ) } 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, ) } @@ -67,14 +79,7 @@ internal fun SelectPayoutMethodDestination( title = stringResource(Res.string.swish), subtitle = stringResource(Res.string.PAYOUT_METHOD_SWISH_DESCRIPTION), onClick = onSwishSelected, - ) - } - - MemberPaymentProvider.INVOICE -> { - PayoutMethodRow( - title = stringResource(Res.string.PAYMENTS_INVOICE), - subtitle = stringResource(Res.string.PAYOUT_METHOD_INVOICE_DESCRIPTION), - onClick = onInvoiceSelected, + provider = provider, ) } @@ -87,16 +92,76 @@ 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.Card, + 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 +@HedvigShortMultiScreenPreview +private fun PreviewSelectPayoutMethodScreen() { + HedvigTheme { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + SelectPayoutMethodDestination( + availableProviders = listOf( + MemberPaymentProvider.SWISH, + MemberPaymentProvider.TRUSTLY, + MemberPaymentProvider.NORDEA, + ), + onTrustlySelected = {}, + onNordeaSelected = {}, + onSwishSelected = {}, + navigateUp = {}, ) } } 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 bfe6cd3607..fb26630729 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 @@ -366,7 +366,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, 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 diff --git a/app/shared/member-quick-actions/build.gradle.kts b/app/shared/member-quick-actions/build.gradle.kts index d5fcdf6a52..6ad9104849 100644 --- a/app/shared/member-quick-actions/build.gradle.kts +++ b/app/shared/member-quick-actions/build.gradle.kts @@ -41,9 +41,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/GetMemberQuickActionsUseCase.kt b/app/shared/member-quick-actions/src/commonMain/kotlin/com/hedvig/android/memberquickactions/GetMemberQuickActionsUseCase.kt index 297be99383..0f6cbf8572 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 @@ -43,6 +43,7 @@ import hedvig.resources.HC_QUICK_ACTIONS_UPGRADE_COVERAGE_SUBTITLE import hedvig.resources.HC_QUICK_ACTIONS_UPGRADE_COVERAGE_TITLE import hedvig.resources.HOME_QUICK_ACTIONS_CHANGE_ADDRESS import hedvig.resources.HOME_QUICK_ACTIONS_EDIT_INSURANCE +import hedvig.resources.MANAGE_BILLING_METHODS_BUTTON import hedvig.resources.Res import hedvig.resources.insurance_details_change_amount import hedvig.resources.insurance_details_change_amount_subtitle @@ -149,7 +150,7 @@ internal class GetMemberQuickActionsUseCaseImpl( QuickAction.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/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 -> { 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 @@ + + + + + + + + + + + + + + + + + + + + + + + +