Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ dependencies {
implementation(projects.designSystemInternals)
implementation(projects.featureAddonPurchase)
implementation(projects.featurePurchaseApartment)
implementation(projects.featurePurchaseCar)
implementation(projects.purchaseCommon)
implementation(projects.featureChat)
implementation(projects.featureChooseTier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import com.hedvig.android.feature.movingflow.di.movingFlowModule
import com.hedvig.android.feature.payments.di.paymentsModule
import com.hedvig.android.feature.profile.di.profileModule
import com.hedvig.android.feature.purchase.apartment.di.apartmentPurchaseModule
import com.hedvig.android.feature.purchase.car.di.carPurchaseModule
import com.hedvig.android.feature.purchase.common.di.purchaseCommonModule
import com.hedvig.android.feature.terminateinsurance.di.terminateInsuranceModule
import com.hedvig.android.feature.travelcertificate.di.travelCertificateModule
Expand Down Expand Up @@ -293,6 +294,7 @@ val applicationModule = module {
addonPurchaseModule,
addonRemovalModule,
apartmentPurchaseModule,
carPurchaseModule,
androidPermissionModule,
apolloAuthListenersModule,
appModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ import com.hedvig.android.feature.profile.navigation.ProfileDestination
import com.hedvig.android.feature.profile.tab.profileGraph
import com.hedvig.android.feature.purchase.apartment.navigation.ApartmentPurchaseGraphDestination
import com.hedvig.android.feature.purchase.apartment.navigation.apartmentPurchaseNavGraph
import com.hedvig.android.feature.purchase.car.navigation.CarPurchaseGraphDestination
import com.hedvig.android.feature.purchase.car.navigation.carPurchaseNavGraph
import com.hedvig.android.feature.purchase.common.navigation.PurchaseCommonDestination
import com.hedvig.android.feature.purchase.common.ui.success.PurchaseSuccessDestination
import com.hedvig.android.feature.terminateinsurance.navigation.TerminateInsuranceGraphDestination
Expand Down Expand Up @@ -332,6 +334,9 @@ internal fun HedvigNavHost(
onNavigateToApartmentPurchase = { productName ->
navController.navigate(ApartmentPurchaseGraphDestination(productName))
},
onNavigateToCarPurchase = { productName ->
navController.navigate(CarPurchaseGraphDestination(productName))
},
)
foreverGraph(
hedvigDeepLinkContainer = hedvigDeepLinkContainer,
Expand Down Expand Up @@ -493,6 +498,12 @@ internal fun HedvigNavHost(
finishApp = finishApp,
crossSellAfterFlowRepository = crossSellAfterFlowRepository,
)
carPurchaseNavGraph(
navController = navController,
popBackStack = popBackStackOrFinish,
finishApp = finishApp,
crossSellAfterFlowRepository = crossSellAfterFlowRepository,
)
navdestination<PurchaseCommonDestination.Success> { backStackEntry ->
val route = backStackEntry.toRoute<PurchaseCommonDestination.Success>()
PurchaseSuccessDestination(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fun NavGraphBuilder.insuranceGraph(
onNavigateToRemoveAddon: (ContractId?, AddonVariant?) -> Unit,
navigateToUpgradeAddon: (ContractId?, AddonVariant?) -> Unit,
onNavigateToApartmentPurchase: (productName: String) -> Unit,
onNavigateToCarPurchase: (productName: String) -> Unit,
) {
navgraph<InsurancesDestination.Graph>(
startDestination = InsurancesDestination.Insurances::class,
Expand All @@ -62,8 +63,21 @@ fun NavGraphBuilder.insuranceGraph(
navController.navigate(InsurancesDestinations.InsuranceContractDetail(contractId))
},
onCrossSellClick = dropUnlessResumed { url: String ->
// Hardcoded for testing: route all cross-sells to in-app purchase
onNavigateToApartmentPurchase("SE_APARTMENT_RENT")
val decoded = try {
URLDecoder.decode(url, "UTF-8")
} catch (_: Exception) {
url
}
val lower = decoded.lowercase()
when {
"car-insurance" in lower || "bilforsakring" in lower ->
onNavigateToCarPurchase("SE_CAR")
"bostadsratt" in lower || "home-insurance/homeowner" in lower ->
onNavigateToApartmentPurchase("SE_APARTMENT_BRF")
"hyresratt" in lower || "home-insurance" in lower || "hemforsakring" in lower ->
onNavigateToApartmentPurchase("SE_APARTMENT_RENT")
else -> openUrl(url)
}
},
navigateToCancelledInsurances = dropUnlessResumed {
navController.navigate(InsurancesDestinations.TerminatedInsurances)
Expand Down Expand Up @@ -123,16 +137,3 @@ fun NavGraphBuilder.insuranceGraph(
}
}

private fun parseApartmentProductFromUrl(url: String): String? {
val decodedUrl = try {
URLDecoder.decode(url, "UTF-8")
} catch (_: Exception) {
url
}
val lowerUrl = decodedUrl.lowercase()
return when {
lowerUrl.contains("hyresratt") || lowerUrl.contains("home-insurance/rental") -> "SE_APARTMENT_RENT"
lowerUrl.contains("bostadsratt") || lowerUrl.contains("home-insurance/homeowner") -> "SE_APARTMENT_BRF"
else -> null
}
}
40 changes: 40 additions & 0 deletions app/feature/feature-purchase-car/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
id("hedvig.android.library")
id("hedvig.gradle.plugin")
}

hedvig {
apollo("octopus")
serialization()
compose()
}

android {
testOptions.unitTests.isReturnDefaultValues = true
}

dependencies {
api(libs.androidx.navigation.common)

implementation(libs.androidx.navigation.compose)
implementation(libs.arrow.core)
implementation(libs.arrow.fx)
implementation(libs.jetbrains.lifecycle.runtime.compose)
implementation(libs.koin.composeViewModel)
implementation(libs.koin.core)
implementation(libs.kotlinx.serialization.core)
implementation(projects.apolloCore)
implementation(projects.apolloOctopusPublic)
implementation(projects.composeUi)
implementation(projects.coreCommonPublic)
implementation(projects.coreResources)
implementation(projects.coreUiData)
implementation(projects.dataCrossSellAfterFlow)
implementation(projects.designSystemHedvig)
implementation(projects.purchaseCommon)
implementation(projects.moleculePublic)
implementation(projects.navigationCommon)
implementation(projects.navigationCompose)
implementation(projects.navigationComposeTyped)
implementation(projects.navigationCore)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query CarMemberContactInfo {
currentMember {
id
ssn
email
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mutation CarPriceIntentConfirm($priceIntentId: UUID!) {
priceIntentConfirm(priceIntentId: $priceIntentId) {
priceIntent {
id
offers {
...CarProductOfferFragment
}
}
userError {
message
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation CarPriceIntentCreate($shopSessionId: UUID!, $productName: String!) {
priceIntentCreate(input: { shopSessionId: $shopSessionId, productName: $productName }) {
id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mutation CarPriceIntentDataUpdate($priceIntentId: UUID!, $data: PricingFormData!) {
priceIntentDataUpdate(priceIntentId: $priceIntentId, data: $data) {
priceIntent {
id
}
userError {
message
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
fragment CarProductOfferFragment on ProductOffer {
id
variant {
displayName
displayNameSubtype
displayNameTier
tierDescription
typeOfContract
perils {
title
description
colorCode
covered
info
}
documents {
type
displayName
url
}
}
cost {
gross {
...MoneyFragment
}
net {
...MoneyFragment
}
discountsV2 {
amount {
...MoneyFragment
}
}
}
startDate
deductible {
displayName
amount
}
usps
exposure {
displayNameShort
}
bundleDiscount {
isEligible
potentialYearlySavings {
...MoneyFragment
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation CarShopSessionCreate($countryCode: CountryCode!) {
shopSessionCreate(input: { countryCode: $countryCode }) {
id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.hedvig.android.feature.purchase.car.data

import com.hedvig.android.core.uidata.UiMoney

internal data class SessionAndIntent(
val shopSessionId: String,
val priceIntentId: String,
val ssn: String,
val email: String,
)

internal data class CarOffers(
val productDisplayName: String,
val offers: List<CarTierOffer>,
)

internal data class CarTierOffer(
val offerId: String,
val tierDisplayName: String,
val tierDescription: String,
val grossPrice: UiMoney,
val netPrice: UiMoney,
val usps: List<String>,
val exposureDisplayName: String,
val deductibleDisplayName: String?,
val hasDiscount: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.hedvig.android.feature.purchase.car.data

import arrow.core.Either
import arrow.core.raise.either
import com.apollographql.apollo.ApolloClient
import com.hedvig.android.apollo.safeExecute
import com.hedvig.android.core.common.ErrorMessage
import com.hedvig.android.logger.LogPriority
import com.hedvig.android.logger.logcat
import octopus.CarMemberContactInfoQuery
import octopus.CarPriceIntentCreateMutation
import octopus.CarShopSessionCreateMutation
import octopus.type.CountryCode

internal interface CreateCarSessionAndPriceIntentUseCase {
suspend fun invoke(productName: String): Either<ErrorMessage, SessionAndIntent>
}

internal class CreateCarSessionAndPriceIntentUseCaseImpl(
private val apolloClient: ApolloClient,
) : CreateCarSessionAndPriceIntentUseCase {
override suspend fun invoke(productName: String): Either<ErrorMessage, SessionAndIntent> {
return either {
val shopSessionId = apolloClient
.mutation(CarShopSessionCreateMutation(CountryCode.SE))
.safeExecute()
.fold(
ifLeft = {
logcat(LogPriority.ERROR) { "Failed to create shop session: $it" }
raise(ErrorMessage())
},
ifRight = { it.shopSessionCreate.id },
)

val priceIntentId = apolloClient
.mutation(CarPriceIntentCreateMutation(shopSessionId = shopSessionId, productName = productName))
.safeExecute()
.fold(
ifLeft = {
logcat(LogPriority.ERROR) { "Failed to create price intent: $it" }
raise(ErrorMessage())
},
ifRight = { it.priceIntentCreate.id },
)

val member = apolloClient
.query(CarMemberContactInfoQuery())
.safeExecute()
.fold(
ifLeft = {
logcat(LogPriority.ERROR) { "Failed to fetch member contact info: $it" }
raise(ErrorMessage())
},
ifRight = { it.currentMember },
)
val ssn = member.ssn
if (ssn == null) {
logcat(LogPriority.ERROR) { "Member is missing SSN — cannot continue car purchase" }
raise(ErrorMessage())
}

SessionAndIntent(
shopSessionId = shopSessionId,
priceIntentId = priceIntentId,
ssn = ssn,
email = member.email,
)
}
}
}
Loading
Loading