Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -1946,21 +1946,46 @@ type CrossSell {
"""
options: [CrossSell!]!
}
type CrossSellCarouselItem {
title: String!
subtitle: String
"""
The slide image, or the poster when the slide is a video
"""
image: StoryblokImageAsset!
"""
HEVC mp4 for iOS, set when the slide is a video; play it over the image
"""
videoUrlMp4: String
"""
VP9 webm for Android, same clip as videoUrlMp4
"""
videoUrlWebm: String
"""
Replay the video while the slide is shown; otherwise play once and hold the last frame
"""
loop: Boolean!
}
"""
A/B testing experiment (name and variant must be SCREAMING_SNAKE_CASE)
"""
input CrossSellExperimentInput {
name: String!
variant: String!
}
type CrossSellHeroIntro {
title: String!
description: String!
}
input CrossSellInput {
userFlow: UserFlow!
"""
REQUIRED for SMART_X_SELL, must be NULL for HOME_X_SELL, INSURANCES, ONBOARDING and HOME
"""
flowSource: FlowSource
"""
A/B test experiments for attribution tracking
A/B test experiments for attribution tracking. ACCIDENT_DISCOUNT with variant NO_DISCOUNT withholds the accident
cross-sell's discount.
"""
experiments: [CrossSellExperimentInput!]!
"""
Expand All @@ -1972,6 +1997,21 @@ input CrossSellInput {
"""
claimId: UUID
}
type CrossSellProductInfo {
heroImage: StoryblokImageAsset!
"""
The intro text under the hero on the product's web page; null when the page has none
"""
heroIntro: CrossSellHeroIntro
"""
Selling points from the product's web page's image-text sections, in page order
"""
usps: [CrossSellUsp!]!
"""
Story-style slides from the product's web page, in page order
"""
carousel: [CrossSellCarouselItem!]!
}
enum CrossSellSource {
HOME
CLOSED_CLAIM
Expand All @@ -1980,6 +2020,10 @@ enum CrossSellSource {
EDIT_COINSURED
MOVING_FLOW
}
type CrossSellUsp {
title: String!
description: String!
}
type CrossSellV2 {
"""
Prioritized recommendation (null for INSURANCES)
Expand Down Expand Up @@ -2971,6 +3015,10 @@ type Member {
"""
crossSellV2(input: CrossSellInput!): CrossSellV2!
"""
Product details for a tapped cross-sell, in the Hedvig-Language of the request. Errors for an unknown id and for the generic HOME, PET and ALL cross-sells.
"""
crossSellProductInfo(crossSellId: ID!): CrossSellProductInfo!
"""
Shop sessions started by this member that have not yet been signed and are not expired.
Ordered by most recent activity first.
"""
Expand Down
1 change: 1 addition & 0 deletions app/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ dependencies {
implementation(projects.featurePayments)
implementation(projects.featurePaymentsNavigation)
implementation(projects.featureProfile)
implementation(projects.featureProductInApp)
implementation(projects.featureTerminateInsurance)
implementation(projects.featureTerminateInsuranceNavigation)
implementation(projects.featureTravelCertificate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.hedvig.android.core.buildconstants.HedvigBuildConstants
import com.hedvig.android.data.addons.data.AddonBannerSource
import com.hedvig.android.data.coinsured.CoInsuredFlowType
import com.hedvig.android.data.contract.ContractId
import com.hedvig.android.data.contract.CrossSell
import com.hedvig.android.design.system.hedvig.GlobalSnackBarState
import com.hedvig.android.feature.addon.purchase.navigation.AddonPurchaseKey
import com.hedvig.android.feature.addon.purchase.navigation.addonPurchaseEntries
Expand Down Expand Up @@ -60,6 +61,7 @@ import com.hedvig.android.feature.payments.navigation.ManualChargeKey
import com.hedvig.android.feature.payments.navigation.paymentsEntries
import com.hedvig.android.feature.payoutaccount.navigation.PayoutAccountKey
import com.hedvig.android.feature.payoutaccount.navigation.payoutAccountEntries
import com.hedvig.android.feature.product.inapp.navigation.productInAppEntries
import com.hedvig.android.feature.profile.navigation.ContactInfoKey
import com.hedvig.android.feature.profile.navigation.UsageDataKey
import com.hedvig.android.feature.profile.tab.profileEntries
Expand Down Expand Up @@ -103,6 +105,7 @@ internal fun EntryProviderScope<HedvigNavKey>.hedvigEntryProvider(
androidAppHost: AndroidAppHost,
openUrl: (String) -> Unit,
openCrossSellUrl: (String) -> Boolean,
openCrossSell: (CrossSell) -> Boolean,
imageLoader: ImageLoader,
languageService: LanguageService,
hedvigBuildConstants: HedvigBuildConstants,
Expand Down Expand Up @@ -137,7 +140,7 @@ internal fun EntryProviderScope<HedvigNavKey>.hedvigEntryProvider(
externalNavigator = externalNavigator,
imageLoader = imageLoader,
openUrl = openUrl,
openCrossSellUrl = openCrossSellUrl,
openCrossSell = openCrossSell,
onNavigateToImageViewer = onNavigateToImageViewer,
navigateToNewConversation = navigateToNewConversation,
navigateToConversation = navigateToConversation,
Expand All @@ -153,7 +156,7 @@ internal fun EntryProviderScope<HedvigNavKey>.hedvigEntryProvider(
windowSizeClass = windowSizeClass,
imageLoader = imageLoader,
openUrl = openUrl,
openCrossSellUrl = openCrossSellUrl,
openCrossSell = openCrossSell,
externalNavigator = externalNavigator,
navigateToNewConversation = navigateToNewConversation,
navigateToMovingFlowFromInsurance = {
Expand Down Expand Up @@ -202,6 +205,7 @@ internal fun EntryProviderScope<HedvigNavKey>.hedvigEntryProvider(
imageLoader = imageLoader,
openUrl = openUrl,
openCrossSellUrl = openCrossSellUrl,
openCrossSell = openCrossSell,
externalNavigator = externalNavigator,
navigateToNewConversation = navigateToNewConversation,
navigateToInbox = navigateToInbox,
Expand Down Expand Up @@ -247,7 +251,7 @@ private fun EntryProviderScope<HedvigNavKey>.addHomeEntries(
externalNavigator: ExternalNavigator,
imageLoader: ImageLoader,
openUrl: (String) -> Unit,
openCrossSellUrl: (String) -> Boolean,
openCrossSell: (CrossSell) -> Boolean,
onNavigateToImageViewer: (imageUrl: String, cacheKey: String) -> Unit,
navigateToNewConversation: () -> Unit,
navigateToConversation: (String) -> Unit,
Expand Down Expand Up @@ -305,7 +309,7 @@ private fun EntryProviderScope<HedvigNavKey>.addHomeEntries(
navigateToUsageData = { backstack.add(UsageDataKey) },
openAppSettings = externalNavigator::openAppSettings,
openUrl = openUrl,
openCrossSellUrl = openCrossSellUrl,
openCrossSell = openCrossSell,
imageLoader = imageLoader,
navigateToAddonPurchaseFlow = navigateToAddonPurchaseFlow,
)
Expand Down Expand Up @@ -375,7 +379,7 @@ private fun EntryProviderScope<HedvigNavKey>.addInsuranceEntries(
windowSizeClass: WindowSizeClass,
imageLoader: ImageLoader,
openUrl: (String) -> Unit,
openCrossSellUrl: (String) -> Boolean,
openCrossSell: (CrossSell) -> Boolean,
externalNavigator: ExternalNavigator,
navigateToNewConversation: () -> Unit,
navigateToMovingFlowFromInsurance: () -> Unit,
Expand Down Expand Up @@ -418,7 +422,7 @@ private fun EntryProviderScope<HedvigNavKey>.addInsuranceEntries(
},
backstack = backstack,
openUrl = openUrl,
openCrossSellUrl = openCrossSellUrl,
openCrossSell = openCrossSell,
onNavigateToNewConversation = navigateToNewConversation,
startMovingFlow = navigateToMovingFlowFromInsurance,
startTerminationFlow = { data: CancelInsuranceData ->
Expand Down Expand Up @@ -585,6 +589,7 @@ private fun EntryProviderScope<HedvigNavKey>.addSharedFlowEntries(
imageLoader: ImageLoader,
openUrl: (String) -> Unit,
openCrossSellUrl: (String) -> Boolean,
openCrossSell: (CrossSell) -> Boolean,
externalNavigator: ExternalNavigator,
navigateToNewConversation: () -> Unit,
navigateToInbox: () -> Unit,
Expand Down Expand Up @@ -623,11 +628,12 @@ private fun EntryProviderScope<HedvigNavKey>.addSharedFlowEntries(
imageLoader = imageLoader,
)
imageViewerEntries(backstack, imageLoader)
productInAppEntries(backstack = backstack, imageLoader = imageLoader, openCrossSellUrl = openCrossSellUrl)
removeAddonsEntries(backstack = backstack)
onboardingEntries(
backstack = backstack,
imageLoader = imageLoader,
openCrossSellUrl = openCrossSellUrl,
openCrossSell = openCrossSell,
openPrivacyPolicy = openPrivacyPolicy,
navigateToChipId = { contractId -> backstack.add(ChipIdKey(contractId)) },
)
Expand Down
18 changes: 17 additions & 1 deletion app/app/src/main/kotlin/com/hedvig/android/app/ui/HedvigApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import com.hedvig.android.core.buildconstants.HedvigBuildConstants
import com.hedvig.android.core.demomode.DemoManager
import com.hedvig.android.core.tracking.EventTrackingClient
import com.hedvig.android.data.addons.data.AddonBannerSource
import com.hedvig.android.data.contract.CrossSell
import com.hedvig.android.data.settings.datastore.SettingsDataStore
import com.hedvig.android.design.system.hedvig.DemoModeLabel
import com.hedvig.android.design.system.hedvig.Surface
Expand All @@ -79,7 +80,9 @@ import com.hedvig.android.feature.home.home.navigation.HomeKey
import com.hedvig.android.feature.onboarding.data.ResetOnboardingSeenUseCase
import com.hedvig.android.feature.onboarding.gate.OnboardingGate
import com.hedvig.android.feature.onboarding.navigation.OnboardingKey
import com.hedvig.android.feature.product.inapp.navigation.navigateToCrossSellProduct
import com.hedvig.android.featureflags.FeatureManager
import com.hedvig.android.featureflags.flags.Feature
import com.hedvig.android.language.LanguageService
import com.hedvig.android.logger.logcat
import com.hedvig.android.navigation.activity.ExternalNavigator
Expand Down Expand Up @@ -176,9 +179,21 @@ internal fun HedvigApp(
scope = scope,
)
}
val showProductPages by remember(featureManager) {
featureManager.isFeatureEnabled(Feature.SHOW_PRODUCT_PAGES)
}.collectAsStateWithLifecycle(initialValue = false)
val openCrossSell: (CrossSell) -> Boolean = { crossSell ->
if (showProductPages) {
backstackController.navigateToCrossSellProduct(crossSell)
true
} else {
authorizationCodeUriHandler.tryOpenUri(crossSell.storeUrl)
}
}
CrossSellSheet(
isInScreenEligibleForCrossSells = hedvigAppState.isInScreenEligibleForCrossSells,
onCrossSellClick = authorizationCodeUriHandler::tryOpenUri,
onCrossSellClick = openCrossSell,
onRecommendedAddonClick = { deepLink -> authorizationCodeUriHandler.tryOpenUri(deepLink) },
onAddonClick = { eligibleInsuranceIds ->
backstackController.add(
AddonPurchaseKey(
Expand Down Expand Up @@ -244,6 +259,7 @@ internal fun HedvigApp(
androidAppHost = androidAppHost,
openUrl = authorizationCodeUriHandler::openUri,
openCrossSellUrl = authorizationCodeUriHandler::tryOpenUri,
openCrossSell = openCrossSell,
imageLoader = imageLoader,
languageService = languageService,
hedvigBuildConstants = hedvigBuildConstants,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package com.hedvig.android.data.contract
* Pick the one matching the box it is drawn into: the same instance is shown both as a 48.dp list row and as
* a 140.dp hero, and reusing one URL across both sizes makes Coil serve the larger cached bitmap to the
* smaller box, which visibly softens its edges.
*
* A generic cross-sell, such as pet, has the products to choose between in [options]; a single product has none.
*/
data class CrossSell(
val id: String,
Expand All @@ -14,6 +16,7 @@ data class CrossSell(
val pillowImageSmall: ImageAsset,
val pillowImageLarge: ImageAsset,
val buttonText: String,
val options: List<CrossSell> = emptyList(),
)

data class ImageAsset(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,21 @@ fragment CrossSellFragment on CrossSell {
id
alt
}
options {
id
title
description
storeUrl
buttonTitle
pillowImageSmall {
src
id
alt
}
pillowImageLarge {
src
id
alt
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged
@Composable
fun CrossSellSheet(
isInScreenEligibleForCrossSells: Boolean,
onCrossSellClick: (String) -> Boolean,
onCrossSellClick: (CrossSell) -> Boolean,
onRecommendedAddonClick: (deepLink: String) -> Unit,
onAddonClick: (eligibleInsuranceIds: List<String>) -> Unit,
imageLoader: ImageLoader,
) {
Expand Down Expand Up @@ -76,11 +77,11 @@ fun CrossSellSheet(
viewModel.emit(CrossSellSheetEvent.CrossSellShown(offerId, offerUrl))
},
onCrossSellClick = dropUnlessResumed { crossSell: CrossSell ->
if (onCrossSellClick(crossSell.storeUrl)) {
if (onCrossSellClick(crossSell)) {
viewModel.emit(CrossSellSheetEvent.CrossSellClicked(crossSell.id, crossSell.storeUrl))
}
},
onRecommendedAddonClick = { deepLink -> onCrossSellClick(deepLink) },
onRecommendedAddonClick = onRecommendedAddonClick,
onAddonClick = onAddonClick,
imageLoader = imageLoader,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,25 @@ internal fun CrossSellFragment.toCrossSell(): CrossSell {
description = pillowImageLarge.alt,
),
buttonText = this.buttonTitle,
options = options.map { option ->
CrossSell(
id = option.id,
title = option.title,
subtitle = option.description,
storeUrl = option.storeUrl,
pillowImageSmall = ImageAsset(
id = option.pillowImageSmall.id,
src = option.pillowImageSmall.src,
description = option.pillowImageSmall.alt,
),
pillowImageLarge = ImageAsset(
id = option.pillowImageLarge.id,
src = option.pillowImageLarge.src,
description = option.pillowImageLarge.alt,
),
buttonText = option.buttonTitle,
)
},
)
}
}
Expand Down
17 changes: 17 additions & 0 deletions app/feature/feature-home/src/main/graphql/QueryHome.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,21 @@ fragment HomeCrossSellFragment on CrossSell {
id
alt
}
options {
id
title
description
storeUrl
buttonTitle
pillowImageSmall {
src
id
alt
}
pillowImageLarge {
src
id
alt
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,25 @@ internal class GetHomeDataUseCaseImpl(
description = pillowImageLarge.alt,
),
buttonText = this.buttonTitle,
options = options.map { option ->
CrossSell(
id = option.id,
title = option.title,
subtitle = option.description,
storeUrl = option.storeUrl,
pillowImageSmall = ImageAsset(
id = option.pillowImageSmall.id,
src = option.pillowImageSmall.src,
description = option.pillowImageSmall.alt,
),
pillowImageLarge = ImageAsset(
id = option.pillowImageLarge.id,
src = option.pillowImageLarge.src,
description = option.pillowImageLarge.alt,
),
buttonText = option.buttonTitle,
)
},
)
}
}
Expand Down
Loading
Loading