Skip to content

Commit

Permalink
Merge pull request #2414 from HedvigInsurance/feature/travel-addon-de…
Browse files Browse the repository at this point in the history
…eplink

GEN-3226 Feature/travel addon deeplink
  • Loading branch information
panasetskaya authored Feb 12, 2025
2 parents 0eec0c7 + f294da4 commit 52273dc
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ internal fun HedvigNavHost(
navigator = navigator,
navController = hedvigAppState.navController,
onNavigateToNewConversation = ::navigateToNewConversation,
hedvigDeepLinkContainer = hedvigDeepLinkContainer,
)
changeTierGraph(
navigator = navigator,
Expand Down
1 change: 1 addition & 0 deletions app/feature/feature-addon-purchase/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ dependencies {
implementation(projects.navigationComposeTyped)
implementation(projects.navigationCore)
implementation(projects.uiTiersAndAddons)
implementation(projects.dataAddons)

testImplementation(libs.apollo.testingSupport)
testImplementation(libs.assertK)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hedvig.android.feature.addon.purchase.di

import com.apollographql.apollo.ApolloClient
import com.hedvig.android.data.addons.data.GetTravelAddonBannerInfoUseCase
import com.hedvig.android.feature.addon.purchase.data.GetInsuranceForTravelAddonUseCase
import com.hedvig.android.feature.addon.purchase.data.GetInsuranceForTravelAddonUseCaseImpl
import com.hedvig.android.feature.addon.purchase.data.GetTravelAddonOfferUseCase
Expand All @@ -11,6 +12,7 @@ import com.hedvig.android.feature.addon.purchase.navigation.SummaryParameters
import com.hedvig.android.feature.addon.purchase.ui.customize.CustomizeTravelAddonViewModel
import com.hedvig.android.feature.addon.purchase.ui.selectinsurance.SelectInsuranceForAddonViewModel
import com.hedvig.android.feature.addon.purchase.ui.summary.AddonSummaryViewModel
import com.hedvig.android.feature.addon.purchase.ui.triage.TravelAddonTriageViewModel
import com.hedvig.android.featureflags.FeatureManager
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
Expand All @@ -37,6 +39,12 @@ val addonPurchaseModule = module {
)
}

viewModel<TravelAddonTriageViewModel> {
TravelAddonTriageViewModel(
get<GetTravelAddonBannerInfoUseCase>(),
)
}

single<GetInsuranceForTravelAddonUseCase> {
GetInsuranceForTravelAddonUseCaseImpl(
apolloClient = get<ApolloClient>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ internal sealed interface AddonPurchaseDestination {

@Serializable
data object SubmitFailure : AddonPurchaseDestination, Destination

@Serializable
data object TravelAddonTriage : AddonPurchaseDestination, Destination
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestina
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestination.SubmitFailure
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestination.SubmitSuccess
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestination.Summary
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestination.TravelAddonTriage
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestination.TravelInsurancePlusExplanation
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseDestination.TravelInsurancePlusExplanation.TravelPerilData
import com.hedvig.android.feature.addon.purchase.ui.customize.CustomizeTravelAddonDestination
Expand All @@ -22,20 +23,46 @@ import com.hedvig.android.feature.addon.purchase.ui.success.SubmitAddonSuccessSc
import com.hedvig.android.feature.addon.purchase.ui.summary.AddonSummaryDestination
import com.hedvig.android.feature.addon.purchase.ui.summary.AddonSummaryViewModel
import com.hedvig.android.feature.addon.purchase.ui.travelinsuranceplusexplanation.TravelInsurancePlusExplanationDestination
import com.hedvig.android.feature.addon.purchase.ui.triage.TravelAddonTriageDestination
import com.hedvig.android.feature.addon.purchase.ui.triage.TravelAddonTriageViewModel
import com.hedvig.android.navigation.compose.navDeepLinks
import com.hedvig.android.navigation.compose.navdestination
import com.hedvig.android.navigation.compose.navgraph
import com.hedvig.android.navigation.compose.typed.getRouteFromBackStack
import com.hedvig.android.navigation.compose.typedPopBackStack
import com.hedvig.android.navigation.compose.typedPopUpTo
import com.hedvig.android.navigation.core.HedvigDeepLinkContainer
import com.hedvig.android.navigation.core.Navigator
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

fun NavGraphBuilder.addonPurchaseNavGraph(
navigator: Navigator,
navController: NavController,
hedvigDeepLinkContainer: HedvigDeepLinkContainer,
onNavigateToNewConversation: (NavBackStackEntry) -> Unit,
) {
/**
* Destination to get eligible insuranceIds if member comes to the feature using the deeplink
*/
navdestination<TravelAddonTriage>(
deepLinks = navDeepLinks(hedvigDeepLinkContainer.travelAddon),
) { backStackEntry ->
val viewModel: TravelAddonTriageViewModel = koinViewModel()
TravelAddonTriageDestination(
viewModel = viewModel,
popBackStack = navigator::popBackStack,
launchFlow = { insuranceIds: List<String> ->
navigator.navigateUnsafe(AddonPurchaseGraphDestination(insuranceIds)) {
typedPopUpTo<TravelAddonTriage>({ inclusive = true })
}
},
onNavigateToNewConversation = {
onNavigateToNewConversation(backStackEntry)
},
)
}

navgraph<AddonPurchaseGraphDestination>(
startDestination = ChooseInsuranceToAddAddonDestination::class,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.hedvig.android.feature.addon.purchase.ui.triage

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.compose.dropUnlessResumed
import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large
import com.hedvig.android.design.system.hedvig.HedvigErrorSection
import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress
import com.hedvig.android.design.system.hedvig.HedvigPreview
import com.hedvig.android.design.system.hedvig.HedvigTextButton
import com.hedvig.android.design.system.hedvig.HedvigTheme
import com.hedvig.android.design.system.hedvig.Surface
import com.hedvig.android.feature.addon.purchase.ui.triage.TravelAddonTriageState.Failure
import com.hedvig.android.feature.addon.purchase.ui.triage.TravelAddonTriageState.Loading
import com.hedvig.android.feature.addon.purchase.ui.triage.TravelAddonTriageState.Success
import hedvig.resources.R

@Composable
internal fun TravelAddonTriageDestination(
viewModel: TravelAddonTriageViewModel,
popBackStack: () -> Unit,
launchFlow: (insuranceIds: List<String>) -> Unit,
onNavigateToNewConversation: () -> Unit,
) {
val uiState: TravelAddonTriageState by viewModel.uiState.collectAsStateWithLifecycle()
StartChangeTierFlowScreen(
uiState = uiState,
reload = {
viewModel.emit(TravelAddonTriageEvent.Reload)
},
launchFlow = launchFlow,
onNavigateToNewConversation = onNavigateToNewConversation,
popBackStack = popBackStack,
)
}

@Composable
private fun StartChangeTierFlowScreen(
uiState: TravelAddonTriageState,
reload: () -> Unit,
popBackStack: () -> Unit,
launchFlow: (List<String>) -> Unit,
onNavigateToNewConversation: () -> Unit,
) {
Surface(
color = HedvigTheme.colorScheme.backgroundPrimary,
modifier = Modifier.fillMaxSize(),
) {
when (uiState) {
is Failure -> {
FailureScreen(
reload = reload,
popBackStack = popBackStack,
reason = uiState.reason,
onNavigateToNewConversation = onNavigateToNewConversation,
)
}

Loading -> HedvigFullScreenCenterAlignedProgress()

is Success -> {
LaunchedEffect(uiState.insuranceIds) {
val params = uiState.insuranceIds
launchFlow(params)
}
}
}
}
}

@Composable
private fun FailureScreen(
reload: () -> Unit,
popBackStack: () -> Unit,
reason: FailureReason,
onNavigateToNewConversation: () -> Unit,
) {
Box(Modifier.fillMaxSize()) {
when (reason) {
FailureReason.GENERAL -> {
HedvigErrorSection(
onButtonClick = reload,
modifier = Modifier.fillMaxSize(),
)
}

FailureReason.NO_TRAVEL_ADDON_AVAILABLE -> {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal +
WindowInsetsSides.Bottom,
),
),
) {
Spacer(Modifier.weight(1f))
val buttonText = stringResource(R.string.open_chat)

HedvigErrorSection(
onButtonClick = onNavigateToNewConversation,
subTitle = stringResource(R.string.GENERAL_ERROR_BODY),
modifier = Modifier.fillMaxSize(),
buttonText = buttonText,
)
Spacer(Modifier.weight(1f))
HedvigTextButton(
stringResource(R.string.general_close_button),
onClick = dropUnlessResumed { popBackStack() },
buttonSize = Large,
modifier = Modifier.fillMaxWidth(),
)
Spacer(Modifier.height(32.dp))
}
}
}
}
}

@HedvigPreview
@Composable
private fun StartTierFlowScreenPreview(
@PreviewParameter(TravelAddonTriageStateProvider::class) state: TravelAddonTriageState,
) {
HedvigTheme {
Surface(color = HedvigTheme.colorScheme.backgroundPrimary) {
StartChangeTierFlowScreen(
uiState = state,
{},
{},
{},
{},
)
}
}
}

internal class TravelAddonTriageStateProvider :
CollectionPreviewParameterProvider<TravelAddonTriageState>(
listOf(
TravelAddonTriageState.Loading,
Success(
listOf("id", "id2"),
),
Failure(FailureReason.GENERAL),
Failure(FailureReason.NO_TRAVEL_ADDON_AVAILABLE),
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.hedvig.android.feature.addon.purchase.ui.triage

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.hedvig.android.core.common.ErrorMessage
import com.hedvig.android.data.addons.data.GetTravelAddonBannerInfoUseCase
import com.hedvig.android.data.addons.data.TravelAddonBannerInfo
import com.hedvig.android.data.addons.data.TravelAddonBannerSource
import com.hedvig.android.molecule.android.MoleculeViewModel
import com.hedvig.android.molecule.public.MoleculePresenter
import com.hedvig.android.molecule.public.MoleculePresenterScope
import kotlinx.coroutines.flow.first

internal class TravelAddonTriageViewModel(
getTravelAddonBannerInfoUseCase: GetTravelAddonBannerInfoUseCase,
) : MoleculeViewModel<TravelAddonTriageEvent, TravelAddonTriageState>(
initialState = TravelAddonTriageState.Loading,
presenter = TravelAddonTriagePresenter(getTravelAddonBannerInfoUseCase),
)

internal class TravelAddonTriagePresenter(
private val getTravelAddonBannerInfoUseCase: GetTravelAddonBannerInfoUseCase,
) : MoleculePresenter<TravelAddonTriageEvent, TravelAddonTriageState> {
@Composable
override fun MoleculePresenterScope<TravelAddonTriageEvent>.present(
lastState: TravelAddonTriageState,
): TravelAddonTriageState {
var currentState by remember { mutableStateOf(lastState) }
var loadIteration by remember { mutableIntStateOf(0) }

LaunchedEffect(loadIteration) {
currentState = TravelAddonTriageState.Loading
val result = getTravelAddonBannerInfoUseCase.invoke(TravelAddonBannerSource.TRAVEL_CERTIFICATES)
result.first().fold(
ifLeft = { left: ErrorMessage ->
currentState = TravelAddonTriageState.Failure(FailureReason.GENERAL)
},
ifRight = { travelBannerInfo: TravelAddonBannerInfo? ->
currentState = if (travelBannerInfo == null || travelBannerInfo.eligibleInsurancesIds.isEmpty()) {
TravelAddonTriageState.Failure(FailureReason.NO_TRAVEL_ADDON_AVAILABLE)
} else {
TravelAddonTriageState.Success(travelBannerInfo.eligibleInsurancesIds)
}
},
)
}

CollectEvents { event ->
when (event) {
TravelAddonTriageEvent.Reload -> loadIteration++
}
}

return currentState
}
}

internal sealed interface TravelAddonTriageState {
data object Loading : TravelAddonTriageState

data class Success(
val insuranceIds: List<String>,
) : TravelAddonTriageState

data class Failure(val reason: FailureReason) : TravelAddonTriageState
}

internal sealed interface TravelAddonTriageEvent {
data object Reload : TravelAddonTriageEvent
}

internal enum class FailureReason {
GENERAL,
NO_TRAVEL_ADDON_AVAILABLE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ interface HedvigDeepLinkContainer {
val chat: List<String> // Hedvig Chat
val inbox: List<String> // Hedvig CBM inbox
val conversation: List<String> // Hedvig specific CBM conversation

// Travel addon purchase flow
val travelAddon: List<String>
}

internal class HedvigDeepLinkContainerImpl(
Expand Down Expand Up @@ -113,6 +116,9 @@ internal class HedvigDeepLinkContainerImpl(
override val conversation: List<String> = baseDeepLinkDomains.map { baseDeepLinkDomain ->
"$baseDeepLinkDomain/conversation/{conversationId}"
}
override val travelAddon: List<String> = baseDeepLinkDomains.map { baseDeepLinkDomain ->
"$baseDeepLinkDomain/travel-addon"
}
}

val HedvigDeepLinkContainer.allDeepLinkUriPatterns: List<String>
Expand All @@ -138,4 +144,5 @@ val HedvigDeepLinkContainer.allDeepLinkUriPatterns: List<String>
chat.first(),
inbox.first(),
conversation.first(),
travelAddon.first(),
)

0 comments on commit 52273dc

Please sign in to comment.