From b544085ee05b595293017d02c7ef7dccbc72b6a7 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 8 Jun 2026 13:45:47 +0200 Subject: [PATCH 1/9] GetMemberTypeUseCase and PaymentConnection.NeedsPayoutSetup --- .../android/data/contract/ContractType.kt | 4 + .../ActiveInsuranceContractTypes.graphql | 7 ++ .../paying/member/GetMemberTypeUseCase.kt | 74 +++++++++++++++++++ .../main/graphql/QueryUpcomingPayment.graphql | 6 ++ .../payments/data/PaymentConnection.kt | 4 +- .../data/GetUpcomingPaymentUseCase.kt | 29 +++++++- .../ui/payments/PaymentsDestination.kt | 45 ++++++++--- .../payments/ui/payments/PaymentsPresenter.kt | 12 +-- 8 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt diff --git a/app/data/data-contract/src/commonMain/kotlin/com/hedvig/android/data/contract/ContractType.kt b/app/data/data-contract/src/commonMain/kotlin/com/hedvig/android/data/contract/ContractType.kt index 7124e8227e..018d0c7bed 100644 --- a/app/data/data-contract/src/commonMain/kotlin/com/hedvig/android/data/contract/ContractType.kt +++ b/app/data/data-contract/src/commonMain/kotlin/com/hedvig/android/data/contract/ContractType.kt @@ -65,6 +65,8 @@ enum class ContractType { SE_GROUP_APARTMENT_RENT, SE_QASA_LONG_TERM_RENTAL, SE_QASA_SHORT_TERM_RENTAL, + + SE_QASA_LANDLORD, NO_HOME_CONTENT_YOUTH_RENT, NO_HOME_CONTENT_YOUTH_OWN, DK_HOME_CONTENT_STUDENT_OWN, @@ -132,6 +134,7 @@ fun ContractType.isTrialContract() = when (this) { SE_DOG_PREMIUM, SE_DOG_STANDARD, UNKNOWN, + ContractType.SE_QASA_LANDLORD -> false SE_CAR_TRIAL_FULL, @@ -162,6 +165,7 @@ fun String.toContractType(): ContractType = when (this) { "SE_GROUP_APARTMENT_RENT" -> SE_GROUP_APARTMENT_RENT "SE_QASA_LONG_TERM_RENTAL" -> SE_QASA_LONG_TERM_RENTAL "SE_QASA_SHORT_TERM_RENTAL" -> SE_QASA_SHORT_TERM_RENTAL + "SE_QASA_LANDLORD" -> ContractType.SE_QASA_LANDLORD "NO_HOME_CONTENT_YOUTH_RENT" -> NO_HOME_CONTENT_YOUTH_RENT "NO_HOME_CONTENT_YOUTH_OWN" -> NO_HOME_CONTENT_YOUTH_OWN "DK_HOME_CONTENT_STUDENT_OWN" -> DK_HOME_CONTENT_STUDENT_OWN diff --git a/app/data/data-paying-member/src/main/graphql/ActiveInsuranceContractTypes.graphql b/app/data/data-paying-member/src/main/graphql/ActiveInsuranceContractTypes.graphql index f30d0179bb..b2b96de5d9 100644 --- a/app/data/data-paying-member/src/main/graphql/ActiveInsuranceContractTypes.graphql +++ b/app/data/data-paying-member/src/main/graphql/ActiveInsuranceContractTypes.graphql @@ -7,5 +7,12 @@ query ActiveInsuranceContractTypes { } } } + terminatedContracts { + currentAgreement { + productVariant { + typeOfContract + } + } + } } } diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt new file mode 100644 index 0000000000..b49840a454 --- /dev/null +++ b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt @@ -0,0 +1,74 @@ +package com.hedvig.android.data.paying.member + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.right +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.apollo.ErrorMessage +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.core.demomode.DemoManager +import com.hedvig.android.core.demomode.ProdOrDemoProvider +import com.hedvig.android.core.demomode.Provider +import com.hedvig.android.data.contract.ContractType +import com.hedvig.android.data.contract.toContractType +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import dev.zacsweers.metro.binding +import octopus.ActiveInsuranceContractTypesQuery + +interface GetMemberTypeUseCase { + suspend fun invoke(): Either +} + +enum class MemberType { + QASA_ONLY_MEMBER, + // in Payments: hide discounts, payment history, member payment details, + // in Payments: always show payout account; + // HelpCenter: hide Payments section + STANDARD_MEMBER, + // current logic +} + +@Inject +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class, binding>()) +internal class GetMemberTypeUseCaseProvider( + override val demoManager: DemoManager, + override val demoImpl: GetMemberTypeUseCaseDemo, + override val prodImpl: GetMemberTypeUseCaseImpl, +) : ProdOrDemoProvider + +@SingleIn(AppScope::class) +@Inject +internal class GetMemberTypeUseCaseImpl( + private val apolloClient: ApolloClient, +) : GetMemberTypeUseCase { + override suspend fun invoke(): Either { + return either { + val result = apolloClient.query(ActiveInsuranceContractTypesQuery()) + .safeExecute(::ErrorMessage) + .bind() + val activeContractsTypes = result.currentMember + .activeContracts + .map { it.currentAgreement.productVariant.typeOfContract.toContractType() } + val terminatedContractsTypes = result.currentMember + .terminatedContracts + .map { it.currentAgreement.productVariant.typeOfContract.toContractType() } + val onlyQasaLandlordContracts = + activeContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } && + terminatedContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } + if (onlyQasaLandlordContracts) MemberType.QASA_ONLY_MEMBER else MemberType.STANDARD_MEMBER + } + } +} + +@SingleIn(AppScope::class) +@Inject +internal class GetMemberTypeUseCaseDemo : GetMemberTypeUseCase { + override suspend fun invoke(): Either { + return MemberType.STANDARD_MEMBER.right() + } +} diff --git a/app/feature/feature-payments/src/main/graphql/QueryUpcomingPayment.graphql b/app/feature/feature-payments/src/main/graphql/QueryUpcomingPayment.graphql index 81decbd041..337fe91c00 100644 --- a/app/feature/feature-payments/src/main/graphql/QueryUpcomingPayment.graphql +++ b/app/feature/feature-payments/src/main/graphql/QueryUpcomingPayment.graphql @@ -31,6 +31,12 @@ query UpcomingPayment { defaultPayinMethod { ...MemberPaymentMethodFragment } + payoutMethods { + ...MemberPaymentMethodFragment + } + defaultPayoutMethod { + ...MemberPaymentMethodFragment + } } } } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentConnection.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentConnection.kt index 0f9324240b..5646b106e1 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentConnection.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentConnection.kt @@ -7,9 +7,11 @@ sealed interface PaymentConnection { data object Pending : PaymentConnection - data class NeedsSetup( + data class NeedsPayinSetup( val terminationDateIfNotConnected: LocalDate?, ) : PaymentConnection + data object NeedsPayoutSetup: PaymentConnection + data object Unknown : PaymentConnection } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt index 4820484050..4f58d2d1aa 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt @@ -11,6 +11,8 @@ import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.GetMemberTypeUseCase +import com.hedvig.android.data.paying.member.MemberType import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.MemberCharge import com.hedvig.android.feature.payments.data.MemberChargeShortInfo @@ -36,12 +38,14 @@ internal interface GetUpcomingPaymentUseCase { internal data class GetUpcomingPaymentUseCaseImpl( val apolloClient: ApolloClient, val clock: Clock, + val getMemberTypeUseCase: GetMemberTypeUseCase, ) : GetUpcomingPaymentUseCase { override suspend fun invoke(): Either = either { val result = apolloClient.query(UpcomingPaymentQuery()) .fetchPolicy(FetchPolicy.NetworkFirst) .safeExecute(::ErrorMessage) .bind() + val memberType = getMemberTypeUseCase.invoke().bind() val missedChargeIdToChargeManually: String? = result.currentMember.missedChargeIdToChargeManually @@ -71,6 +75,8 @@ internal data class GetUpcomingPaymentUseCaseImpl( val paymentMethods = result.currentMember.paymentMethods val payinMethod = paymentMethods.defaultPayinMethod ?: paymentMethods.payinMethods.find { it.isDefault } + val payoutMethod = paymentMethods.defaultPayoutMethod + ?: paymentMethods.payinMethods.find { it.isDefault } if (payinMethod == null) { val firstKnownTerminationDateForContractTerminatedDueToMissedPayments = result .currentMember @@ -79,12 +85,27 @@ internal data class GetUpcomingPaymentUseCaseImpl( .mapNotNull { it.terminationDate } .sorted() .firstOrNull() - return@run PaymentConnection.NeedsSetup( - firstKnownTerminationDateForContractTerminatedDueToMissedPayments, - ) + + when (memberType) { + + MemberType.STANDARD_MEMBER -> return@run PaymentConnection.NeedsPayinSetup( + firstKnownTerminationDateForContractTerminatedDueToMissedPayments, + ) + + MemberType.QASA_ONLY_MEMBER -> { + if (payoutMethod == null) { + return@run PaymentConnection.NeedsPayoutSetup + } else return@run PaymentConnection.Active + } + } } when (payinMethod.status) { - MemberPaymentMethodStatus.ACTIVE -> PaymentConnection.Active + MemberPaymentMethodStatus.ACTIVE -> { + if (payoutMethod == null) { + return@run PaymentConnection.NeedsPayoutSetup + } else return@run PaymentConnection.Active + } + MemberPaymentMethodStatus.PENDING -> PaymentConnection.Pending MemberPaymentMethodStatus.UNKNOWN__ -> PaymentConnection.Unknown } 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 0c4091d20d..1641fce5a0 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 @@ -33,7 +33,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics @@ -59,6 +58,7 @@ import com.hedvig.android.design.system.hedvig.HorizontalDivider import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.NotificationDefaults +import com.hedvig.android.design.system.hedvig.NotificationDefaults.InfoCardStyle import com.hedvig.android.design.system.hedvig.NotificationDefaults.InfoCardStyle.Button import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info @@ -106,6 +106,8 @@ import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_BUTTON import hedvig.resources.PAYMENTS_PAYMENT_OVERDUE_TITLE import hedvig.resources.PAYMENTS_PROCESSING_PAYMENT import hedvig.resources.PAYMENTS_UPCOMING_PAYMENT +import hedvig.resources.PAYOUT_ADD_PAYOUT_METHOD +import hedvig.resources.PAYOUT_MISSING_INFO import hedvig.resources.PAYOUT_PAGE_HEADING import hedvig.resources.PROFILE_PAYMENT_CONNECT_DIRECT_DEBIT_TITLE import hedvig.resources.R @@ -299,22 +301,40 @@ private fun PaymentsContent( .padding(horizontal = 16.dp) .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), ) - val showConnectedPaymentInfo = uiState is Content && - uiState.connectedPaymentInfo is ConnectedPaymentInfo.NeedsSetup + val showConnectPaymentInfo = uiState is Content && + uiState.connectedPaymentInfo is ConnectedPaymentInfo.NeedsPayinSetup AnimatedVisibility( - visibleState = remember { MutableTransitionState(showConnectedPaymentInfo) }.apply { - targetState = showConnectedPaymentInfo + visibleState = remember { MutableTransitionState(showConnectPaymentInfo) }.apply { + targetState = showConnectPaymentInfo }, enter = expandVertically(expandFrom = Alignment.CenterVertically), ) { CardNotConnectedWarningCard( - connectedPaymentInfo = (uiState as? Content)?.connectedPaymentInfo as? ConnectedPaymentInfo.NeedsSetup, + connectedPaymentInfo = (uiState as? Content)?.connectedPaymentInfo as? ConnectedPaymentInfo.NeedsPayinSetup, onChangeBankAccount = onChangeBankAccount, modifier = Modifier .padding(horizontal = 16.dp) .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), ) } + val showConnectPayoutInfo = uiState is Content && + uiState.connectedPaymentInfo is ConnectedPaymentInfo.NeedsPayoutSetup + AnimatedVisibility( + showConnectPayoutInfo, + enter = expandVertically(expandFrom = Alignment.CenterVertically), + ) { + HedvigNotificationCard( + message = stringResource(Res.string.PAYOUT_MISSING_INFO), + modifier = Modifier .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + priority = NotificationPriority.Attention, + style = Button( + buttonText = stringResource(Res.string.PAYOUT_ADD_PAYOUT_METHOD), + onButtonClick = onPayoutAccountClicked, + ), + minLines = 1, + ) + } PaymentsListItems( uiState, @@ -340,7 +360,8 @@ private fun PaymentsContent( ) } - is ConnectedPaymentInfo.NeedsSetup, + is ConnectedPaymentInfo.NeedsPayinSetup, + ConnectedPaymentInfo.NeedsPayoutSetup, ConnectedPaymentInfo.Unknown, is ConnectedPaymentInfo.Active, -> { @@ -352,7 +373,7 @@ private fun PaymentsContent( @Composable private fun CardNotConnectedWarningCard( - connectedPaymentInfo: ConnectedPaymentInfo.NeedsSetup?, + connectedPaymentInfo: ConnectedPaymentInfo.NeedsPayinSetup?, onChangeBankAccount: () -> Unit, modifier: Modifier = Modifier, ) { @@ -845,7 +866,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< isManualChargeAllowed = null, ), ongoingCharges = emptyList(), - connectedPaymentInfo = ConnectedPaymentInfo.NeedsSetup( + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( null, ), showPayoutButton = false, @@ -861,7 +882,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ), upcomingPaymentInfo = NoInfo, ongoingCharges = emptyList(), - connectedPaymentInfo = ConnectedPaymentInfo.NeedsSetup( + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( null, ), showPayoutButton = false, @@ -881,7 +902,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< isManualChargeAllowed = null, ), ongoingCharges = emptyList(), - connectedPaymentInfo = ConnectedPaymentInfo.NeedsSetup( + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( dueDateToConnect = System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), showPayoutButton = false, @@ -901,7 +922,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< isManualChargeAllowed = null, ), ongoingCharges = emptyList(), - connectedPaymentInfo = ConnectedPaymentInfo.NeedsSetup( + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), showPayoutButton = false, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt index c7a403a282..098044a994 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt @@ -13,10 +13,9 @@ import com.hedvig.android.core.demomode.Provider import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.MemberCharge -import com.hedvig.android.feature.payments.data.MemberPaymentChargeMethod import com.hedvig.android.feature.payments.data.PaymentConnection import com.hedvig.android.feature.payments.data.PaymentConnection.Active -import com.hedvig.android.feature.payments.data.PaymentConnection.NeedsSetup +import com.hedvig.android.feature.payments.data.PaymentConnection.NeedsPayinSetup import com.hedvig.android.feature.payments.data.PaymentConnection.Pending import com.hedvig.android.feature.payments.data.PaymentConnection.Unknown import com.hedvig.android.feature.payments.data.PaymentOverview @@ -28,8 +27,6 @@ import com.hedvig.android.molecule.public.MoleculePresenter import com.hedvig.android.molecule.public.MoleculePresenterScope import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.firstOrNull import kotlinx.datetime.LocalDate internal class PaymentsPresenter( @@ -122,11 +119,12 @@ private fun PaymentConnection.toConnectedPaymentInfo(): ConnectedPaymentInfo { Pending -> ConnectedPaymentInfo.Pending - is NeedsSetup -> ConnectedPaymentInfo.NeedsSetup( + is NeedsPayinSetup -> ConnectedPaymentInfo.NeedsPayinSetup( dueDateToConnect = terminationDateIfNotConnected, ) Unknown -> ConnectedPaymentInfo.Unknown + PaymentConnection.NeedsPayoutSetup -> ConnectedPaymentInfo.NeedsPayoutSetup } } @@ -172,13 +170,15 @@ internal sealed interface PaymentsUiState { sealed interface ConnectedPaymentInfo { object Unknown : ConnectedPaymentInfo - data class NeedsSetup( + data class NeedsPayinSetup( val dueDateToConnect: LocalDate?, ) : ConnectedPaymentInfo data object Pending : ConnectedPaymentInfo data object Active : ConnectedPaymentInfo + + data object NeedsPayoutSetup: ConnectedPaymentInfo } } } From 1a8c97814316e8992ba88e02237e3b150604df0d Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 8 Jun 2026 15:43:26 +0200 Subject: [PATCH 2/9] PaymentsListItems changes --- .../paying/member/GetMemberTypeUseCase.kt | 23 +- .../android/feature/payments/PreviewData.kt | 2 + .../feature/payments/data/PaymentOverview.kt | 2 + .../data/GetUpcomingPaymentUseCase.kt | 7 +- .../ui/payments/PaymentsDestination.kt | 305 ++++++++++++------ .../payments/ui/payments/PaymentsPresenter.kt | 3 + 6 files changed, 233 insertions(+), 109 deletions(-) diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt index b49840a454..0e47fc33c3 100644 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt +++ b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt @@ -25,11 +25,19 @@ interface GetMemberTypeUseCase { enum class MemberType { QASA_ONLY_MEMBER, + // in Payments: hide discounts, payment history, member payment details, // in Payments: always show payout account; + // in Payments: show connect payout reminder if missing // HelpCenter: hide Payments section STANDARD_MEMBER, + // current logic + STANDARD_TO_QASA_MEMBER + // in Payments: if no upcoming payments: hide discounts and member payment details + // in Payments: if upcoming payment and missing payin: show connect payin reminder, + // else show connect payout reminder if missing + // in Payments: always show payout account; } @Inject @@ -57,10 +65,19 @@ internal class GetMemberTypeUseCaseImpl( val terminatedContractsTypes = result.currentMember .terminatedContracts .map { it.currentAgreement.productVariant.typeOfContract.toContractType() } - val onlyQasaLandlordContracts = + + val onlyQasaContracts = (activeContractsTypes.isNotEmpty() + || terminatedContractsTypes.isNotEmpty()) + activeContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } && + terminatedContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } + if (onlyQasaContracts) return@either MemberType.QASA_ONLY_MEMBER + + val standardToQasa = activeContractsTypes.isNotEmpty() && activeContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } && - terminatedContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } - if (onlyQasaLandlordContracts) MemberType.QASA_ONLY_MEMBER else MemberType.STANDARD_MEMBER + terminatedContractsTypes.any { it != ContractType.SE_QASA_LANDLORD } + if (standardToQasa) return@either MemberType.STANDARD_TO_QASA_MEMBER + + MemberType.STANDARD_MEMBER } } } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt index 9c5d7783df..44849f0661 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/PreviewData.kt @@ -2,6 +2,7 @@ package com.hedvig.android.feature.payments import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.MemberType import com.hedvig.android.feature.payments.data.Discount import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.MemberCharge @@ -280,6 +281,7 @@ internal val paymentOverViewPreviewData: PaymentOverview ongoingCharges = listOf(OngoingCharge("id", LocalDate.fromEpochDays(401), UiMoney(200.0, UiCurrencyCode.SEK))), paymentConnection = PaymentConnection.Active, isManualChargeAllowed = ManualChargeToPrompt(UiMoney(200.0, UiCurrencyCode.SEK)), + memberType = MemberType.STANDARD_MEMBER ) } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt index 06376e27c3..51369543ef 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/PaymentOverview.kt @@ -1,6 +1,7 @@ package com.hedvig.android.feature.payments.data import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.MemberType import kotlinx.datetime.LocalDate data class PaymentOverview( @@ -8,6 +9,7 @@ data class PaymentOverview( val ongoingCharges: List, val paymentConnection: PaymentConnection, val isManualChargeAllowed: ManualChargeToPrompt?, + val memberType: MemberType ) { data class OngoingCharge( val id: String, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt index 4f58d2d1aa..6e3923fc21 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt @@ -17,6 +17,7 @@ import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.MemberCharge import com.hedvig.android.feature.payments.data.MemberChargeShortInfo import com.hedvig.android.feature.payments.data.PaymentConnection +import com.hedvig.android.feature.payments.data.PaymentConnection.* import com.hedvig.android.feature.payments.data.PaymentOverview import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge import com.hedvig.android.feature.payments.data.toFailedCharge @@ -88,7 +89,7 @@ internal data class GetUpcomingPaymentUseCaseImpl( when (memberType) { - MemberType.STANDARD_MEMBER -> return@run PaymentConnection.NeedsPayinSetup( + MemberType.STANDARD_MEMBER -> return@run NeedsPayinSetup( firstKnownTerminationDateForContractTerminatedDueToMissedPayments, ) @@ -97,6 +98,8 @@ internal data class GetUpcomingPaymentUseCaseImpl( return@run PaymentConnection.NeedsPayoutSetup } else return@run PaymentConnection.Active } + + MemberType.STANDARD_TO_QASA_MEMBER -> TODO() } } when (payinMethod.status) { @@ -111,6 +114,7 @@ internal data class GetUpcomingPaymentUseCaseImpl( } }, isManualChargeAllowed = isManualChargeAllowed, + memberType = memberType ) } } @@ -145,6 +149,7 @@ internal class GetUpcomingPaymentUseCaseDemo( emptyList(), PaymentConnection.Unknown, isManualChargeAllowed = null, + memberType = MemberType.STANDARD_MEMBER ).right() } } diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsDestination.kt index 1641fce5a0..9b23232a56 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 @@ -33,6 +33,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics @@ -45,6 +46,7 @@ import com.hedvig.android.core.common.safeCast import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiCurrencyCode.SEK import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.MemberType import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard @@ -57,12 +59,12 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HorizontalDivider import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.NotificationDefaults import com.hedvig.android.design.system.hedvig.NotificationDefaults.InfoCardStyle import com.hedvig.android.design.system.hedvig.NotificationDefaults.InfoCardStyle.Button import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.debugBorder import com.hedvig.android.design.system.hedvig.hedvigDropShadow import com.hedvig.android.design.system.hedvig.icon.Campaign import com.hedvig.android.design.system.hedvig.icon.Card @@ -278,7 +280,7 @@ private fun PaymentsContent( } val upcomingPayment = (uiState as? Content)?.upcomingPayment if (upcomingPayment == NoUpcomingPayment) { - if (ongoingCharges.isNullOrEmpty()) { + if (ongoingCharges.isNullOrEmpty() && uiState.memberType != MemberType.QASA_ONLY_MEMBER) { HedvigInformationSection( stringResource(Res.string.PAYMENTS_NO_PAYMENTS_IN_PROGRESS), modifier = Modifier @@ -295,56 +297,14 @@ private fun PaymentsContent( .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), ) } - UpcomingPaymentInfoCard( - upcomingPaymentInfo = (uiState as? Content)?.upcomingPaymentInfo, - modifier = Modifier - .padding(horizontal = 16.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - ) - val showConnectPaymentInfo = uiState is Content && - uiState.connectedPaymentInfo is ConnectedPaymentInfo.NeedsPayinSetup - AnimatedVisibility( - visibleState = remember { MutableTransitionState(showConnectPaymentInfo) }.apply { - targetState = showConnectPaymentInfo - }, - enter = expandVertically(expandFrom = Alignment.CenterVertically), - ) { - CardNotConnectedWarningCard( - connectedPaymentInfo = (uiState as? Content)?.connectedPaymentInfo as? ConnectedPaymentInfo.NeedsPayinSetup, - onChangeBankAccount = onChangeBankAccount, + if (uiState is Content) { + UpcomingPaymentInfoCard( + upcomingPaymentInfo = uiState.upcomingPaymentInfo, modifier = Modifier .padding(horizontal = 16.dp) .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), ) - } - val showConnectPayoutInfo = uiState is Content && - uiState.connectedPaymentInfo is ConnectedPaymentInfo.NeedsPayoutSetup - AnimatedVisibility( - showConnectPayoutInfo, - enter = expandVertically(expandFrom = Alignment.CenterVertically), - ) { - HedvigNotificationCard( - message = stringResource(Res.string.PAYOUT_MISSING_INFO), - modifier = Modifier .padding(horizontal = 16.dp) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), - priority = NotificationPriority.Attention, - style = Button( - buttonText = stringResource(Res.string.PAYOUT_ADD_PAYOUT_METHOD), - onButtonClick = onPayoutAccountClicked, - ), - minLines = 1, - ) - } - PaymentsListItems( - uiState, - onDiscountClicked = onDiscountClicked, - onPaymentHistoryClicked = onPaymentHistoryClicked, - onPayoutAccountClicked = onPayoutAccountClicked, - onPaymentDetailsClicked = onPaymentDetailsClicked, - showPayoutButton = (uiState as? Content)?.showPayoutButton == true, - ) - if (uiState is Content) { when (uiState.connectedPaymentInfo) { ConnectedPaymentInfo.Pending -> { HedvigNotificationCard( @@ -360,14 +320,45 @@ private fun PaymentsContent( ) } - is ConnectedPaymentInfo.NeedsPayinSetup, - ConnectedPaymentInfo.NeedsPayoutSetup, + is ConnectedPaymentInfo.NeedsPayinSetup -> { + CardNotConnectedWarningCard( + connectedPaymentInfo = uiState.connectedPaymentInfo as? ConnectedPaymentInfo.NeedsPayinSetup, + onChangeBankAccount = onChangeBankAccount, + modifier = Modifier + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + ) + } + + ConnectedPaymentInfo.NeedsPayoutSetup -> { + HedvigNotificationCard( + message = stringResource(Res.string.PAYOUT_MISSING_INFO), + modifier = Modifier + .padding(horizontal = 16.dp) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)), + priority = NotificationPriority.Attention, + style = Button( + buttonText = stringResource(Res.string.PAYOUT_ADD_PAYOUT_METHOD), + onButtonClick = onPayoutAccountClicked, + ), + minLines = 1, + ) + } + ConnectedPaymentInfo.Unknown, is ConnectedPaymentInfo.Active, - -> { - } + -> {} } } + + PaymentsListItems( + uiState, + onDiscountClicked = onDiscountClicked, + onPaymentHistoryClicked = onPaymentHistoryClicked, + onPayoutAccountClicked = onPayoutAccountClicked, + onPaymentDetailsClicked = onPaymentDetailsClicked, + showPayoutButton = (uiState as? Content)?.showPayoutButton == true, + ) } } @@ -400,9 +391,10 @@ private fun CardNotConnectedWarningCard( @Composable private fun UpcomingPaymentInfoCard(upcomingPaymentInfo: UpcomingPaymentInfo?, modifier: Modifier = Modifier) { + if (upcomingPaymentInfo == NoInfo || upcomingPaymentInfo == null) return Box(modifier) { when (upcomingPaymentInfo) { - NoInfo -> {} + NoInfo, null -> {} InProgress -> { HedvigNotificationCard( @@ -422,13 +414,11 @@ private fun UpcomingPaymentInfoCard(upcomingPaymentInfo: UpcomingPaymentInfo?, m monthDateFormatter.format(upcomingPaymentInfo.failedPaymentStartDate), monthDateFormatter.format(upcomingPaymentInfo.failedPaymentEndDate), ), - style = NotificationDefaults.InfoCardStyle.Default, + style = InfoCardStyle.Default, ) } } } - - null -> {} } } } @@ -446,41 +436,67 @@ private fun PaymentsListItems( .padding(horizontal = 16.dp) .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)) Column { - PaymentsListItem( - text = stringResource(Res.string.PAYMENTS_DISCOUNTS_SECTION_TITLE), - icon = { - Icon( - imageVector = HedvigIcons.Campaign, - contentDescription = null, - tint = HedvigTheme.colorScheme.signalGreenElement, - modifier = Modifier.size(24.dp), + if (uiState is Content) { + val showDiscountsItem = when (uiState.memberType) { + MemberType.QASA_ONLY_MEMBER, + MemberType.STANDARD_TO_QASA_MEMBER, + -> false + + MemberType.STANDARD_MEMBER -> true + } + if (showDiscountsItem) { + PaymentsListItem( + text = stringResource(Res.string.PAYMENTS_DISCOUNTS_SECTION_TITLE), + icon = { + Icon( + imageVector = HedvigIcons.Campaign, + contentDescription = null, + tint = HedvigTheme.colorScheme.signalGreenElement, + modifier = Modifier.size(24.dp), + ) + }, + modifier = Modifier + .clickable(onClick = onDiscountClicked) + .then(listItemsSideSpacingModifier) + .padding(vertical = 16.dp) + .fillMaxWidth(), ) - }, - modifier = Modifier - .clickable(onClick = onDiscountClicked) - .then(listItemsSideSpacingModifier) - .padding(vertical = 16.dp) - .fillMaxWidth(), - ) - HorizontalDivider(modifier = listItemsSideSpacingModifier) - PaymentsListItem( - text = stringResource(Res.string.PAYMENTS_PAYMENT_HISTORY_BUTTON_LABEL), - icon = { - Icon( - imageVector = HedvigIcons.Clock, - contentDescription = null, - modifier = Modifier.size(24.dp), + HorizontalDivider(modifier = listItemsSideSpacingModifier) + } + + + val showPaymentHistoryItem = when (uiState.memberType) { + MemberType.QASA_ONLY_MEMBER -> false + MemberType.STANDARD_MEMBER, + MemberType.STANDARD_TO_QASA_MEMBER, + -> true + } + if (showPaymentHistoryItem) { + PaymentsListItem( + text = stringResource(Res.string.PAYMENTS_PAYMENT_HISTORY_BUTTON_LABEL), + icon = { + Icon( + imageVector = HedvigIcons.Clock, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + modifier = Modifier + .clickable(onClick = onPaymentHistoryClicked) + .then(listItemsSideSpacingModifier) + .padding(vertical = 16.dp) + .fillMaxWidth(), ) - }, - modifier = Modifier - .clickable(onClick = onPaymentHistoryClicked) - .then(listItemsSideSpacingModifier) - .padding(vertical = 16.dp) - .fillMaxWidth(), - ) - if (uiState is Content) { - if (uiState.connectedPaymentInfo is ConnectedPaymentInfo.Active) { HorizontalDivider(listItemsSideSpacingModifier) + } + + val showPaymentDetailsItem = when (uiState.memberType) { + MemberType.QASA_ONLY_MEMBER -> false + MemberType.STANDARD_MEMBER, + MemberType.STANDARD_TO_QASA_MEMBER, + -> uiState.connectedPaymentInfo is ConnectedPaymentInfo.Active + } + if (showPaymentDetailsItem) { PaymentsListItem( text = stringResource(Res.string.PAYMENTS_PAYMENT_DETAILS_INFO_TITLE), icon = { @@ -496,25 +512,34 @@ private fun PaymentsListItems( .padding(vertical = 16.dp) .fillMaxWidth(), ) + HorizontalDivider(modifier = listItemsSideSpacingModifier) + } + + val showPayoutItem = when (uiState.memberType) { + MemberType.QASA_ONLY_MEMBER, + MemberType.STANDARD_TO_QASA_MEMBER, + -> true + + MemberType.STANDARD_MEMBER -> showPayoutButton + } + if (showPayoutItem) { + PaymentsListItem( + text = stringResource(Res.string.PAYOUT_PAGE_HEADING), + icon = { + Icon( + imageVector = HedvigIcons.PaymentOutline, + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + }, + modifier = Modifier + .clickable(onClick = onPayoutAccountClicked) + .then(listItemsSideSpacingModifier) + .padding(vertical = 16.dp) + .fillMaxWidth(), + ) + HorizontalDivider(modifier = listItemsSideSpacingModifier) } - } - if (showPayoutButton) { - HorizontalDivider(modifier = listItemsSideSpacingModifier) - PaymentsListItem( - text = stringResource(Res.string.PAYOUT_PAGE_HEADING), - icon = { - Icon( - imageVector = HedvigIcons.PaymentOutline, - contentDescription = null, - modifier = Modifier.size(24.dp), - ) - }, - modifier = Modifier - .clickable(onClick = onPayoutAccountClicked) - .then(listItemsSideSpacingModifier) - .padding(vertical = 16.dp) - .fillMaxWidth(), - ) } } } @@ -788,6 +813,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = listOf(OngoingCharge("id", LocalDate.fromEpochDays(401), UiMoney(200.0, SEK))), connectedPaymentInfo = ConnectedPaymentInfo.Active, showPayoutButton = true, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -802,6 +828,24 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, + ), + ) + add( + Content( + isRetrying = false, + upcomingPayment = UpcomingPayment.Content( + UiMoney(100.0, SEK), + System.now().toLocalDateTime(TimeZone.UTC).date, + "rdg", + ), + upcomingPaymentInfo = NoInfo, + ongoingCharges = emptyList(), + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayinSetup( + null, + ), + showPayoutButton = false, + memberType = MemberType.STANDARD_TO_QASA_MEMBER, ), ) add( @@ -816,6 +860,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -836,6 +881,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Active, showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -850,6 +896,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< ongoingCharges = emptyList(), connectedPaymentInfo = ConnectedPaymentInfo.Pending, showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -870,6 +917,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< null, ), showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -886,6 +934,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< null, ), showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -906,6 +955,7 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< dueDateToConnect = System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, ), ) add( @@ -926,6 +976,51 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< System.now().plus(30.days).toLocalDateTime(TimeZone.UTC).date, ), showPayoutButton = false, + memberType = MemberType.STANDARD_MEMBER, + ), + ) + add( + Content( + isRetrying = false, + upcomingPayment = NoUpcomingPayment, + upcomingPaymentInfo = NoInfo, + ongoingCharges = emptyList(), + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, + showPayoutButton = false, + memberType = MemberType.STANDARD_TO_QASA_MEMBER, + ), + ) + add( + Content( + isRetrying = false, + upcomingPayment = NoUpcomingPayment, + upcomingPaymentInfo = NoInfo, + ongoingCharges = emptyList(), + connectedPaymentInfo = ConnectedPaymentInfo.Active, + showPayoutButton = false, + memberType = MemberType.STANDARD_TO_QASA_MEMBER, + ), + ) + add( + Content( + isRetrying = false, + upcomingPayment = NoUpcomingPayment, + upcomingPaymentInfo = NoInfo, + ongoingCharges = emptyList(), + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, + showPayoutButton = false, + memberType = MemberType.QASA_ONLY_MEMBER, + ), + ) + add( + Content( + isRetrying = false, + upcomingPayment = NoUpcomingPayment, + upcomingPaymentInfo = NoInfo, + ongoingCharges = emptyList(), + connectedPaymentInfo = ConnectedPaymentInfo.Active, + showPayoutButton = false, + memberType = MemberType.QASA_ONLY_MEMBER, ), ) }, diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt index 098044a994..7b7e00797a 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/ui/payments/PaymentsPresenter.kt @@ -11,6 +11,7 @@ import arrow.core.Either import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.demomode.Provider import com.hedvig.android.core.uidata.UiMoney +import com.hedvig.android.data.paying.member.MemberType import com.hedvig.android.feature.payments.data.ManualChargeToPrompt import com.hedvig.android.feature.payments.data.MemberCharge import com.hedvig.android.feature.payments.data.PaymentConnection @@ -84,6 +85,7 @@ internal class PaymentsPresenter( ongoingCharges = paymentOverview.ongoingCharges, connectedPaymentInfo = paymentOverview.paymentConnection.toConnectedPaymentInfo(), showPayoutButton = shouldShowPayout, + memberType = paymentOverview.memberType, ) }, ) @@ -144,6 +146,7 @@ internal sealed interface PaymentsUiState { val ongoingCharges: List, val connectedPaymentInfo: ConnectedPaymentInfo, val showPayoutButton: Boolean, + val memberType: MemberType ) : PaymentsUiState { sealed interface UpcomingPayment { data object NoUpcomingPayment : UpcomingPayment From fdb4534675871e97fe6aac59f6479e658f80e6f6 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 8 Jun 2026 16:09:51 +0200 Subject: [PATCH 3/9] change GetConnectPaymentReminderUseCase --- .../android/apollo/octopus/schema.graphqls | 17 ++++++++++-- .../paying/member/GetMemberTypeUseCase.kt | 3 ++- .../data/GetUpcomingPaymentUseCase.kt | 5 ++-- .../graphql/QueryGetPayinMethodStatus.graphql | 1 + .../GetConnectPaymentReminderUseCase.kt | 27 +++++++------------ 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls index c0d3aabc80..be520b5dfb 100644 --- a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls +++ b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls @@ -2894,7 +2894,7 @@ type MemberPaymentChargeMethodInfo { """ dueDate: Int """ - Payment provider used for charging the member, eg Trustly or Kivra. + Payment provider used for charging the member, eg Trustly or Invoice. This is used to determine the charge schedule and mandate provider. """ paymentProvider: String! @@ -2939,7 +2939,7 @@ type MemberPaymentInformation { } type MemberPaymentMethod { """ - Payment provider, eg Trustly, Swish, Nordea, Kivra etc. + Payment provider, eg Trustly, Swish, Nordea, Invoice etc. This is used as the "identifier" of the payment method since there can only be one ACTIVE or PENDING payment method per provider. """ @@ -3015,6 +3015,15 @@ type MemberPaymentMethods { The date of the month when member's premium will be charged (or due if invoice) """ chargingDay: Int + """ + Indicates which payment connection the member is missing, if any. The rule is: + - If member has only QASA-paid agreements, the payin connection is irrelevant and only payout is checked. + Returns PAYOUT if no default payout method exists, else null. + - Otherwise (any non-QASA agreement, e.g. member-paid), payin takes priority: + Returns PAYIN if no default payin method exists, else PAYOUT if no default payout method exists, else null. + - If member has no ongoing (active or future) agreements, returns null. + """ + missingConnection: MissingPaymentConnection } enum MemberPaymentMethodStatus { """ @@ -3159,6 +3168,10 @@ type MidtermChangePriceDetailItem { displayName: String! displayValue: String! } +enum MissingPaymentConnection { + PAYIN + PAYOUT +} """ A monetary value with currency. """ diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt index 0e47fc33c3..a484dc06a5 100644 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt +++ b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetMemberTypeUseCase.kt @@ -13,6 +13,7 @@ import com.hedvig.android.core.demomode.ProdOrDemoProvider import com.hedvig.android.core.demomode.Provider import com.hedvig.android.data.contract.ContractType import com.hedvig.android.data.contract.toContractType +import com.hedvig.android.logger.logcat import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn @@ -67,7 +68,7 @@ internal class GetMemberTypeUseCaseImpl( .map { it.currentAgreement.productVariant.typeOfContract.toContractType() } val onlyQasaContracts = (activeContractsTypes.isNotEmpty() - || terminatedContractsTypes.isNotEmpty()) + || terminatedContractsTypes.isNotEmpty()) && activeContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } && terminatedContractsTypes.all { it == ContractType.SE_QASA_LANDLORD } if (onlyQasaContracts) return@either MemberType.QASA_ONLY_MEMBER diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt index 6e3923fc21..c16de620ed 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt @@ -9,6 +9,7 @@ import com.apollographql.apollo.cache.normalized.fetchPolicy import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage +import com.hedvig.android.core.demomode.Provider import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.data.paying.member.GetMemberTypeUseCase @@ -39,14 +40,14 @@ internal interface GetUpcomingPaymentUseCase { internal data class GetUpcomingPaymentUseCaseImpl( val apolloClient: ApolloClient, val clock: Clock, - val getMemberTypeUseCase: GetMemberTypeUseCase, + val getMemberTypeUseCaseProvider: Provider, ) : GetUpcomingPaymentUseCase { override suspend fun invoke(): Either = either { val result = apolloClient.query(UpcomingPaymentQuery()) .fetchPolicy(FetchPolicy.NetworkFirst) .safeExecute(::ErrorMessage) .bind() - val memberType = getMemberTypeUseCase.invoke().bind() + val memberType = getMemberTypeUseCaseProvider.provide().invoke().bind() val missedChargeIdToChargeManually: String? = result.currentMember.missedChargeIdToChargeManually diff --git a/app/member-reminders/member-reminders-public/src/main/graphql/QueryGetPayinMethodStatus.graphql b/app/member-reminders/member-reminders-public/src/main/graphql/QueryGetPayinMethodStatus.graphql index 508833a63d..a812148af7 100644 --- a/app/member-reminders/member-reminders-public/src/main/graphql/QueryGetPayinMethodStatus.graphql +++ b/app/member-reminders/member-reminders-public/src/main/graphql/QueryGetPayinMethodStatus.graphql @@ -9,6 +9,7 @@ query GetPayinMethodStatus { id } paymentMethods { + missingConnection payoutMethods { status isDefault diff --git a/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt b/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt index c809022edd..bdb6a0093f 100644 --- a/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt +++ b/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt @@ -18,7 +18,7 @@ import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import kotlinx.datetime.LocalDate import octopus.GetPayinMethodStatusQuery -import octopus.type.MemberPaymentConnectionStatus +import octopus.type.MissingPaymentConnection internal interface GetConnectPaymentReminderUseCase { suspend fun invoke(): Either @@ -28,20 +28,15 @@ internal interface GetConnectPaymentReminderUseCase { @SingleIn(AppScope::class) @Inject internal class GetConnectPaymentReminderUseCaseImpl( - private val apolloClient: ApolloClient, - private val getOnlyHasNonPayingContractsUseCaseProvider: Provider, -) : GetConnectPaymentReminderUseCase { + private val apolloClient: ApolloClient) : GetConnectPaymentReminderUseCase { override suspend fun invoke(): Either { return either { - val onlyHasNonPayingContracts = getOnlyHasNonPayingContractsUseCaseProvider.provide().invoke().getOrNull() == true - ensure(onlyHasNonPayingContracts == false) { - ConnectPaymentReminderError.DomainError.NonPayingMember - } val result = apolloClient.query(GetPayinMethodStatusQuery()) .fetchPolicy(FetchPolicy.NetworkOnly) .safeExecute(::ErrorMessage) .mapLeft(ConnectPaymentReminderError::NetworkError) .bind() + val missingPaymentsContractTerminationDate = result.currentMember.activeContracts .filter { it.terminationDueToMissedPayments } .sortedBy { it.terminationDate } @@ -50,16 +45,14 @@ internal class GetConnectPaymentReminderUseCaseImpl( if (missingPaymentsContractTerminationDate != null) { return@either PaymentReminder.ShowMissingPaymentsReminder(missingPaymentsContractTerminationDate) } - val payStatus = result.currentMember.paymentInformation.status - if (payStatus == MemberPaymentConnectionStatus.NEEDS_SETUP) { - return@either PaymentReminder.ShowConnectPaymentReminder - } - val hasDefaultPayoutMethod = result.currentMember.paymentMethods.payoutMethods.any { it.isDefault } - val hasAvailableMethods = result.currentMember.paymentMethods.availableMethods.isNotEmpty() - if (!hasDefaultPayoutMethod && hasAvailableMethods) { - return@either PaymentReminder.ShowConnectPayoutReminder + + val missingConnection = result.currentMember.paymentMethods.missingConnection + when (missingConnection) { + MissingPaymentConnection.PAYIN -> return@either PaymentReminder.ShowConnectPaymentReminder + MissingPaymentConnection.PAYOUT -> return@either PaymentReminder.ShowConnectPayoutReminder + MissingPaymentConnection.UNKNOWN__ , null -> raise(ConnectPaymentReminderError.DomainError.AlreadySetup) } - raise(ConnectPaymentReminderError.DomainError.AlreadySetup) + }.onLeft { if (it !is ConnectPaymentReminderError.DomainError) { logcat { "GetConnectPaymentReminderUseCase failed with error:$it" } From e70edc09cd922aa7e1e6c476e80478abfcda9e54 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Mon, 8 Jun 2026 16:16:57 +0200 Subject: [PATCH 4/9] fix --- .../overview/data/GetUpcomingPaymentUseCase.kt | 6 +++++- .../payments/ui/payments/PaymentsDestination.kt | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt index c16de620ed..609cf35087 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/overview/data/GetUpcomingPaymentUseCase.kt @@ -22,6 +22,7 @@ import com.hedvig.android.feature.payments.data.PaymentConnection.* import com.hedvig.android.feature.payments.data.PaymentOverview import com.hedvig.android.feature.payments.data.PaymentOverview.OngoingCharge import com.hedvig.android.feature.payments.data.toFailedCharge +import com.hedvig.android.logger.logcat import dev.zacsweers.metro.Inject import kotlin.time.Clock import kotlin.time.Duration.Companion.days @@ -77,8 +78,9 @@ internal data class GetUpcomingPaymentUseCaseImpl( val paymentMethods = result.currentMember.paymentMethods val payinMethod = paymentMethods.defaultPayinMethod ?: paymentMethods.payinMethods.find { it.isDefault } + logcat {"Mariia: payinMethod $payinMethod"} val payoutMethod = paymentMethods.defaultPayoutMethod - ?: paymentMethods.payinMethods.find { it.isDefault } + ?: paymentMethods.payoutMethods.find { it.isDefault } if (payinMethod == null) { val firstKnownTerminationDateForContractTerminatedDueToMissedPayments = result .currentMember @@ -105,6 +107,8 @@ internal data class GetUpcomingPaymentUseCaseImpl( } when (payinMethod.status) { MemberPaymentMethodStatus.ACTIVE -> { + logcat {"Mariia: MemberPaymentMethodStatus.ACTIVE"} + logcat {"Mariia: payoutMethod $payoutMethod"} if (payoutMethod == null) { return@run PaymentConnection.NeedsPayoutSetup } else return@run PaymentConnection.Active 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 9b23232a56..9437a42547 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 @@ -1023,5 +1023,20 @@ private class PaymentsStatePreviewProvider : CollectionPreviewParameterProvider< memberType = MemberType.QASA_ONLY_MEMBER, ), ) + add( + Content( + isRetrying = false, + upcomingPayment = UpcomingPayment.Content( + UiMoney(100.0, SEK), + System.now().toLocalDateTime(TimeZone.UTC).date, + "w345423t6", + ), + upcomingPaymentInfo = UpcomingPaymentInfo.NoInfo, + ongoingCharges = emptyList(), + connectedPaymentInfo = ConnectedPaymentInfo.NeedsPayoutSetup, + showPayoutButton = true, + memberType = MemberType.STANDARD_MEMBER, + ), + ) }, ) From d78c24a8830adc0f18247c60dbe15cf133e49e48 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 9 Jun 2026 15:32:40 +0200 Subject: [PATCH 5/9] move from depr. cost to itemCost --- .../android/apollo/octopus/schema.graphqls | 14 +++-- .../graphql/QueryInsuranceContracts.graphql | 4 +- .../data/GetInsuranceContractsUseCase.kt | 6 +-- .../insurances/data/InsuranceContract.kt | 8 +-- .../ContractDetailDestination.kt | 53 ++++++++++--------- .../insurancedetail/yourinfo/YourInfoTab.kt | 16 +++--- 6 files changed, 58 insertions(+), 43 deletions(-) diff --git a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls index be520b5dfb..b2c618e3f9 100644 --- a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls +++ b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls @@ -359,7 +359,11 @@ type Agreement { """ The cost details of this agreement on today's date or the date it starts """ - cost: ItemCost! + cost: ItemCost! @deprecated(reason: "Use itemCost instead") + """ + The cost details of this agreement on today's date or the date it starts + """ + itemCost: ItemCost """ The date this agreement took effect. """ @@ -3738,7 +3742,7 @@ type Mutation { Optional `attribution` is merged field-by-field over `ShopSession.attribution` for this confirm only (quotes and `PriceIntentConfirmed` events use the merged result). """ - priceIntentConfirm(priceIntentId: UUID!, attribution: PriceIntentConfirmAttributionInput): PriceIntentMutationOutput! + priceIntentConfirm(priceIntentId: UUID!, attribution: PriceIntentConfirmAttributionInput, generateRecommendations: Boolean): PriceIntentMutationOutput! """ Change the start date of the given `ProductOffer`s by their ID. This is used because it's common to want to change the start date AFTER getting the offer. @@ -3981,7 +3985,11 @@ type PendingContract { """ Calculated based on the pending agreement """ - cost: ItemCost! + cost: ItemCost! @deprecated(reason: "Use itemCost instead") + """ + Calculated based on the pending agreement + """ + itemCost: ItemCost discountsDetails: ContractDiscountDetails! """ Same as `Contract.exposureDisplayName`. diff --git a/app/feature/feature-insurances/src/main/graphql/QueryInsuranceContracts.graphql b/app/feature/feature-insurances/src/main/graphql/QueryInsuranceContracts.graphql index 73ecdf2250..fd373ace75 100644 --- a/app/feature/feature-insurances/src/main/graphql/QueryInsuranceContracts.graphql +++ b/app/feature/feature-insurances/src/main/graphql/QueryInsuranceContracts.graphql @@ -36,7 +36,7 @@ query InsuranceContracts( ...MoneyFragment } } - cost { + itemCost { ...MonthlyCostFragment } basePremium { @@ -106,7 +106,7 @@ fragment AgreementFragment on Agreement { ...MoneyFragment } } - cost { + itemCost { ...MonthlyCostFragment } basePremium { diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt index 60df0cef75..47066cc6ef 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt @@ -144,7 +144,7 @@ private fun InsuranceContractsQuery.Data.CurrentMember.PendingContract.toPending premium = UiMoney.fromMoneyFragment(it.premium), ) }, - cost = this.cost.toMonthlyCost(), + cost = this.itemCost?.toMonthlyCost(), basePremium = UiMoney.fromMoneyFragment(this.basePremium), chipId = ChipIdState.NotRequired, supportsTermination = supportsTermination, @@ -185,7 +185,7 @@ private fun ContractFragment.toContract( premium = UiMoney.fromMoneyFragment(it.premium), ) }, - cost = currentAgreement.cost.toMonthlyCost(), + cost = currentAgreement.itemCost?.toMonthlyCost(), basePremium = UiMoney.fromMoneyFragment(currentAgreement.basePremium), ), upcomingInsuranceAgreement = upcomingChangedAgreement?.let { @@ -204,7 +204,7 @@ private fun ContractFragment.toContract( premium = UiMoney.fromMoneyFragment(it.premium), ) }, - cost = it.cost.toMonthlyCost(), + cost = it.itemCost?.toMonthlyCost(), basePremium = UiMoney.fromMoneyFragment(it.basePremium), ) }, diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/InsuranceContract.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/InsuranceContract.kt index b7a2aa220c..561d19dd98 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/InsuranceContract.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/InsuranceContract.kt @@ -34,7 +34,7 @@ sealed interface InsuranceContract { val existingAddons: List val availableAddons: List - val cost: MonthlyCost + val cost: MonthlyCost? val basePremium: UiMoney @@ -69,7 +69,7 @@ sealed interface InsuranceContract { override val coInsured: List = currentInsuranceAgreement.coInsured override val coOwners: List = currentInsuranceAgreement.coOwners override val addons: List? = currentInsuranceAgreement.addons - override val cost: MonthlyCost = currentInsuranceAgreement.cost + override val cost: MonthlyCost? = currentInsuranceAgreement.cost override val basePremium: UiMoney = currentInsuranceAgreement.basePremium override val supportsRemovingAddon: Boolean = existingAddons.any { it.isRemovable && it.status !is ContractAddon.Status.EndsAt @@ -86,7 +86,7 @@ sealed interface InsuranceContract { override val productVariant: ProductVariant, override val displayItems: List, override val addons: List?, - override val cost: MonthlyCost, + override val cost: MonthlyCost?, override val basePremium: UiMoney, override val chipId: ChipIdState, override val supportsTermination: Boolean, @@ -157,7 +157,7 @@ data class InsuranceAgreement( val coOwners: List, val creationCause: CreationCause, val addons: List?, - val cost: MonthlyCost, + val cost: MonthlyCost?, val basePremium: UiMoney, ) { data class CoInsured( diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/ContractDetailDestination.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/ContractDetailDestination.kt index 6a353e9066..6d6fff47a3 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/ContractDetailDestination.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/ContractDetailDestination.kt @@ -229,6 +229,7 @@ private fun ContractDetailScreen( is Success -> { val contract = state.insuranceContract + val cost = contract.cost val consumedWindowInsets = remember { MutableWindowInsets() } LazyColumn( contentPadding = WindowInsets @@ -286,30 +287,32 @@ private fun ContractDetailScreen( ) { pageIndex -> when (pageIndex) { 0 -> { - val priceInfoForBottomSheet = PriceInfoForBottomSheet( - displayItems = buildList { - add( - contract.displayName to stringResource( - Res.string.OFFER_COST_AND_PREMIUM_PERIOD_ABBREVIATION, - contract.basePremium.toString(), - ), - ) - contract.addons?.forEach { addon -> + val priceInfoForBottomSheet = if (cost!=null) { + PriceInfoForBottomSheet( + displayItems = buildList { add( - addon.addonVariant.displayName - to stringResource( + contract.displayName to stringResource( + Res.string.OFFER_COST_AND_PREMIUM_PERIOD_ABBREVIATION, + contract.basePremium.toString(), + ), + ) + contract.addons?.forEach { addon -> + add( + addon.addonVariant.displayName + to stringResource( Res.string.OFFER_COST_AND_PREMIUM_PERIOD_ABBREVIATION, addon.premium.toString(), ), - ) - } - contract.cost.discounts.forEach { discount -> - add(discount.displayName to discount.displayValue) - } - }, - totalGross = contract.cost.monthlyGross, - totalNet = contract.cost.monthlyNet, - ) + ) + } + cost.discounts.forEach { discount -> + add(discount.displayName to discount.displayValue) + } + }, + totalGross = cost.monthlyGross, + totalNet = cost.monthlyNet, + ) + } else null YourInfoTab( contractId = contract.id, coverageItems = contract.displayItems, @@ -351,11 +354,13 @@ private fun ContractDetailScreen( isTerminated = contract.isTerminated, contractHolderDisplayName = contract.contractHolderDisplayName, contractHolderSSN = contract.contractHolderSSN, - priceToShow = contract.cost.monthlyNet, - showPriceInfoIcon = contract.cost.monthlyNet != contract.cost.monthlyGross || - contract.basePremium != contract.cost.monthlyNet, + priceToShow = cost?.monthlyNet, + showPriceInfoIcon = cost!=null && (cost.monthlyNet != cost.monthlyGross || + contract.basePremium != cost.monthlyNet), onInfoIconClick = { - costBreakdownBottomSheetState.show(priceInfoForBottomSheet) + if (priceInfoForBottomSheet!=null) { + costBreakdownBottomSheetState.show(priceInfoForBottomSheet) + } }, existingAddons = contract.existingAddons, availableAddons = contract.availableAddons, diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt index 3c7a8d2fc6..d0e072c96d 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt @@ -153,7 +153,7 @@ internal fun YourInfoTab( isTerminated: Boolean, contractHolderDisplayName: String, contractHolderSSN: String?, - priceToShow: UiMoney, + priceToShow: UiMoney?, showPriceInfoIcon: Boolean, onInfoIconClick: () -> Unit, existingAddons: List, @@ -299,12 +299,14 @@ internal fun YourInfoTab( ) { Column { CoverageRows(coverageItems, Modifier.padding(horizontal = 16.dp)) - PriceRow( - priceToShow, - showPriceInfoIcon, - onInfoIconClick, - Modifier.padding(horizontal = 16.dp), - ) + if (priceToShow!=null) { + PriceRow( + priceToShow, + showPriceInfoIcon, + onInfoIconClick, + Modifier.padding(horizontal = 16.dp), + ) + } if (allowEditCoInsured && coInsured.isNotEmpty()) { HorizontalDivider(Modifier.padding(horizontal = 16.dp)) Spacer(Modifier.height(16.dp)) From 8afe907984af06f247f6e27ee833fc523cc780c3 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 9 Jun 2026 20:29:55 +0200 Subject: [PATCH 6/9] DeflectMessage stepContent --- .../android/apollo/octopus/schema.graphqls | 9 ++- .../graphql/FragmentClaimIntent.graphql | 5 ++ .../feature/claim/chat/ClaimChatViewModel.kt | 1 + .../feature/claim/chat/data/ClaimIntent.kt | 8 ++ .../feature/claim/chat/data/ClaimIntentExt.kt | 5 ++ .../claim/chat/ui/ClaimChatDestination.kt | 73 +++++++++++++------ .../insurancedetail/yourinfo/YourInfoTab.kt | 3 +- 7 files changed, 79 insertions(+), 25 deletions(-) diff --git a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls index b2c618e3f9..3e30d7a249 100644 --- a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls +++ b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/schema.graphqls @@ -1003,7 +1003,7 @@ type ClaimIntentStep { """ A union of all the different kinds of "step content". """ -union ClaimIntentStepContent = ClaimIntentStepContentForm|ClaimIntentStepContentSelect|ClaimIntentStepContentTask|ClaimIntentStepContentAudioRecording|ClaimIntentStepContentFileUpload|ClaimIntentStepContentSummary|ClaimIntentStepContentDeflection +union ClaimIntentStepContent = ClaimIntentStepContentForm|ClaimIntentStepContentSelect|ClaimIntentStepContentTask|ClaimIntentStepContentAudioRecording|ClaimIntentStepContentFileUpload|ClaimIntentStepContentSummary|ClaimIntentStepContentDeflection|ClaimIntentStepContentDeflectionMessage """ An audio recording step is one where the user is meant to record some audio. Submitted using `Mutation.claimIntentSubmitAudio`. @@ -1045,6 +1045,13 @@ type ClaimIntentStepContentDeflectionInfoBlock { title: String! description: String! } +""" +A minimal variant of a deflection step which only carries a text message - no button. +This is a terminal step - and cannot be submitted. +""" +type ClaimIntentStepContentDeflectionMessage { + message: String! +} type ClaimIntentStepContentDeflectionPartner { id: ID! imageUrl: Url diff --git a/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql b/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql index dbf85e27b0..14e8d0d94e 100644 --- a/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql +++ b/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql @@ -24,6 +24,11 @@ fragment ClaimIntentStepContentFragment on ClaimIntentStepContent { ...FileUploadFragment ...SummaryFragment ...DeflectionFragment + ...DeflectionMessageFragment +} + +fragment DeflectionMessageFragment on ClaimIntentStepContentDeflectionMessage { + message } fragment FormFragment on ClaimIntentStepContentForm { diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt index 38ace0dcbe..f870fb032b 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt @@ -1122,6 +1122,7 @@ private fun ClaimIntentStep.clearContent(): ClaimIntentStep = when (val content is StepContent.Summary, is StepContent.Task, is StepContent.Deflect, + is StepContent.DeflectMessage, StepContent.Unknown, -> this } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt index b443d59be8..6df1fd1eed 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt @@ -168,6 +168,14 @@ internal sealed interface StepContent { override val isSkippable: Boolean = false } + @Serializable + data class DeflectMessage( + @Contextual + val message: String, + ) : StepContent { + override val isSkippable: Boolean = false + } + object Unknown : StepContent { override val isSkippable: Boolean = false } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt index 81b697e6ae..191359eb00 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt @@ -15,6 +15,7 @@ import octopus.fragment.ClaimIntentStepContentFragment import octopus.fragment.ContentSelectFragment import octopus.fragment.DeflectionFragment import octopus.fragment.DeflectionInfoBlockFragment +import octopus.fragment.DeflectionMessageFragment import octopus.fragment.FileUploadFragment import octopus.fragment.FormFragment import octopus.fragment.SummaryFragment @@ -181,6 +182,10 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): ) } + is DeflectionMessageFragment -> StepContent.DeflectMessage( + message = message + ) + else -> { logcat { "ClaimIntentStepContentFragment: Unknown step" } raise(ClaimChatErrorMessage.NeedsUpdate) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index adfaeeadaa..953217abfc 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.semantics.traversalIndex import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.dropUnlessResumed import androidx.navigationevent.NavigationEventInfo import androidx.navigationevent.compose.NavigationEventHandler import androidx.navigationevent.compose.rememberNavigationEventState @@ -64,6 +65,7 @@ import com.hedvig.android.compose.ui.plus import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.design.system.hedvig.ErrorDialog import com.hedvig.android.design.system.hedvig.HedvigAlertDialog +import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress import com.hedvig.android.design.system.hedvig.HedvigText @@ -80,6 +82,8 @@ import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.logger.LogPriority import com.hedvig.android.logger.logcat import com.hedvig.feature.claim.chat.ClaimChatEvent +import com.hedvig.feature.claim.chat.ClaimChatEvent.* +import com.hedvig.feature.claim.chat.ClaimChatEvent.AudioRecording.* import com.hedvig.feature.claim.chat.ClaimChatUiState import com.hedvig.feature.claim.chat.ClaimChatViewModel import com.hedvig.feature.claim.chat.data.ClaimChatErrorMessage @@ -143,11 +147,11 @@ internal fun ClaimChatDestination( openAppSettings = openAppSettings, onNavigateToImageViewer = onNavigateToImageViewer, navigateToClaimOutcome = { - claimChatViewModel.emit(ClaimChatEvent.HandledOutcomeNavigation) + claimChatViewModel.emit(HandledOutcomeNavigation) navigateToClaimOutcome(it) }, navigateToDeflect = { stepId, deflect -> - claimChatViewModel.emit(ClaimChatEvent.HandledDeflectNavigation(stepId)) + claimChatViewModel.emit(HandledDeflectNavigation(stepId)) navigateToDeflect(deflect) }, appPackageId = appPackageId, @@ -177,7 +181,7 @@ internal fun ClaimChatScreenContent( when (uiState) { ClaimChatUiState.FailedToStart -> { HedvigErrorSection( - { claimChatViewModel.emit(ClaimChatEvent.RetryInitializing) }, + { claimChatViewModel.emit(RetryInitializing) }, ) } @@ -227,11 +231,11 @@ private fun ClaimChatScreen( freeTextHint = stringResource(Res.string.CLAIMS_TEXT_INPUT_POPOVER_PLACEHOLDER), freeTextTitle = stringResource(Res.string.CLAIMS_TEXT_INPUT_PLACEHOLDER), freeTextOnCancelClick = { - onEvent(ClaimChatEvent.CloseFreeChatOverlay) + onEvent(CloseFreeChatOverlay) }, freeTextOnSaveClick = { feedback -> - onEvent(ClaimChatEvent.UpdateFreeText(feedback)) - onEvent(ClaimChatEvent.CloseFreeChatOverlay) + onEvent(UpdateFreeText(feedback)) + onEvent(CloseFreeChatOverlay) }, shouldShowOverlay = uiState.showFreeTextOverlay != null, overlaidContent = { @@ -283,7 +287,7 @@ private fun ClaimChatScreenContent( title = stringResource(Res.string.general_error), message = stringResource(messageRes), onDismiss = { - onEvent(ClaimChatEvent.DismissErrorDialog) + onEvent(DismissErrorDialog) }, buttonText = when (uiState.errorSubmittingStep) { ClaimChatErrorMessage.NeedsUpdate -> stringResource(Res.string.EMBARK_UPDATE_APP_BUTTON) @@ -302,10 +306,10 @@ private fun ClaimChatScreenContent( confirmButtonLabel = stringResource(Res.string.CLAIM_CHAT_EDIT_ANSWER_BUTTON), dismissButtonLabel = stringResource(Res.string.general_cancel_button), onDismissRequest = { - onEvent(ClaimChatEvent.DismissConfirmEditDialog) + onEvent(DismissConfirmEditDialog) }, onConfirmClick = { - onEvent(ClaimChatEvent.Regret(uiState.showConfirmEditDialogForStep)) + onEvent(Regret(uiState.showConfirmEditDialogForStep)) }, ) } @@ -392,6 +396,13 @@ private fun ClaimChatScreenContent( imageLoader = imageLoader, openAppSettings = openAppSettings, modifier = Modifier.fillMaxSize(), + closeFlow = { + if (uiState.steps.size > 1) { + showCloseFlowDialog = true + } else { + navigateUp() + } + } ) } if (showScrollArrow) { @@ -430,6 +441,7 @@ private fun ClaimChatScrollableContent( appPackageId: String, imageLoader: ImageLoader, openAppSettings: () -> Unit, + closeFlow: () -> Unit, modifier: Modifier = Modifier, ) { val density = LocalDensity.current @@ -491,6 +503,7 @@ private fun ClaimChatScrollableContent( } else { Modifier }, + closeFlow = closeFlow ) } } @@ -540,6 +553,7 @@ private fun StepContentSection( appPackageId: String, imageLoader: ImageLoader, openAppSettings: () -> Unit, + closeFlow: () -> Unit, onResponseHeightChanged: (IntSize) -> Unit, modifier: Modifier = Modifier, ) { @@ -569,7 +583,7 @@ private fun StepContentSection( if (showBottomContent && isAnimationInProcess) { delay(bottomContentAnimationDuration.toLong()) isAnimationInProcess = false - onEvent(ClaimChatEvent.AddToShownAnimations(stepItem.id)) + onEvent(AddToShownAnimations(stepItem.id)) } } @@ -584,7 +598,7 @@ private fun StepContentSection( isAnimationComplete = !isAnimationInProcess, onAnimationFinished = { showBottomContent = true - onEvent(ClaimChatEvent.FinishTaskAnimation) + onEvent(FinishTaskAnimation) }, onNavigateToImageViewer = onNavigateToImageViewer, imageLoader = imageLoader, @@ -617,6 +631,7 @@ private fun StepContentSection( modifier = Modifier.onSizeChanged { size -> onResponseHeightChanged(size) }, + closeFlow = closeFlow, ) } } @@ -750,6 +765,7 @@ private fun StepBottomContent( appPackageId: String, imageLoader: ImageLoader, openAppSettings: () -> Unit, + closeFlow: ()-> Unit, modifier: Modifier = Modifier, ) { Column(modifier) { @@ -759,31 +775,31 @@ private fun StepBottomContent( item = stepItem, stepContent = stepItem.stepContent, onShowFreeText = { - onEvent(ClaimChatEvent.AudioRecording.SwitchToFreeText(stepItem.id)) + onEvent(SwitchToFreeText(stepItem.id)) }, onSwitchToAudioRecording = { - onEvent(ClaimChatEvent.AudioRecording.SwitchToAudioRecording(stepItem.id)) + onEvent(SwitchToAudioRecording(stepItem.id)) }, onLaunchFullScreenEditText = { restrictions -> - onEvent(ClaimChatEvent.OpenFreeTextOverlay(restrictions)) + onEvent(OpenFreeTextOverlay(restrictions)) }, startRecording = { - onEvent(ClaimChatEvent.AudioRecording.StartRecording(stepItem.id)) + onEvent(StartRecording(stepItem.id)) }, stopRecording = { - onEvent(ClaimChatEvent.AudioRecording.StopRecording(stepItem.id)) + onEvent(StopRecording(stepItem.id)) }, redoRecording = { - onEvent(ClaimChatEvent.AudioRecording.RedoRecording(stepItem.id)) + onEvent(RedoRecording(stepItem.id)) }, submitFreeText = { - onEvent(ClaimChatEvent.AudioRecording.SubmitTextInput(stepItem.id)) + onEvent(SubmitTextInput(stepItem.id)) }, submitAudioFile = { - onEvent(ClaimChatEvent.AudioRecording.SubmitAudioFile(stepItem.id)) + onEvent(SubmitAudioFile(stepItem.id)) }, onSkip = { - onEvent(ClaimChatEvent.Skip(stepItem.id)) + onEvent(Skip(stepItem.id)) }, isCurrentStep = isCurrentStep, clock = Clock.System, @@ -805,7 +821,7 @@ private fun StepBottomContent( currentContinueButtonLoading = currentContinueButtonLoading, canSkip = stepItem.stepContent.isSkippable, onSkip = { - onEvent(ClaimChatEvent.Skip(stepItem.id)) + onEvent(Skip(stepItem.id)) }, skipButtonLoading = currentSkipButtonLoading, stepContent = stepItem.stepContent, @@ -847,7 +863,7 @@ private fun StepBottomContent( is StepContent.Summary -> { ChatClaimSummaryBottomContent( onSubmit = { - onEvent(ClaimChatEvent.SubmitClaim(stepItem.id)) + onEvent(SubmitClaim(stepItem.id)) }, isCurrentStep = isCurrentStep, continueButtonLoading = currentContinueButtonLoading, @@ -868,7 +884,7 @@ private fun StepBottomContent( TaskStepBottomContent( stepItem.stepContent, onRetrySubmittingTask = { - onEvent(ClaimChatEvent.RetrySubmittingTaskStep(stepItem.id)) + onEvent(RetrySubmittingTaskStep(stepItem.id)) }, modifier = Modifier.fillMaxWidth(), ) @@ -879,6 +895,17 @@ private fun StepBottomContent( logcat(LogPriority.ERROR) { "StepContent.Unknown received in StepBottomContent" } } } + + is StepContent.DeflectMessage -> { + HedvigButton( + modifier = modifier, + text = stringResource(Res.string.general_close_button), + onClick = dropUnlessResumed { + closeFlow() + }, + enabled = true, + ) + } } } } diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt index d0e072c96d..69a4124630 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/insurancedetail/yourinfo/YourInfoTab.kt @@ -199,7 +199,8 @@ internal fun YourInfoTab( } val upcomingChangesBottomSheet = rememberHedvigBottomSheetState() - if (upcomingChangesInsuranceAgreement != null) { + val upcomingChangesCost = upcomingChangesInsuranceAgreement?.cost + if (upcomingChangesInsuranceAgreement != null && upcomingChangesCost!=null) { val upcomingPriceInfoForBottomSheet = PriceInfoForBottomSheet( displayItems = buildList { add( From e81d5113ec86bfdbf938c05e538d4c601f76c72e Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 10 Jun 2026 10:06:11 +0200 Subject: [PATCH 7/9] fix tests --- .../GetConnectPaymentReminderUseCaseTest.kt | 98 +++++++------------ 1 file changed, 34 insertions(+), 64 deletions(-) diff --git a/app/member-reminders/member-reminders-public/src/test/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCaseTest.kt b/app/member-reminders/member-reminders-public/src/test/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCaseTest.kt index 904b77e080..10568df5eb 100644 --- a/app/member-reminders/member-reminders-public/src/test/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCaseTest.kt +++ b/app/member-reminders/member-reminders-public/src/test/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCaseTest.kt @@ -1,7 +1,5 @@ package com.hedvig.android.memberreminders -import arrow.core.Either -import arrow.core.right import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf @@ -12,21 +10,19 @@ import com.apollographql.apollo.testing.enqueueTestResponse import com.google.testing.junit.testparameterinjector.TestParameterInjector import com.hedvig.android.apollo.octopus.test.OctopusFakeResolver import com.hedvig.android.apollo.test.TestApolloClientRule -import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.test.isLeft import com.hedvig.android.core.common.test.isRight -import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.data.paying.member.GetOnlyHasNonPayingContractsUseCase import com.hedvig.android.logger.TestLogcatLoggingRule import com.hedvig.android.memberreminders.PaymentReminder.ShowConnectPaymentReminder +import com.hedvig.android.memberreminders.PaymentReminder.ShowConnectPayoutReminder import com.hedvig.android.memberreminders.PaymentReminder.ShowMissingPaymentsReminder import kotlinx.coroutines.test.runTest import kotlinx.datetime.LocalDate import octopus.GetPayinMethodStatusQuery -import octopus.type.MemberPaymentConnectionStatus +import octopus.type.MissingPaymentConnection import octopus.type.buildContract import octopus.type.buildMember -import octopus.type.buildMemberPaymentInformation +import octopus.type.buildMemberPaymentMethods import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -43,17 +39,14 @@ class GetConnectPaymentReminderUseCaseTest { get() = testApolloClientRule.apolloClient @Test - fun `when payin method needs setup, show the reminder`() = runTest { - val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl( - apolloClient, - doesHavePayingContractsUseCaseProvider, - ) + fun `when payin connection is missing, show the connect payment reminder`() = runTest { + val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl(apolloClient) apolloClient.enqueueTestResponse( GetPayinMethodStatusQuery(), GetPayinMethodStatusQuery.Data(OctopusFakeResolver) { currentMember = buildMember { - paymentInformation = buildMemberPaymentInformation { - status = MemberPaymentConnectionStatus.NEEDS_SETUP + paymentMethods = buildMemberPaymentMethods { + missingConnection = MissingPaymentConnection.PAYIN } } }, @@ -65,19 +58,32 @@ class GetConnectPaymentReminderUseCaseTest { } @Test - fun `when payin method does not need setup but is missing payments, show the missing payment reminder`() = runTest { - val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl( - apolloClient, - doesHavePayingContractsUseCaseProvider, + fun `when payout connection is missing, show the connect payout reminder`() = runTest { + val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl(apolloClient) + apolloClient.enqueueTestResponse( + GetPayinMethodStatusQuery(), + GetPayinMethodStatusQuery.Data(OctopusFakeResolver) { + currentMember = buildMember { + paymentMethods = buildMemberPaymentMethods { + missingConnection = MissingPaymentConnection.PAYOUT + } + } + }, ) + + val result = getConnectPaymentReminderUseCase.invoke() + + assertThat(result).isRight().isEqualTo(ShowConnectPayoutReminder) + } + + @Test + fun `when a contract is being terminated due to missed payments, show the missing payment reminder`() = runTest { + val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl(apolloClient) val terminationDate = LocalDate.parse("2030-01-01") apolloClient.enqueueTestResponse( GetPayinMethodStatusQuery(), GetPayinMethodStatusQuery.Data(OctopusFakeResolver) { currentMember = buildMember { - paymentInformation = buildMemberPaymentInformation { - status = MemberPaymentConnectionStatus.ACTIVE - } this.activeContracts = listOf( this.buildContract { this.terminationDueToMissedPayments = true @@ -94,17 +100,14 @@ class GetConnectPaymentReminderUseCaseTest { } @Test - fun `with the feature flag on but payment already connected, don't get a 'ShowReminder' response`() = runTest { - val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl( - apolloClient, - doesHavePayingContractsUseCaseProvider, - ) + fun `when no payment connection is missing, don't get a 'ShowReminder' response`() = runTest { + val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl(apolloClient) apolloClient.enqueueTestResponse( GetPayinMethodStatusQuery(), GetPayinMethodStatusQuery.Data(OctopusFakeResolver) { currentMember = buildMember { - paymentInformation = buildMemberPaymentInformation { - status = MemberPaymentConnectionStatus.ACTIVE + paymentMethods = buildMemberPaymentMethods { + missingConnection = null } } }, @@ -116,11 +119,8 @@ class GetConnectPaymentReminderUseCaseTest { } @Test - fun `with the feature flag on but network failure, don't get a 'ShowReminder' response`() = runTest { - val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl( - apolloClient, - doesHavePayingContractsUseCaseProvider, - ) + fun `on network failure, don't get a 'ShowReminder' response`() = runTest { + val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl(apolloClient) apolloClient.enqueueTestNetworkError() val result = getConnectPaymentReminderUseCase.invoke() @@ -129,34 +129,4 @@ class GetConnectPaymentReminderUseCaseTest { .isLeft() .isInstanceOf() } - - @Test - fun `for a non paying member, don't remind them to connect payment`() = runTest { - val getConnectPaymentReminderUseCase = GetConnectPaymentReminderUseCaseImpl( - apolloClient, - onlyHasNonPayingContractsUseCaseProvider, - ) - - val result = getConnectPaymentReminderUseCase.invoke() - - assertThat(result) - .isLeft() - .isInstanceOf() - } - - private val doesHavePayingContractsUseCaseProvider: Provider = Provider { - object : GetOnlyHasNonPayingContractsUseCase { - override suspend fun invoke(): Either { - return false.right() - } - } - } - - private val onlyHasNonPayingContractsUseCaseProvider: Provider = Provider { - object : GetOnlyHasNonPayingContractsUseCase { - override suspend fun invoke(): Either { - return true.right() - } - } - } -} +} \ No newline at end of file From 230f0500c8380cbd4056c0265f82556eb6bc72ec Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 10 Jun 2026 13:55:24 +0200 Subject: [PATCH 8/9] remove GetOnlyHasNonPayingContractsUseCase --- .../com/hedvig/android/app/MainActivity.kt | 5 --- .../com/hedvig/android/app/ui/HedvigApp.kt | 3 -- .../hedvig/android/app/ui/HedvigAppState.kt | 24 ++----------- .../member/GetChatRepositoryProvider.kt | 19 ---------- .../GetOnlyHasNonPayingContractsUseCase.kt | 11 ------ ...GetOnlyHasNonPayingContractsUseCaseDemo.kt | 13 ------- ...GetOnlyHasNonPayingContractsUseCaseImpl.kt | 35 ------------------- .../data/GetDiscountsOverviewUseCase.kt | 12 ++----- .../GetConnectPaymentReminderUseCase.kt | 1 - 9 files changed, 5 insertions(+), 118 deletions(-) delete mode 100644 app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetChatRepositoryProvider.kt delete mode 100644 app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCase.kt delete mode 100644 app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseDemo.kt delete mode 100644 app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseImpl.kt diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/MainActivity.kt b/app/app/src/main/kotlin/com/hedvig/android/app/MainActivity.kt index cbc504f88b..2d4d11d53e 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/MainActivity.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/MainActivity.kt @@ -41,7 +41,6 @@ import com.hedvig.android.core.buildconstants.HedvigBuildConstants import com.hedvig.android.core.demomode.DemoManager import com.hedvig.android.core.demomode.Provider import com.hedvig.android.core.rive.RiveInitializer -import com.hedvig.android.data.paying.member.GetOnlyHasNonPayingContractsUseCase import com.hedvig.android.data.settings.datastore.SettingsDataStore import com.hedvig.android.featureflags.FeatureManager import com.hedvig.android.language.LanguageLaunchCheckUseCase @@ -70,9 +69,6 @@ class MainActivity : AppCompatActivity() { @Inject private lateinit var featureManager: FeatureManager - @Inject - private lateinit var getOnlyHasNonPayingContractsUseCase: Provider - @Inject private lateinit var hedvigBuildConstants: HedvigBuildConstants @@ -206,7 +202,6 @@ class MainActivity : AppCompatActivity() { deepLinkChannel = deepLinkChannel, windowSizeClass = windowSizeClass, settingsDataStore = settingsDataStore, - getOnlyHasNonPayingContractsUseCase = getOnlyHasNonPayingContractsUseCase, featureManager = featureManager, splashIsRemovedSignal = splashIsRemovedSignal, authTokenService = authTokenService, diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigApp.kt b/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigApp.kt index de643d6f54..e6bc98ab17 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigApp.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigApp.kt @@ -60,7 +60,6 @@ import com.hedvig.android.core.appreview.WaitUntilAppReviewDialogShouldBeOpenedU import com.hedvig.android.core.buildconstants.HedvigBuildConstants import com.hedvig.android.core.demomode.DemoManager import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.data.paying.member.GetOnlyHasNonPayingContractsUseCase import com.hedvig.android.data.settings.datastore.SettingsDataStore import com.hedvig.android.design.system.hedvig.DemoModeLabel import com.hedvig.android.design.system.hedvig.Surface @@ -95,7 +94,6 @@ internal fun HedvigApp( deepLinkChannel: Channel, windowSizeClass: WindowSizeClass, settingsDataStore: SettingsDataStore, - getOnlyHasNonPayingContractsUseCase: Provider, featureManager: FeatureManager, splashIsRemovedSignal: Channel, authTokenService: AuthTokenService, @@ -118,7 +116,6 @@ internal fun HedvigApp( backstackController = backstackController, windowSizeClass = windowSizeClass, settingsDataStore = settingsDataStore, - getOnlyHasNonPayingContractsUseCase = getOnlyHasNonPayingContractsUseCase, featureManager = featureManager, missedPaymentNotificationServiceProvider = missedPaymentNotificationServiceProvider, ) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigAppState.kt b/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigAppState.kt index e2773d8a9f..7fe5ee0dac 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigAppState.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigAppState.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import com.hedvig.android.app.navigation.BackstackController import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.data.paying.member.GetOnlyHasNonPayingContractsUseCase import com.hedvig.android.data.settings.datastore.SettingsDataStore import com.hedvig.android.featureflags.FeatureManager import com.hedvig.android.featureflags.flags.Feature @@ -22,6 +21,7 @@ import com.hedvig.android.navigation.compose.NavigationSuiteType import com.hedvig.android.notification.badge.data.payment.MissedPaymentNotificationService import com.hedvig.android.theme.Theme import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.emitAll @@ -33,7 +33,6 @@ internal fun rememberHedvigAppState( backstackController: BackstackController, windowSizeClass: WindowSizeClass, settingsDataStore: SettingsDataStore, - getOnlyHasNonPayingContractsUseCase: Provider, featureManager: FeatureManager, missedPaymentNotificationServiceProvider: Provider, coroutineScope: CoroutineScope = rememberCoroutineScope(), @@ -43,7 +42,6 @@ internal fun rememberHedvigAppState( windowSizeClass, coroutineScope, settingsDataStore, - getOnlyHasNonPayingContractsUseCase, featureManager, missedPaymentNotificationServiceProvider, ) { @@ -52,7 +50,6 @@ internal fun rememberHedvigAppState( windowSizeClass = windowSizeClass, coroutineScope = coroutineScope, settingsDataStore = settingsDataStore, - getOnlyHasNonPayingContractsUseCase = getOnlyHasNonPayingContractsUseCase, featureManager = featureManager, missedPaymentNotificationServiceProvider = missedPaymentNotificationServiceProvider, ) @@ -66,7 +63,6 @@ internal class HedvigAppState( val windowSizeClass: WindowSizeClass, coroutineScope: CoroutineScope, private val settingsDataStore: SettingsDataStore, - getOnlyHasNonPayingContractsUseCase: Provider, featureManager: FeatureManager, missedPaymentNotificationServiceProvider: Provider, ) { @@ -94,25 +90,11 @@ internal class HedvigAppState( val isInScreenEligibleForCrossSells: Boolean get() = backstackController.currentDestination is CrossSellEligibleDestination - val topLevelTabs: StateFlow> = flow { - val onlyHasNonPayingContracts = getOnlyHasNonPayingContractsUseCase.provide().invoke().getOrNull() - emit( - buildList { - add(TopLevelTab.Home) - add(TopLevelTab.Insurances) - if (onlyHasNonPayingContracts != true) { - add(TopLevelTab.Forever) - } - add(TopLevelTab.Payments) - add(TopLevelTab.Profile) - }.toSet(), - ) - }.stateIn( - coroutineScope, - SharingStarted.Eagerly, + val topLevelTabs: StateFlow> = MutableStateFlow( setOf( TopLevelTab.Home, TopLevelTab.Insurances, + TopLevelTab.Forever, TopLevelTab.Payments, TopLevelTab.Profile, ), diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetChatRepositoryProvider.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetChatRepositoryProvider.kt deleted file mode 100644 index 91dce5a1aa..0000000000 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetChatRepositoryProvider.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.hedvig.android.data.paying.member - -import com.hedvig.android.core.common.di.AppScope -import com.hedvig.android.core.demomode.DemoManager -import com.hedvig.android.core.demomode.ProdOrDemoProvider -import com.hedvig.android.core.demomode.Provider -import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn -import dev.zacsweers.metro.binding - -@Inject -@SingleIn(AppScope::class) -@ContributesBinding(AppScope::class, binding>()) -internal class GetOnlyHasNonPayingContractsUseCaseProvider( - override val demoManager: DemoManager, - override val demoImpl: GetOnlyHasNonPayingContractsUseCaseDemo, - override val prodImpl: GetOnlyHasNonPayingContractsUseCaseImpl, -) : ProdOrDemoProvider diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCase.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCase.kt deleted file mode 100644 index 6ec41dd2ac..0000000000 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.hedvig.android.data.paying.member - -import arrow.core.Either -import com.hedvig.android.core.common.ErrorMessage - -/** - * Returns true when all the member's active contracts are non-paying contracts. - */ -interface GetOnlyHasNonPayingContractsUseCase { - suspend fun invoke(): Either -} diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseDemo.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseDemo.kt deleted file mode 100644 index bb73bb5ad8..0000000000 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseDemo.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hedvig.android.data.paying.member - -import arrow.core.Either -import arrow.core.right -import com.hedvig.android.core.common.ErrorMessage -import dev.zacsweers.metro.Inject - -@Inject -internal class GetOnlyHasNonPayingContractsUseCaseDemo : GetOnlyHasNonPayingContractsUseCase { - override suspend fun invoke(): Either { - return false.right() - } -} diff --git a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseImpl.kt b/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseImpl.kt deleted file mode 100644 index 9278462fe6..0000000000 --- a/app/data/data-paying-member/src/main/kotlin/com/hedvig/android/data/paying/member/GetOnlyHasNonPayingContractsUseCaseImpl.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.hedvig.android.data.paying.member - -import arrow.core.Either -import arrow.core.raise.either -import com.apollographql.apollo.ApolloClient -import com.hedvig.android.apollo.ErrorMessage -import com.hedvig.android.apollo.safeExecute -import com.hedvig.android.core.common.ErrorMessage -import com.hedvig.android.data.contract.ContractType -import com.hedvig.android.data.contract.toContractType -import dev.zacsweers.metro.Inject -import octopus.ActiveInsuranceContractTypesQuery - -@Inject -internal class GetOnlyHasNonPayingContractsUseCaseImpl( - private val apolloClient: ApolloClient, -) : GetOnlyHasNonPayingContractsUseCase { - override suspend fun invoke(): Either { - return either { - val contractTypes: List = - apolloClient.query(ActiveInsuranceContractTypesQuery()) - .safeExecute(::ErrorMessage) - .bind() - .currentMember - .activeContracts - .map { it.currentAgreement.productVariant.typeOfContract.toContractType() } - - contractTypes.all { it.isNonPayingContractType() } - } - } -} - -private fun ContractType.isNonPayingContractType(): Boolean { - return this == ContractType.SE_QASA_SHORT_TERM_RENTAL || this == ContractType.SE_QASA_LONG_TERM_RENTAL -} diff --git a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetDiscountsOverviewUseCase.kt b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetDiscountsOverviewUseCase.kt index 7718844f7f..e72e6b8c63 100644 --- a/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetDiscountsOverviewUseCase.kt +++ b/app/feature/feature-payments/src/main/kotlin/com/hedvig/android/feature/payments/data/GetDiscountsOverviewUseCase.kt @@ -5,8 +5,6 @@ import arrow.core.raise.either import arrow.fx.coroutines.parZip import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope -import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.data.paying.member.GetOnlyHasNonPayingContractsUseCase import com.hedvig.android.feature.payments.overview.data.ForeverInformation import com.hedvig.android.feature.payments.overview.data.GetForeverInformationUseCase import dev.zacsweers.metro.ContributesBinding @@ -23,22 +21,16 @@ internal interface GetDiscountsOverviewUseCase { internal class GetDiscountsOverviewUseCaseImpl( private val getDiscountsUseCase: GetDiscountsUseCase, private val getForeverInformationUseCase: GetForeverInformationUseCase, - private val getOnlyHasNonPayingContractsUseCaseProvider: Provider, ) : GetDiscountsOverviewUseCase { override suspend fun invoke(): Either { return either { parZip( { getForeverInformationUseCase.invoke().bind() }, { getDiscountsUseCase.invoke().bind() }, - { getOnlyHasNonPayingContractsUseCaseProvider.provide().invoke().bind() }, - ) { foreverInformation, discountedContracts, onlyHasNonPayingContracts -> + ) { foreverInformation, discountedContracts -> DiscountsOverview( discountedContracts = discountedContracts, - foreverInformation = if (onlyHasNonPayingContracts) { - null - } else { - foreverInformation - }, + foreverInformation = foreverInformation, ) } } diff --git a/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt b/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt index bdb6a0093f..2aebad6462 100644 --- a/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt +++ b/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetConnectPaymentReminderUseCase.kt @@ -11,7 +11,6 @@ import com.hedvig.android.apollo.safeExecute import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.common.di.AppScope import com.hedvig.android.core.demomode.Provider -import com.hedvig.android.data.paying.member.GetOnlyHasNonPayingContractsUseCase import com.hedvig.android.logger.logcat import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject From f0a1f1e2784231296794eed5ec8318dd93938776 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Fri, 12 Jun 2026 10:44:44 +0200 Subject: [PATCH 9/9] button style --- .../com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index 953217abfc..449d2f0b2f 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -63,6 +63,7 @@ import androidx.navigationevent.compose.rememberNavigationEventState import coil3.ImageLoader import com.hedvig.android.compose.ui.plus import com.hedvig.android.core.uidata.UiFile +import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.ErrorDialog import com.hedvig.android.design.system.hedvig.HedvigAlertDialog import com.hedvig.android.design.system.hedvig.HedvigButton @@ -898,12 +899,13 @@ private fun StepBottomContent( is StepContent.DeflectMessage -> { HedvigButton( - modifier = modifier, + modifier = modifier.fillMaxWidth(), text = stringResource(Res.string.general_close_button), onClick = dropUnlessResumed { closeFlow() }, enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary ) } }