From 2f6d1d0252047417313535de23c88b2c168d1b43 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:07:56 +0200 Subject: [PATCH 01/30] feat: add PartnerClaim type to GraphQL schema Co-Authored-By: Claude Sonnet 4.6 --- .../android/apollo/octopus/schema.graphqls | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 288983627b..3bba2b35f0 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 @@ -1184,6 +1184,19 @@ type ClaimMutationOutput { claim: Claim userError: UserError } +type PartnerClaim { + id: ID! + externalId: String! + exposureDisplayName: String + status: ClaimStatus + submittedAt: Date + payoutAmount: Money + associatedTypeOfContract: String + claimType: String + handlerEmail: String + displayItems: [ClaimDisplayItem!]! + productVariant: ProductVariant +} enum ClaimOutcome { PAID NOT_COMPENSATED @@ -2419,6 +2432,8 @@ type Member { claims: [Claim!]! claimsActive: [Claim!]! claimsHistory: [Claim!]! + partnerClaimsActive: [PartnerClaim!]! + partnerClaimsHistory: [PartnerClaim!]! firstName: String! lastName: String! ssn: String @@ -4306,6 +4321,7 @@ type Query { """ conversation(id: UUID!): Conversation claim(id: ID!): Claim + partnerClaim(id: ID!): PartnerClaim claimIntent(id: ID!): ClaimIntent! claimIntentFormFieldSearch(input: ClaimIntentFormFieldSearchInput!): ClaimIntentFormFieldSearchOutput! personalInformation(input: PersonalInformationInput!): PersonalInformation From c46ab04e58ac922f72680174c2912d7c1e10b698 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:07:59 +0200 Subject: [PATCH 02/30] feat: add PartnerClaimFragment GraphQL fragment Co-Authored-By: Claude Sonnet 4.6 --- .../FragmentPartnerClaimFragment.graphql | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/graphql/FragmentPartnerClaimFragment.graphql diff --git a/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/graphql/FragmentPartnerClaimFragment.graphql b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/graphql/FragmentPartnerClaimFragment.graphql new file mode 100644 index 0000000000..fa6fb0a00f --- /dev/null +++ b/app/apollo/apollo-octopus-public/src/commonMain/graphql/com/hedvig/android/apollo/octopus/graphql/FragmentPartnerClaimFragment.graphql @@ -0,0 +1,25 @@ +fragment PartnerClaimFragment on PartnerClaim { + id + externalId + exposureDisplayName + status + submittedAt + payoutAmount { + ...MoneyFragment + } + associatedTypeOfContract + claimType + handlerEmail + displayItems { + displayTitle + displayValue + } + productVariant { + typeOfContract + displayName + documents { + type + url + } + } +} From 07a2a6e9ef9ed9317102b9300eb6bf7d65a2559d Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:10:44 +0200 Subject: [PATCH 03/30] feat: add fromPartnerClaim factory methods for claim card UI state Co-Authored-By: Claude Sonnet 4.6 --- .../android/ui/claimstatus/model/ClaimPillType.kt | 12 ++++++++++++ .../ui/claimstatus/model/ClaimProgressSegment.kt | 13 +++++++++++++ .../claimstatus/model/ClaimStatusCardUiState.kt | 15 +++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt index abac84a1f8..522e862ed3 100644 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt @@ -25,6 +25,18 @@ sealed interface ClaimPillType { } companion object { + fun fromPartnerClaim(status: ClaimStatus?): List { + return when (status) { + ClaimStatus.CLOSED -> listOf(Closed.GenericClosed) + ClaimStatus.CREATED, + ClaimStatus.IN_PROGRESS, + ClaimStatus.REOPENED, + ClaimStatus.UNKNOWN__, + null, + -> listOf(Claim) + } + } + fun fromClaimFragment(claim: ClaimFragment): List { return when (claim.status) { ClaimStatus.CREATED -> { diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimProgressSegment.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimProgressSegment.kt index 9e31dee661..71dc5400f0 100644 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimProgressSegment.kt +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimProgressSegment.kt @@ -22,6 +22,19 @@ data class ClaimProgressSegment( } companion object { + fun fromPartnerClaim(status: ClaimStatus?): List = when (status) { + ClaimStatus.REOPENED, + ClaimStatus.IN_PROGRESS, + -> buildSegments(ACTIVE, ACTIVE, INACTIVE) + + ClaimStatus.CLOSED -> buildSegments(ACTIVE, ACTIVE, ACTIVE) + + ClaimStatus.CREATED, + ClaimStatus.UNKNOWN__, + null, + -> buildSegments(ACTIVE, INACTIVE, INACTIVE) + } + fun fromClaimFragment(claim: ClaimFragment): List = when (claim.status) { ClaimStatus.CREATED -> buildSegments(ACTIVE, INACTIVE, INACTIVE) diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt index 823eccd16a..d19bdefeeb 100644 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt @@ -1,7 +1,11 @@ package com.hedvig.android.ui.claimstatus.model +import kotlin.time.Clock import kotlin.time.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn import octopus.fragment.ClaimFragment +import octopus.fragment.PartnerClaimFragment data class ClaimStatusCardUiState( val id: String, @@ -12,6 +16,17 @@ data class ClaimStatusCardUiState( val claimProgressItemsUiState: List, ) { companion object { + fun fromPartnerClaim(claim: PartnerClaimFragment): ClaimStatusCardUiState { + return ClaimStatusCardUiState( + id = claim.id, + claimType = claim.claimType, + insuranceDisplayName = claim.exposureDisplayName ?: claim.productVariant?.displayName, + submittedDate = claim.submittedAt?.atStartOfDayIn(TimeZone.UTC) ?: Clock.System.now(), + pillTypes = ClaimPillType.fromPartnerClaim(claim.status), + claimProgressItemsUiState = ClaimProgressSegment.fromPartnerClaim(claim.status), + ) + } + fun fromClaimStatusCardsQuery(claim: ClaimFragment): ClaimStatusCardUiState { return ClaimStatusCardUiState( id = claim.id, From a2a1ff2ff21195bc4175c2f6ae05f1450c33f3cc Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:13:59 +0200 Subject: [PATCH 04/30] feat: merge partner claims into home screen claims pager Extends the Home query to include partnerClaimsActive, merges them with regular claims sorted by date, and threads partnerClaimIds through ClaimStatusCardsData so the UI can dispatch to the correct details screen. Co-Authored-By: Claude Sonnet 4.6 --- .../src/main/graphql/QueryHome.graphql | 3 +++ .../home/home/data/GetHomeDataUseCase.kt | 20 +++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/feature/feature-home/src/main/graphql/QueryHome.graphql b/app/feature/feature-home/src/main/graphql/QueryHome.graphql index 448965eb09..c333001488 100644 --- a/app/feature/feature-home/src/main/graphql/QueryHome.graphql +++ b/app/feature/feature-home/src/main/graphql/QueryHome.graphql @@ -6,6 +6,9 @@ query Home($claimsHistoryFlag: Boolean!) { claimsActive@include(if: $claimsHistoryFlag) { ...ClaimFragment } + partnerClaimsActive { + ...PartnerClaimFragment + } terminatedContracts { id currentAgreement { diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt index 182b2ff26a..2478bd3d04 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt @@ -45,7 +45,6 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.atStartOfDayIn import octopus.HomeQuery import octopus.UnreadMessageCountQuery -import octopus.fragment.ClaimFragment import octopus.fragment.HomeCrossSellFragment internal interface GetHomeDataUseCase { @@ -268,11 +267,19 @@ internal class GetHomeDataUseCaseImpl( } private fun HomeQuery.Data.claimStatusCards(): HomeData.ClaimStatusCardsData? { - val claimStatusCards: NonEmptyList = - this.currentMember.claims?.toNonEmptyListOrNull() - ?: this.currentMember.claimsActive?.toNonEmptyListOrNull() - ?: return null - return HomeData.ClaimStatusCardsData(claimStatusCards.map(ClaimStatusCardUiState::fromClaimStatusCardsQuery)) + val regularCards = (this.currentMember.claims ?: this.currentMember.claimsActive).orEmpty() + .map(ClaimStatusCardUiState::fromClaimStatusCardsQuery) + val partnerClaims = this.currentMember.partnerClaimsActive + val partnerCards = partnerClaims.map(ClaimStatusCardUiState::fromPartnerClaim) + + val allCards = (regularCards + partnerCards) + .sortedByDescending { it.submittedDate } + .toNonEmptyListOrNull() ?: return null + + return HomeData.ClaimStatusCardsData( + claimStatusCardsUiState = allCards, + partnerClaimIds = partnerClaims.map { it.id }.toSet(), + ) } internal data class HomeData( @@ -290,6 +297,7 @@ internal data class HomeData( @Immutable data class ClaimStatusCardsData( val claimStatusCardsUiState: NonEmptyList, + val partnerClaimIds: Set = emptySet(), ) data class VeryImportantMessage( From 72b6ce901166d9a2da35a6954f4d32a16c85ee12 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:14:04 +0200 Subject: [PATCH 05/30] feat: dispatch partner claim navigation from home screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds onPartnerClaimDetailCardClicked parameter throughout the home composable stack (HomeGraph → HomeDestination → HomeScreen → HomeScreenSuccess), routing partner claim card taps to a separate details screen based on partnerClaimIds from ClaimStatusCardsData. Co-Authored-By: Claude Sonnet 4.6 --- .../feature/home/home/navigation/HomeGraph.kt | 4 ++++ .../feature/home/home/ui/HomeDestination.kt | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt index 072b01417f..018f7ed416 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt @@ -23,6 +23,7 @@ fun NavGraphBuilder.homeGraph( onNavigateToInbox: () -> Unit, onNavigateToNewConversation: () -> Unit, navigateToClaimDetails: (claimId: String) -> Unit, + navigateToPartnerClaimDetails: (claimId: String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToContactInfo: () -> Unit, navigateToMissingInfo: (String, CoInsuredFlowType) -> Unit, @@ -53,6 +54,9 @@ fun NavGraphBuilder.homeGraph( onClaimDetailCardClicked = dropUnlessResumed { claimId: String -> navigateToClaimDetails(claimId) }, + onPartnerClaimDetailCardClicked = dropUnlessResumed { claimId: String -> + navigateToPartnerClaimDetails(claimId) + }, navigateToConnectPayment = dropUnlessResumed { navigateToConnectPayment() }, navigateToMissingInfo = dropUnlessResumed { contractId, type -> navigateToMissingInfo(contractId, type) }, navigateToHelpCenter = dropUnlessResumed { navigateToHelpCenter() }, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 71a9ae2965..48f743a11a 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -157,6 +157,7 @@ internal fun HomeDestination( navigateToClaimChat: () -> Unit, navigateToClaimChatInDevMode: () -> Unit, onClaimDetailCardClicked: (String) -> Unit, + onPartnerClaimDetailCardClicked: (String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToHelpCenter: () -> Unit, openUrl: (String) -> Unit, @@ -179,6 +180,7 @@ internal fun HomeDestination( navigateToClaimChat = navigateToClaimChat, navigateToClaimChatInDevMode = navigateToClaimChatInDevMode, onClaimDetailCardClicked = onClaimDetailCardClicked, + onPartnerClaimDetailCardClicked = onPartnerClaimDetailCardClicked, navigateToConnectPayment = navigateToConnectPayment, navigateToHelpCenter = navigateToHelpCenter, openUrl = openUrl, @@ -207,6 +209,7 @@ private fun HomeScreen( navigateToClaimChat: () -> Unit, navigateToClaimChatInDevMode: () -> Unit, onClaimDetailCardClicked: (String) -> Unit, + onPartnerClaimDetailCardClicked: (String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToHelpCenter: () -> Unit, openUrl: (String) -> Unit, @@ -275,6 +278,7 @@ private fun HomeScreen( toolbarHeight = toolbarHeight, notificationPermissionState = notificationPermissionState, onClaimDetailCardClicked = onClaimDetailCardClicked, + onPartnerClaimDetailCardClicked = onPartnerClaimDetailCardClicked, navigateToConnectPayment = navigateToConnectPayment, navigateToHelpCenter = navigateToHelpCenter, openClaimFlowSheet = startClaimBottomSheetState::show, @@ -422,6 +426,7 @@ private fun HomeScreenSuccess( toolbarHeight: Dp, notificationPermissionState: NotificationPermissionState, onClaimDetailCardClicked: (claimId: String) -> Unit, + onPartnerClaimDetailCardClicked: (claimId: String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToHelpCenter: () -> Unit, openClaimFlowSheet: () -> Unit, @@ -474,7 +479,13 @@ private fun HomeScreenSuccess( claimStatusCards = { if (uiState.claimStatusCardsData != null) { ClaimStatusCards( - onClick = onClaimDetailCardClicked, + onClick = { claimId -> + if (claimId in (uiState.claimStatusCardsData.partnerClaimIds)) { + onPartnerClaimDetailCardClicked(claimId) + } else { + onClaimDetailCardClicked(claimId) + } + }, claimStatusCardsUiState = uiState.claimStatusCardsData.claimStatusCardsUiState, contentPadding = PaddingValues(horizontal = 16.dp) + horizontalInsets, ) @@ -800,6 +811,7 @@ private fun PreviewHomeScreen( onNavigateToNewConversation = {}, navigateToClaimChat = {}, onClaimDetailCardClicked = {}, + onPartnerClaimDetailCardClicked = {}, navigateToConnectPayment = {}, navigateToHelpCenter = {}, openUrl = {}, @@ -832,6 +844,7 @@ private fun PreviewHomeScreenWithError() { onNavigateToNewConversation = {}, navigateToClaimChat = {}, onClaimDetailCardClicked = {}, + onPartnerClaimDetailCardClicked = {}, navigateToConnectPayment = {}, navigateToHelpCenter = {}, openUrl = {}, @@ -885,6 +898,7 @@ private fun PreviewHomeScreenAllHomeTextTypes( onNavigateToNewConversation = {}, navigateToClaimChat = {}, onClaimDetailCardClicked = {}, + onPartnerClaimDetailCardClicked = {}, navigateToConnectPayment = {}, navigateToHelpCenter = {}, openUrl = {}, From 08d0b425a74a173c7db55fdf0f380dff5c43b64d Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:18:58 +0200 Subject: [PATCH 06/30] feat: merge partner claims into claim history Extends the claim history screen to include partner claims alongside regular claims, sorted by submission date. Partner claims dispatch to a separate navigation target on click. Co-Authored-By: Claude Sonnet 4.6 --- .../android/app/navigation/HedvigNavHost.kt | 1 + .../claimhistory/ClaimHistoryDestination.kt | 23 ++++++-- .../nav/ClaimHistoryDestination.kt | 7 ++- .../graphql/QueryClaimsHistory.graphql | 3 ++ .../claimhistory/GetClaimsHistoryUseCase.kt | 54 ++++++++++--------- 5 files changed, 59 insertions(+), 29 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index d1c6cc480c..3a1c3709b9 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -354,6 +354,7 @@ internal fun HedvigNavHost( navigateToClaimDetails = { claimId -> navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) }, + navigateToPartnerClaimDetails = {}, ) }, popBackStackOrFinish = popBackStackOrFinish, diff --git a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt index a6d1ea9a4a..998fc8582b 100644 --- a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt +++ b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt @@ -60,12 +60,14 @@ internal fun ClaimHistoryDestination( claimHistoryViewModel: ClaimHistoryViewModel, navigateUp: () -> Unit, navigateToClaimDetails: (String) -> Unit, + navigateToPartnerClaimDetails: (String) -> Unit, ) { val uiState by claimHistoryViewModel.uiState.collectAsStateWithLifecycle() ClaimHistoryScreen( uiState = uiState, navigateUp = navigateUp, navigateToClaimDetails = navigateToClaimDetails, + navigateToPartnerClaimDetails = navigateToPartnerClaimDetails, reload = { claimHistoryViewModel.emit(ClaimHistoryEvent.Reload) }, ) } @@ -75,6 +77,7 @@ private fun ClaimHistoryScreen( uiState: ClaimHistoryUiState, navigateUp: () -> Unit, navigateToClaimDetails: (String) -> Unit, + navigateToPartnerClaimDetails: (String) -> Unit, reload: () -> Unit, ) { HedvigScaffold( @@ -108,7 +111,7 @@ private fun ClaimHistoryScreen( .fillMaxWidth(), ) - is ClaimHistoryUiState.Content -> ClaimHistoryContent(uiState, navigateToClaimDetails) + is ClaimHistoryUiState.Content -> ClaimHistoryContent(uiState, navigateToClaimDetails, navigateToPartnerClaimDetails) } } } @@ -118,14 +121,20 @@ private fun ClaimHistoryScreen( private fun ColumnScope.ClaimHistoryContent( uiState: ClaimHistoryUiState.Content, navigateToClaimDetails: (String) -> Unit, + navigateToPartnerClaimDetails: (String) -> Unit, ) { uiState.claims.forEachIndexed { index, claim -> - ClaimHistoryItem(index, claim, navigateToClaimDetails) + ClaimHistoryItem(index, claim, navigateToClaimDetails, navigateToPartnerClaimDetails) } } @Composable -private fun ClaimHistoryItem(index: Int, claim: ClaimHistory, navigateToClaimDetails: (String) -> Unit) { +private fun ClaimHistoryItem( + index: Int, + claim: ClaimHistory, + navigateToClaimDetails: (String) -> Unit, + navigateToPartnerClaimDetails: (String) -> Unit, +) { val hedvigDateTimeFormatter = rememberHedvigDateTimeFormatter() HorizontalItemsWithMaximumSpaceTaken( { @@ -179,7 +188,11 @@ private fun ClaimHistoryItem(index: Int, claim: ClaimHistory, navigateToClaimDet .fillMaxWidth() .clickable( onClick = dropUnlessResumed { - navigateToClaimDetails(claim.id) + if (claim.isPartnerClaim) { + navigateToPartnerClaimDetails(claim.id) + } else { + navigateToClaimDetails(claim.id) + } }, ) .horizontalDivider(DividerPosition.Top, show = index != 0, horizontalPadding = 18.dp) @@ -199,6 +212,7 @@ private fun PreviewClaimHistoryScreen( {}, {}, {}, + {}, ) } } @@ -214,6 +228,7 @@ private class ClaimHistoryUiStateCollectionPreviewParameterProvider : claimType = "$it", outcome = ClaimHistory.ClaimOutcome.entries[it], submittedAt = Instant.fromEpochMilliseconds(100), + isPartnerClaim = false, ) }.toNonEmptyListOrThrow(), ), diff --git a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt index 2011549621..92de3a7d7e 100644 --- a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt +++ b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt @@ -11,12 +11,17 @@ import org.koin.compose.viewmodel.koinViewModel @Serializable data object ClaimHistoryDestination : Destination -fun NavGraphBuilder.claimHistoryGraph(navigateUp: () -> Unit, navigateToClaimDetails: (String) -> Unit) { +fun NavGraphBuilder.claimHistoryGraph( + navigateUp: () -> Unit, + navigateToClaimDetails: (String) -> Unit, + navigateToPartnerClaimDetails: (String) -> Unit, +) { navdestination { ClaimHistoryDestination( claimHistoryViewModel = koinViewModel(), navigateUp = navigateUp, navigateToClaimDetails = navigateToClaimDetails, + navigateToPartnerClaimDetails = navigateToPartnerClaimDetails, ) } } diff --git a/app/feature/feature-claim-history/src/commonMain/graphql/QueryClaimsHistory.graphql b/app/feature/feature-claim-history/src/commonMain/graphql/QueryClaimsHistory.graphql index 2d24c3c392..70835ca9f8 100644 --- a/app/feature/feature-claim-history/src/commonMain/graphql/QueryClaimsHistory.graphql +++ b/app/feature/feature-claim-history/src/commonMain/graphql/QueryClaimsHistory.graphql @@ -3,5 +3,8 @@ query ClaimsHistory { claimsHistory { ...ClaimFragment } + partnerClaimsHistory { + ...PartnerClaimFragment + } } } diff --git a/app/feature/feature-claim-history/src/commonMain/kotlin/com/hedvig/android/feature/claimhistory/GetClaimsHistoryUseCase.kt b/app/feature/feature-claim-history/src/commonMain/kotlin/com/hedvig/android/feature/claimhistory/GetClaimsHistoryUseCase.kt index d6cda3be07..0aa2bf3942 100644 --- a/app/feature/feature-claim-history/src/commonMain/kotlin/com/hedvig/android/feature/claimhistory/GetClaimsHistoryUseCase.kt +++ b/app/feature/feature-claim-history/src/commonMain/kotlin/com/hedvig/android/feature/claimhistory/GetClaimsHistoryUseCase.kt @@ -6,9 +6,12 @@ import com.apollographql.apollo.ApolloClient import com.hedvig.android.apollo.ErrorMessage import com.hedvig.android.apollo.safeFlow import com.hedvig.android.core.common.ErrorMessage +import kotlin.time.Clock import kotlin.time.Instant import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn import octopus.ClaimsHistoryQuery import octopus.type.ClaimOutcome @@ -21,30 +24,32 @@ internal class GetClaimsHistoryUseCase( .safeFlow(::ErrorMessage) .map { result -> either { - result - .bind() - .currentMember - .claimsHistory - .map { history -> - ClaimHistory( - id = history.id, - claimType = history.claimType, - submittedAt = history.submittedAt, - outcome = when (history.outcome) { - ClaimOutcome.PAID -> ClaimHistory.ClaimOutcome.PAID - - ClaimOutcome.NOT_COMPENSATED -> ClaimHistory.ClaimOutcome.NOT_COMPENSATED - - ClaimOutcome.NOT_COVERED -> ClaimHistory.ClaimOutcome.NOT_COVERED - - ClaimOutcome.UNRESPONSIVE -> ClaimHistory.ClaimOutcome.UNRESPONSIVE - - ClaimOutcome.UNKNOWN__, - null, - -> ClaimHistory.ClaimOutcome.UNKNOWN - }, - ) - } + val data = result.bind() + val regularClaims = data.currentMember.claimsHistory.map { history -> + ClaimHistory( + id = history.id, + claimType = history.claimType, + submittedAt = history.submittedAt, + outcome = when (history.outcome) { + ClaimOutcome.PAID -> ClaimHistory.ClaimOutcome.PAID + ClaimOutcome.NOT_COMPENSATED -> ClaimHistory.ClaimOutcome.NOT_COMPENSATED + ClaimOutcome.NOT_COVERED -> ClaimHistory.ClaimOutcome.NOT_COVERED + ClaimOutcome.UNRESPONSIVE -> ClaimHistory.ClaimOutcome.UNRESPONSIVE + ClaimOutcome.UNKNOWN__, null -> ClaimHistory.ClaimOutcome.UNKNOWN + }, + isPartnerClaim = false, + ) + } + val partnerClaims = data.currentMember.partnerClaimsHistory.map { history -> + ClaimHistory( + id = history.id, + claimType = history.claimType, + submittedAt = history.submittedAt?.atStartOfDayIn(TimeZone.UTC) ?: Clock.System.now(), + outcome = ClaimHistory.ClaimOutcome.UNKNOWN, + isPartnerClaim = true, + ) + } + (regularClaims + partnerClaims).sortedByDescending { it.submittedAt } } } } @@ -57,6 +62,7 @@ internal data class ClaimHistory( // Subtitle uses this date to show when the claim was submitted val submittedAt: Instant, val outcome: ClaimOutcome?, + val isPartnerClaim: Boolean, ) { enum class ClaimOutcome { PAID, From f5210ee24984c177a4408552b440ea6863736c8f Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:23:52 +0200 Subject: [PATCH 07/30] refactor: extract shared claim detail composables to ui-claim-status Co-Authored-By: Claude Sonnet 4.6 --- .../details/ui/ClaimDetailsDestination.kt | 143 +--------------- app/ui/claim-status/build.gradle.kts | 2 + .../ClaimDetailSharedComponents.kt | 157 ++++++++++++++++++ 3 files changed, 168 insertions(+), 134 deletions(-) create mode 100644 app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt index 7ab7f80e6a..79806be362 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -28,7 +27,6 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -47,20 +45,15 @@ import com.hedvig.android.compose.photo.capture.state.rememberPhotoCaptureState import com.hedvig.android.compose.ui.LayoutWithoutPlacement import com.hedvig.android.compose.ui.plus import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider -import com.hedvig.android.compose.ui.stringWithShiftedLabel import com.hedvig.android.core.common.safeCast import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.data.display.items.DisplayItem -import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Date -import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.DateTime import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Text -import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Medium import com.hedvig.android.design.system.hedvig.ErrorDialog import com.hedvig.android.design.system.hedvig.File -import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection @@ -69,32 +62,30 @@ import com.hedvig.android.design.system.hedvig.HedvigMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigThreeDotsProgressIndicator 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.IconButton -import com.hedvig.android.design.system.hedvig.LocalContentColor -import com.hedvig.android.design.system.hedvig.LocalTextStyle import com.hedvig.android.design.system.hedvig.NotificationDefaults import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.TopAppBar import com.hedvig.android.design.system.hedvig.TopAppBarActionType.BACK -import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast import com.hedvig.android.design.system.hedvig.icon.Chat import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.InfoFilled import com.hedvig.android.design.system.hedvig.notificationCircle import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader import com.hedvig.android.design.system.hedvig.show import com.hedvig.android.logger.logcat import com.hedvig.android.shared.file.upload.ui.FilePickerBottomSheet +import com.hedvig.android.ui.claimstatus.ClaimDisplayItemsSection +import com.hedvig.android.ui.claimstatus.ClaimDocumentCard +import com.hedvig.android.ui.claimstatus.ClaimExplanationBottomSheet import com.hedvig.android.ui.claimstatus.ClaimStatusCard +import com.hedvig.android.ui.claimstatus.ClaimTermsConditionsCard import com.hedvig.android.ui.claimstatus.model.ClaimPillType import com.hedvig.android.ui.claimstatus.model.ClaimProgressSegment import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState @@ -102,15 +93,12 @@ import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.audio.player.data.SignedAudioUrl import hedvig.resources.CLAIMS_YOUR_CLAIM import hedvig.resources.DASHBOARD_OPEN_CHAT -import hedvig.resources.MY_DOCUMENTS_INSURANCE_TERMS import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION import hedvig.resources.Res -import hedvig.resources.TALKBACK_OPEN_EXTERNAL_LINK import hedvig.resources.claim_outcome_unresponsive_support_text import hedvig.resources.claim_status_appeal_instruction_link_text import hedvig.resources.claim_status_being_handled_reopened_support_text import hedvig.resources.claim_status_being_handled_support_text -import hedvig.resources.claim_status_claim_details_info_text import hedvig.resources.claim_status_claim_details_title import hedvig.resources.claim_status_detail_add_files import hedvig.resources.claim_status_detail_add_more_files @@ -122,7 +110,6 @@ import hedvig.resources.claim_status_not_covered_support_text import hedvig.resources.claim_status_paid_support_text_short import hedvig.resources.claim_status_submitted_support_text import hedvig.resources.claim_status_uploaded_files_upload_text -import hedvig.resources.general_close_button import hedvig.resources.general_error import hedvig.resources.something_went_wrong import hedvig.resources.travel_certificate_downloading_error @@ -323,7 +310,7 @@ private fun NonDynamicGrid( navigateToConversation: (() -> Unit)?, ) { val explanationBottomSheetState = rememberHedvigBottomSheetState() - ExplanationBottomSheet(explanationBottomSheetState) + ClaimExplanationBottomSheet(explanationBottomSheetState) Column( Modifier .padding( @@ -379,26 +366,6 @@ private fun NonDynamicGrid( } } -@Composable -internal fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { - HedvigBottomSheet(sheetState) { _ -> - HedvigText( - text = stringResource(Res.string.claim_status_claim_details_info_text), - modifier = Modifier - .fillMaxWidth(), - ) - Spacer(Modifier.height(32.dp)) - HedvigTextButton( - text = stringResource(Res.string.general_close_button), - buttonSize = Large, - onClick = { sheetState.dismiss() }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(8.dp)) - Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) - } -} - @Composable private fun BeforeGridContent( uiState: ClaimDetailUiState.Content, @@ -498,7 +465,7 @@ private fun BeforeGridContent( ) Spacer(Modifier.height(8.dp)) - DisplayItemsSection( + ClaimDisplayItemsSection( displayItems = uiState.displayItems, modifier = Modifier .fillMaxWidth() @@ -582,7 +549,7 @@ private fun AfterGridContent( Spacer(Modifier.height(8.dp)) } if (uiState.termsConditionsUrl != null) { - TermsConditionsCard( + ClaimTermsConditionsCard( onClick = { downloadFromUrl(uiState.termsConditionsUrl) }, modifier = Modifier.padding(16.dp), isLoading = uiState.termsConditionsUrl == uiState.isLoadingPdf, @@ -600,38 +567,6 @@ private fun AfterGridContent( } } -@Composable -private fun TermsConditionsCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { - HedvigCard(onClick = onClick) { - Row( - modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - if (isLoading) { - LayoutWithoutPlacement( - sizeAdjustingContent = { - DocumentCard( - title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), - ) - }, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - HedvigThreeDotsProgressIndicator() - } - } - } else { - DocumentCard( - title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), - ) - } - } - } -} - @Composable private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { HedvigCard(onClick = onClick) { @@ -642,7 +577,7 @@ private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modif if (isLoading) { LayoutWithoutPlacement( sizeAdjustingContent = { - DocumentCard( + ClaimDocumentCard( title = stringResource(Res.string.claim_status_appeal_instruction_link_text), ) }, @@ -656,7 +591,7 @@ private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modif } } } else { - DocumentCard( + ClaimDocumentCard( title = stringResource(Res.string.claim_status_appeal_instruction_link_text), ) } @@ -664,39 +599,6 @@ private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modif } } -@Composable -private fun DocumentCard(title: String) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - Column { - HedvigText( - text = stringWithShiftedLabel( - text = title, - labelText = "PDF", - labelFontSize = HedvigTheme.typography.label.fontSize, - textColor = LocalContentColor.current, - textFontSize = LocalTextStyle.current.fontSize, - ), - ) - } - }, - endSlot = { - Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = HedvigIcons.ArrowNorthEast, - contentDescription = stringResource(Res.string.TALKBACK_OPEN_EXTERNAL_LINK), - modifier = Modifier.size(16.dp), - ) - } - }, - spaceBetween = 8.dp, - ) - } -} - @Composable private fun statusParagraphText( claimStatus: ClaimDetailUiState.Content.ClaimStatus, @@ -755,33 +657,6 @@ private fun ClaimDetailHedvigAudioPlayerItem(signedAudioUrl: SignedAudioUrl, mod } } -@Composable -private fun DisplayItemsSection(displayItems: List, modifier: Modifier = Modifier) { - CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { - Column(modifier) { - for (displayItem in displayItems) { - HorizontalItemsWithMaximumSpaceTaken( - spaceBetween = 8.dp, - startSlot = { - HedvigText(text = displayItem.title) - }, - endSlot = { - val formatter = rememberHedvigDateTimeFormatter() - HedvigText( - text = when (val item = displayItem.value) { - is Date -> formatter.format(item.date) - is DateTime -> formatter.format(item.localDateTime) - is Text -> item.text - }, - textAlign = TextAlign.End, - ) - }, - ) - } - } - } -} - @HedvigPreview @Composable private fun PreviewClaimDetailScreen( diff --git a/app/ui/claim-status/build.gradle.kts b/app/ui/claim-status/build.gradle.kts index 26f86c7139..4ccc8d7215 100644 --- a/app/ui/claim-status/build.gradle.kts +++ b/app/ui/claim-status/build.gradle.kts @@ -14,7 +14,9 @@ dependencies { implementation(libs.jetbrains.compose.ui) implementation(projects.apolloOctopusPublic) implementation(projects.composePagerIndicator) + implementation(projects.composeUi) implementation(projects.coreResources) implementation(projects.coreUiData) + implementation(projects.dataDisplayItems) implementation(projects.designSystemHedvig) } diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt new file mode 100644 index 0000000000..d6eae4516e --- /dev/null +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt @@ -0,0 +1,157 @@ +package com.hedvig.android.ui.claimstatus + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.hedvig.android.compose.ui.LayoutWithoutPlacement +import com.hedvig.android.compose.ui.stringWithShiftedLabel +import com.hedvig.android.data.display.items.DisplayItem +import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Date +import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.DateTime +import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Text +import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet +import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HedvigThreeDotsProgressIndicator +import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.LocalContentColor +import com.hedvig.android.design.system.hedvig.LocalTextStyle +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter +import hedvig.resources.MY_DOCUMENTS_INSURANCE_TERMS +import hedvig.resources.Res +import hedvig.resources.TALKBACK_OPEN_EXTERNAL_LINK +import hedvig.resources.claim_status_claim_details_info_text +import hedvig.resources.general_close_button +import org.jetbrains.compose.resources.stringResource + +@Composable +fun ClaimExplanationBottomSheet(sheetState: HedvigBottomSheetState) { + HedvigBottomSheet(sheetState) { _ -> + HedvigText( + text = stringResource(Res.string.claim_status_claim_details_info_text), + modifier = Modifier + .fillMaxWidth(), + ) + Spacer(Modifier.height(32.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_close_button), + buttonSize = Large, + onClick = { sheetState.dismiss() }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } +} + +@Composable +fun ClaimDisplayItemsSection(displayItems: List, modifier: Modifier = Modifier) { + CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { + Column(modifier) { + for (displayItem in displayItems) { + HorizontalItemsWithMaximumSpaceTaken( + spaceBetween = 8.dp, + startSlot = { + HedvigText(text = displayItem.title) + }, + endSlot = { + val formatter = rememberHedvigDateTimeFormatter() + HedvigText( + text = when (val item = displayItem.value) { + is Date -> formatter.format(item.date) + is DateTime -> formatter.format(item.localDateTime) + is Text -> item.text + }, + textAlign = TextAlign.End, + ) + }, + ) + } + } + } +} + +@Composable +fun ClaimTermsConditionsCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { + HedvigCard(onClick = onClick) { + Row( + modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + if (isLoading) { + LayoutWithoutPlacement( + sizeAdjustingContent = { + ClaimDocumentCard( + title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), + ) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + HedvigThreeDotsProgressIndicator() + } + } + } else { + ClaimDocumentCard( + title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), + ) + } + } + } +} + +@Composable +fun ClaimDocumentCard(title: String) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + HorizontalItemsWithMaximumSpaceTaken( + startSlot = { + Column { + HedvigText( + text = stringWithShiftedLabel( + text = title, + labelText = "PDF", + labelFontSize = HedvigTheme.typography.label.fontSize, + textColor = LocalContentColor.current, + textFontSize = LocalTextStyle.current.fontSize, + ), + ) + } + }, + endSlot = { + Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = HedvigIcons.ArrowNorthEast, + contentDescription = stringResource(Res.string.TALKBACK_OPEN_EXTERNAL_LINK), + modifier = Modifier.size(16.dp), + ) + } + }, + spaceBetween = 8.dp, + ) + } +} From 774062367196c448bbf9d76c835efa96639acc7f Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:37:37 +0200 Subject: [PATCH 08/30] feat: add partner claim details module with simplified details screen Co-Authored-By: Claude Sonnet 4.6 --- .../build.gradle.kts | 34 +++ .../claim/details/QueryPartnerClaim.graphql | 5 + .../data/GetPartnerClaimDetailUseCase.kt | 58 +++++ .../details/di/PartnerClaimDetailsModule.kt | 19 ++ .../PartnerClaimDetailDestinations.kt | 9 + .../navigation/PartnerClaimDetailGraph.kt | 22 ++ .../ui/PartnerClaimDetailsDestination.kt | 216 ++++++++++++++++++ .../ui/PartnerClaimDetailsViewModel.kt | 69 ++++++ 8 files changed, 432 insertions(+) create mode 100644 app/feature/feature-partner-claim-details/build.gradle.kts create mode 100644 app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql create mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt create mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt create mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt create mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt create mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt create mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt diff --git a/app/feature/feature-partner-claim-details/build.gradle.kts b/app/feature/feature-partner-claim-details/build.gradle.kts new file mode 100644 index 0000000000..e0845494a9 --- /dev/null +++ b/app/feature/feature-partner-claim-details/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id("hedvig.android.library") + id("hedvig.gradle.plugin") +} + +hedvig { + apollo("octopus") + serialization() + compose() +} + +dependencies { + implementation(libs.androidx.navigation.compose) + implementation(libs.apollo.normalizedCache) + implementation(libs.apollo.runtime) + implementation(libs.arrow.core) + implementation(libs.jetbrains.lifecycle.runtime.compose) + implementation(libs.koin.composeViewModel) + implementation(libs.koin.core) + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.serialization.core) + implementation(projects.apolloCore) + implementation(projects.apolloOctopusPublic) + implementation(projects.claimStatus) + implementation(projects.composeUi) + implementation(projects.coreCommonPublic) + implementation(projects.coreResources) + implementation(projects.coreUiData) + implementation(projects.dataDisplayItems) + implementation(projects.designSystemHedvig) + implementation(projects.moleculePublic) + implementation(projects.navigationCommon) + implementation(projects.navigationCompose) +} diff --git a/app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql b/app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql new file mode 100644 index 0000000000..d6da1d153a --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql @@ -0,0 +1,5 @@ +query PartnerClaimDetail($claimId: ID!) { + partnerClaim(id: $claimId) { + ...PartnerClaimFragment + } +} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt new file mode 100644 index 0000000000..6008192319 --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt @@ -0,0 +1,58 @@ +package com.hedvig.android.feature.partner.claim.details.data + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensureNotNull +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.cache.normalized.FetchPolicy +import com.apollographql.apollo.cache.normalized.fetchPolicy +import com.hedvig.android.apollo.safeFlow +import com.hedvig.android.data.display.items.DisplayItem +import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailUiState +import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive +import octopus.PartnerClaimDetailQuery +import octopus.type.InsuranceDocumentType + +internal class GetPartnerClaimDetailUseCase( + private val apolloClient: ApolloClient, +) { + fun invoke(claimId: String): Flow> { + return flow { + while (currentCoroutineContext().isActive) { + emitAll( + apolloClient.query(PartnerClaimDetailQuery(claimId)) + .fetchPolicy(FetchPolicy.NetworkOnly) + .safeFlow { Error } + .map { result -> + either { + val data = result.bind() + val claim = data.partnerClaim + ensureNotNull(claim) { Error } + PartnerClaimDetailUiState.Content( + claimStatusCardUiState = ClaimStatusCardUiState.fromPartnerClaim(claim), + claimStatus = claim.status, + displayItems = claim.displayItems.map { + DisplayItem.fromStrings(it.displayTitle, it.displayValue) + }, + handlerEmail = claim.handlerEmail, + termsConditionsUrl = claim.productVariant?.documents + ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS }?.url, + ) + } + }, + ) + delay(10.seconds) + } + } + } + + data object Error +} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt new file mode 100644 index 0000000000..537fa67aff --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt @@ -0,0 +1,19 @@ +package com.hedvig.android.feature.partner.claim.details.di + +import com.apollographql.apollo.ApolloClient +import com.hedvig.android.feature.partner.claim.details.data.GetPartnerClaimDetailUseCase +import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailsViewModel +import org.koin.core.module.dsl.viewModel +import org.koin.dsl.module + +val partnerClaimDetailsModule = module { + single { + GetPartnerClaimDetailUseCase(get()) + } + viewModel { (claimId: String) -> + PartnerClaimDetailsViewModel( + claimId = claimId, + getPartnerClaimDetailUseCase = get(), + ) + } +} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt new file mode 100644 index 0000000000..b7ccb4999b --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt @@ -0,0 +1,9 @@ +package com.hedvig.android.feature.partner.claim.details.navigation + +import com.hedvig.android.navigation.common.Destination +import kotlinx.serialization.Serializable + +@Serializable +data class PartnerClaimOverviewDestination( + val claimId: String, +) : Destination diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt new file mode 100644 index 0000000000..68514acb54 --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt @@ -0,0 +1,22 @@ +package com.hedvig.android.feature.partner.claim.details.navigation + +import androidx.navigation.NavGraphBuilder +import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailsDestination +import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailsViewModel +import com.hedvig.android.navigation.compose.navdestination +import org.koin.compose.viewmodel.koinViewModel +import org.koin.core.parameter.parametersOf + +fun NavGraphBuilder.partnerClaimDetailsGraph( + navigateUp: () -> Unit, + openUrl: (String) -> Unit, +) { + navdestination { + val viewModel: PartnerClaimDetailsViewModel = koinViewModel { parametersOf(claimId) } + PartnerClaimDetailsDestination( + viewModel = viewModel, + navigateUp = navigateUp, + openUrl = openUrl, + ) + } +} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt new file mode 100644 index 0000000000..06f4f22008 --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt @@ -0,0 +1,216 @@ +package com.hedvig.android.feature.partner.claim.details.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.asPaddingValues +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.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.compose.ui.plus +import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigErrorSection +import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced +import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken +import com.hedvig.android.design.system.hedvig.Icon +import com.hedvig.android.design.system.hedvig.IconButton +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.TopAppBar +import com.hedvig.android.design.system.hedvig.TopAppBarActionType.BACK +import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons +import com.hedvig.android.design.system.hedvig.icon.InfoFilled +import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.show +import com.hedvig.android.ui.claimstatus.ClaimDisplayItemsSection +import com.hedvig.android.ui.claimstatus.ClaimExplanationBottomSheet +import com.hedvig.android.ui.claimstatus.ClaimStatusCard +import com.hedvig.android.ui.claimstatus.ClaimTermsConditionsCard +import hedvig.resources.CLAIMS_YOUR_CLAIM +import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION +import hedvig.resources.Res +import hedvig.resources.claim_status_being_handled_support_text +import hedvig.resources.claim_status_claim_details_title +import hedvig.resources.claim_status_detail_documents_title +import octopus.type.ClaimStatus +import org.jetbrains.compose.resources.stringResource + +@Composable +internal fun PartnerClaimDetailsDestination( + viewModel: PartnerClaimDetailsViewModel, + navigateUp: () -> Unit, + openUrl: (String) -> Unit, +) { + val viewState by viewModel.uiState.collectAsStateWithLifecycle() + PartnerClaimDetailScreen( + uiState = viewState, + navigateUp = navigateUp, + openUrl = openUrl, + retry = { viewModel.emit(PartnerClaimDetailEvent.Retry) }, + ) +} + +@Composable +private fun PartnerClaimDetailScreen( + uiState: PartnerClaimDetailUiState, + navigateUp: () -> Unit, + openUrl: (String) -> Unit, + retry: () -> Unit, +) { + Surface( + color = HedvigTheme.colorScheme.backgroundPrimary, + modifier = Modifier.fillMaxSize(), + ) { + Column(Modifier.fillMaxSize()) { + TopAppBar( + title = stringResource(Res.string.CLAIMS_YOUR_CLAIM), + actionType = BACK, + onActionClick = navigateUp, + ) + when (uiState) { + is PartnerClaimDetailUiState.Content -> { + PartnerClaimDetailContent( + uiState = uiState, + openUrl = openUrl, + ) + } + + PartnerClaimDetailUiState.Error -> { + Spacer(Modifier.weight(1f)) + HedvigErrorSection(onButtonClick = retry) + Spacer(Modifier.weight(1f)) + } + + PartnerClaimDetailUiState.Loading -> { + HedvigFullScreenCenterAlignedProgressDebounced() + } + } + } + } +} + +@Composable +private fun PartnerClaimDetailContent( + uiState: PartnerClaimDetailUiState.Content, + openUrl: (String) -> Unit, +) { + val explanationBottomSheetState = rememberHedvigBottomSheetState() + ClaimExplanationBottomSheet(explanationBottomSheetState) + Column( + Modifier + .padding( + PaddingValues(horizontal = 16.dp) + WindowInsets.safeDrawing + .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) + .asPaddingValues(), + ) + .verticalScroll(rememberScrollState()), + ) { + Spacer(Modifier.height(8.dp)) + ClaimStatusCard(uiState = uiState.claimStatusCardUiState) + if (uiState.claimStatus == ClaimStatus.IN_PROGRESS) { + Spacer(Modifier.height(8.dp)) + HedvigCard { + Column(modifier = Modifier.padding(16.dp)) { + HedvigText( + text = stringResource(Res.string.claim_status_being_handled_support_text), + style = HedvigTheme.typography.bodySmall, + ) + } + } + } + Spacer(Modifier.height(24.dp)) + HorizontalItemsWithMaximumSpaceTaken( + startSlot = { + Row(verticalAlignment = Alignment.CenterVertically) { + HedvigText( + stringResource(Res.string.claim_status_claim_details_title), + Modifier.padding(horizontal = 2.dp), + ) + } + }, + endSlot = { + Row(horizontalArrangement = Arrangement.End) { + IconButton( + onClick = { explanationBottomSheetState.show(Unit) }, + modifier = Modifier.size(40.dp), + ) { + Icon( + imageVector = HedvigIcons.InfoFilled, + contentDescription = stringResource(Res.string.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION), + modifier = Modifier.size(24.dp), + ) + } + } + }, + spaceBetween = 8.dp, + ) + Spacer(Modifier.height(8.dp)) + ClaimDisplayItemsSection( + displayItems = uiState.displayItems, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 2.dp), + ) + if (uiState.handlerEmail != null) { + Spacer(Modifier.height(24.dp)) + HedvigCard { + HorizontalItemsWithMaximumSpaceTaken( + modifier = Modifier + .clip(HedvigTheme.shapes.cornerXSmall) + .clickable { openUrl("mailto:${uiState.handlerEmail}") } + .padding(16.dp), + startSlot = { + HedvigText( + text = uiState.handlerEmail, + style = HedvigTheme.typography.bodySmall, + modifier = Modifier.wrapContentSize(Alignment.CenterStart), + ) + }, + endSlot = { + Icon( + imageVector = HedvigIcons.ArrowNorthEast, + contentDescription = null, + modifier = Modifier.size(16.dp), + ) + }, + spaceBetween = 8.dp, + ) + } + } + if (uiState.termsConditionsUrl != null) { + Spacer(Modifier.height(24.dp)) + HedvigText( + stringResource(Res.string.claim_status_detail_documents_title), + Modifier.padding(horizontal = 2.dp), + ) + Spacer(Modifier.height(8.dp)) + ClaimTermsConditionsCard( + onClick = { openUrl(uiState.termsConditionsUrl) }, + isLoading = false, + modifier = Modifier.padding(16.dp), + ) + } + Spacer(Modifier.height(16.dp)) + } +} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt new file mode 100644 index 0000000000..acd9d85ca6 --- /dev/null +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt @@ -0,0 +1,69 @@ +package com.hedvig.android.feature.partner.claim.details.ui + +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 arrow.core.merge +import arrow.core.raise.either +import com.hedvig.android.data.display.items.DisplayItem +import com.hedvig.android.feature.partner.claim.details.data.GetPartnerClaimDetailUseCase +import com.hedvig.android.molecule.public.MoleculePresenter +import com.hedvig.android.molecule.public.MoleculePresenterScope +import com.hedvig.android.molecule.public.MoleculeViewModel +import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState +import kotlinx.coroutines.flow.collectLatest +import octopus.type.ClaimStatus + +internal class PartnerClaimDetailsViewModel( + claimId: String, + getPartnerClaimDetailUseCase: GetPartnerClaimDetailUseCase, +) : MoleculeViewModel( + PartnerClaimDetailUiState.Loading, + PartnerClaimDetailPresenter(claimId, getPartnerClaimDetailUseCase), + ) + +private class PartnerClaimDetailPresenter( + private val claimId: String, + private val getPartnerClaimDetailUseCase: GetPartnerClaimDetailUseCase, +) : MoleculePresenter { + @Composable + override fun MoleculePresenterScope.present( + lastState: PartnerClaimDetailUiState, + ): PartnerClaimDetailUiState { + var state by remember { mutableStateOf(lastState) } + var loadIteration by remember { mutableIntStateOf(0) } + CollectEvents { event -> + when (event) { + PartnerClaimDetailEvent.Retry -> loadIteration++ + } + } + LaunchedEffect(loadIteration) { + getPartnerClaimDetailUseCase.invoke(claimId).collectLatest { result -> + state = either { + result.mapLeft { PartnerClaimDetailUiState.Error }.bind() + }.merge() + } + } + return state + } +} + +internal sealed interface PartnerClaimDetailEvent { + data object Retry : PartnerClaimDetailEvent +} + +internal sealed interface PartnerClaimDetailUiState { + data object Loading : PartnerClaimDetailUiState + data object Error : PartnerClaimDetailUiState + data class Content( + val claimStatusCardUiState: ClaimStatusCardUiState, + val claimStatus: ClaimStatus?, + val displayItems: List, + val handlerEmail: String?, + val termsConditionsUrl: String?, + ) : PartnerClaimDetailUiState +} From d7cce7099e92f054f96b1922f7c339f68e20dc97 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 10:43:26 +0200 Subject: [PATCH 09/30] feat: wire partner claim details into app navigation and DI Co-Authored-By: Claude Sonnet 4.6 --- app/app/build.gradle.kts | 2 +- .../com/hedvig/android/app/di/ApplicationModule.kt | 2 ++ .../hedvig/android/app/navigation/HedvigNavHost.kt | 13 ++++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index a1029b89c3..018eacaa46 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -190,7 +190,7 @@ dependencies { implementation(projects.featureClaimChat) implementation(projects.featureClaimDetails) implementation(projects.featureClaimHistory) - + implementation(projects.featurePartnerClaimDetails) implementation(projects.featureConnectPaymentTrustly) implementation(projects.featureCrossSellSheet) implementation(projects.featureDeleteAccount) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt b/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt index b4d78717c5..661b573eea 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt @@ -78,6 +78,7 @@ import com.hedvig.android.feature.insurance.certificate.di.insuranceEvidenceModu import com.hedvig.android.feature.insurances.di.insurancesModule import com.hedvig.android.feature.login.di.loginModule import com.hedvig.android.feature.movingflow.di.movingFlowModule +import com.hedvig.android.feature.partner.claim.details.di.partnerClaimDetailsModule import com.hedvig.android.feature.payments.di.paymentsModule import com.hedvig.android.feature.profile.di.profileModule import com.hedvig.android.feature.terminateinsurance.di.terminateInsuranceModule @@ -344,6 +345,7 @@ val applicationModule = module { networkModule, notificationBadgeModule, notificationModule, + partnerClaimDetailsModule, paymentsModule, profileModule, settingsDatastoreModule, diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 3a1c3709b9..24dc791c02 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -32,6 +32,8 @@ import com.hedvig.android.feature.chip.id.navigation.ChipIdGraphDestination import com.hedvig.android.feature.chip.id.navigation.chipIdGraph import com.hedvig.android.feature.claim.details.navigation.ClaimDetailDestination import com.hedvig.android.feature.claim.details.navigation.claimDetailsGraph +import com.hedvig.android.feature.partner.claim.details.navigation.PartnerClaimOverviewDestination +import com.hedvig.android.feature.partner.claim.details.navigation.partnerClaimDetailsGraph import com.hedvig.android.feature.claimhistory.nav.ClaimHistoryDestination import com.hedvig.android.feature.claimhistory.nav.claimHistoryGraph import com.hedvig.android.feature.connect.payment.connectPaymentGraph @@ -183,6 +185,9 @@ internal fun HedvigNavHost( navigateToClaimDetails = { claimId -> navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) }, + navigateToPartnerClaimDetails = { claimId -> + navController.navigate(PartnerClaimOverviewDestination(claimId)) + }, navigateToConnectPayment = navigateToConnectPayment, navigateToMissingInfo = { contractId: String, type: CoInsuredFlowType -> navController.navigate(CoInsuredAddInfo(contractId, type)) @@ -354,7 +359,9 @@ internal fun HedvigNavHost( navigateToClaimDetails = { claimId -> navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) }, - navigateToPartnerClaimDetails = {}, + navigateToPartnerClaimDetails = { claimId -> + navController.navigate(PartnerClaimOverviewDestination(claimId)) + }, ) }, popBackStackOrFinish = popBackStackOrFinish, @@ -556,6 +563,10 @@ private fun NavGraphBuilder.nestedHomeGraphs( applicationId = hedvigBuildConstants.appPackageId, hedvigDeepLinkContainer = hedvigDeepLinkContainer, ) + partnerClaimDetailsGraph( + navigateUp = navController::navigateUp, + openUrl = openUrl, + ) travelCertificateGraph( navController = navController, applicationId = hedvigBuildConstants.appPackageId, From ad5512eb7bf9043b33f6f35111e00059f3e6af6d Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 11:47:22 +0200 Subject: [PATCH 10/30] fix: right-align arrow icon in partner claim email row Co-Authored-By: Claude Opus 4.6 (1M context) --- .../details/ui/PartnerClaimDetailsDestination.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt index 06f4f22008..8bbd68496a 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt @@ -188,11 +188,13 @@ private fun PartnerClaimDetailContent( ) }, endSlot = { - Icon( - imageVector = HedvigIcons.ArrowNorthEast, - contentDescription = null, - modifier = Modifier.size(16.dp), - ) + Row(horizontalArrangement = Arrangement.End) { + Icon( + imageVector = HedvigIcons.ArrowNorthEast, + contentDescription = null, + modifier = Modifier.size(16.dp), + ) + } }, spaceBetween = 8.dp, ) From c43dae0ff19b2530a0f571b292240418badd0382 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 11:51:21 +0200 Subject: [PATCH 11/30] fix: match email row styling to chat row pattern in claim details Use IconButton with wrapContentSize(CenterEnd) and Column padding, matching the established pattern from the regular claim details screen. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ui/PartnerClaimDetailsDestination.kt | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt index 8bbd68496a..4fab79ffad 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt @@ -175,29 +175,36 @@ private fun PartnerClaimDetailContent( if (uiState.handlerEmail != null) { Spacer(Modifier.height(24.dp)) HedvigCard { - HorizontalItemsWithMaximumSpaceTaken( - modifier = Modifier - .clip(HedvigTheme.shapes.cornerXSmall) - .clickable { openUrl("mailto:${uiState.handlerEmail}") } - .padding(16.dp), - startSlot = { - HedvigText( - text = uiState.handlerEmail, - style = HedvigTheme.typography.bodySmall, - modifier = Modifier.wrapContentSize(Alignment.CenterStart), - ) - }, - endSlot = { - Row(horizontalArrangement = Arrangement.End) { - Icon( - imageVector = HedvigIcons.ArrowNorthEast, - contentDescription = null, - modifier = Modifier.size(16.dp), + Column(modifier = Modifier.padding(16.dp)) { + HorizontalItemsWithMaximumSpaceTaken( + modifier = Modifier + .clip(HedvigTheme.shapes.cornerXSmall) + .clickable { openUrl("mailto:${uiState.handlerEmail}") }, + startSlot = { + HedvigText( + text = uiState.handlerEmail, + style = HedvigTheme.typography.bodySmall, + modifier = Modifier.wrapContentSize(Alignment.CenterStart), ) - } - }, - spaceBetween = 8.dp, - ) + }, + endSlot = { + IconButton( + onClick = { openUrl("mailto:${uiState.handlerEmail}") }, + modifier = Modifier + .size(40.dp) + .wrapContentSize(Alignment.CenterEnd), + ) { + Icon( + imageVector = HedvigIcons.ArrowNorthEast, + contentDescription = null, + tint = HedvigTheme.colorScheme.signalGreyElement, + modifier = Modifier.size(16.dp), + ) + } + }, + spaceBetween = 8.dp, + ) + } } } if (uiState.termsConditionsUrl != null) { From ad3d88c089582f8860b9800a79583d62b1e83531 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 12:32:43 +0200 Subject: [PATCH 12/30] fix: simplify email row to plain clickable card Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ui/PartnerClaimDetailsDestination.kt | 39 ++++--------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt index 4fab79ffad..3cc7aee52d 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt @@ -174,37 +174,14 @@ private fun PartnerClaimDetailContent( ) if (uiState.handlerEmail != null) { Spacer(Modifier.height(24.dp)) - HedvigCard { - Column(modifier = Modifier.padding(16.dp)) { - HorizontalItemsWithMaximumSpaceTaken( - modifier = Modifier - .clip(HedvigTheme.shapes.cornerXSmall) - .clickable { openUrl("mailto:${uiState.handlerEmail}") }, - startSlot = { - HedvigText( - text = uiState.handlerEmail, - style = HedvigTheme.typography.bodySmall, - modifier = Modifier.wrapContentSize(Alignment.CenterStart), - ) - }, - endSlot = { - IconButton( - onClick = { openUrl("mailto:${uiState.handlerEmail}") }, - modifier = Modifier - .size(40.dp) - .wrapContentSize(Alignment.CenterEnd), - ) { - Icon( - imageVector = HedvigIcons.ArrowNorthEast, - contentDescription = null, - tint = HedvigTheme.colorScheme.signalGreyElement, - modifier = Modifier.size(16.dp), - ) - } - }, - spaceBetween = 8.dp, - ) - } + HedvigCard( + onClick = { openUrl("mailto:${uiState.handlerEmail}") }, + ) { + HedvigText( + text = uiState.handlerEmail, + style = HedvigTheme.typography.bodySmall, + modifier = Modifier.padding(16.dp), + ) } } if (uiState.termsConditionsUrl != null) { From de0f652d6d52b4fae7b4a0cc94827c592e702a19 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 12:35:41 +0200 Subject: [PATCH 13/30] fix: remove handler email from partner claim details screen Co-Authored-By: Claude Opus 4.6 (1M context) --- .../details/data/GetPartnerClaimDetailUseCase.kt | 1 - .../details/ui/PartnerClaimDetailsDestination.kt | 12 ------------ .../claim/details/ui/PartnerClaimDetailsViewModel.kt | 1 - 3 files changed, 14 deletions(-) diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt index 6008192319..b65e468dcc 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt @@ -42,7 +42,6 @@ internal class GetPartnerClaimDetailUseCase( displayItems = claim.displayItems.map { DisplayItem.fromStrings(it.displayTitle, it.displayValue) }, - handlerEmail = claim.handlerEmail, termsConditionsUrl = claim.productVariant?.documents ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS }?.url, ) diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt index 3cc7aee52d..79a7c1cd9a 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt @@ -172,18 +172,6 @@ private fun PartnerClaimDetailContent( .fillMaxWidth() .padding(horizontal = 2.dp), ) - if (uiState.handlerEmail != null) { - Spacer(Modifier.height(24.dp)) - HedvigCard( - onClick = { openUrl("mailto:${uiState.handlerEmail}") }, - ) { - HedvigText( - text = uiState.handlerEmail, - style = HedvigTheme.typography.bodySmall, - modifier = Modifier.padding(16.dp), - ) - } - } if (uiState.termsConditionsUrl != null) { Spacer(Modifier.height(24.dp)) HedvigText( diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt index acd9d85ca6..aed91b9eea 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt @@ -63,7 +63,6 @@ internal sealed interface PartnerClaimDetailUiState { val claimStatusCardUiState: ClaimStatusCardUiState, val claimStatus: ClaimStatus?, val displayItems: List, - val handlerEmail: String?, val termsConditionsUrl: String?, ) : PartnerClaimDetailUiState } From 7b9621a54c4555f41563cc39d7754e0581592d2d Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 12:54:28 +0200 Subject: [PATCH 14/30] chore: update GraphQL schema from introspection Co-Authored-By: Claude Opus 4.6 (1M context) --- .../android/apollo/octopus/schema.graphqls | 274 +++++++++++++----- 1 file changed, 208 insertions(+), 66 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 3bba2b35f0..eaf47adc1a 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 @@ -468,6 +468,41 @@ type BundleYearlySavings { """ bundleDiscountCoversFullPeriod: Boolean! } +""" +SHA-256-hashed user data for Facebook Conversions API (CAPI). +All non-null field values are hex-encoded SHA-256 hashes of the normalized plaintext. +Normalization follows https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters +""" +type CapiUserData { + """ + Hashed email (em) + """ + em: String! + """ + Hashed Gmail-normalized email, only present for Gmail addresses (gem) + """ + gem: String + """ + Hashed first name (fn) + """ + fn: String! + """ + Hashed last name (ln) + """ + ln: String! + """ + Hashed postal/zip code, null if unavailable (zp) + """ + zp: String + """ + Hashed city, null if unavailable (ct) + """ + ct: String + """ + Hashed country code, e.g. 'se' (co) + """ + co: String! +} type CarItemNotification { message: String! } @@ -1184,19 +1219,6 @@ type ClaimMutationOutput { claim: Claim userError: UserError } -type PartnerClaim { - id: ID! - externalId: String! - exposureDisplayName: String - status: ClaimStatus - submittedAt: Date - payoutAmount: Money - associatedTypeOfContract: String - claimType: String - handlerEmail: String - displayItems: [ClaimDisplayItem!]! - productVariant: ProductVariant -} enum ClaimOutcome { PAID NOT_COMPENSATED @@ -1700,15 +1722,15 @@ returned as ExtendedItemDiscount """ type ExtendedItemDiscount { """ - General discount information + General discount information """ itemDiscount: ItemDiscount! """ - Monthly reduction applied by the discount. It's a negative number + Monthly reduction applied by the discount. It's a negative number """ amount: Money! """ - Whether discount is on a pending state or not + Whether discount is on a pending state or not """ isPending: Boolean! } @@ -1741,6 +1763,11 @@ type ExternalInsurer { displayName: String! insurelyId: String } +type FetchedExternalInsurance { + displayName: String! + subtitle: String + insurer: ExternalInsurer! +} type FirstVetAction { sections: [FirstVetSection!]! } @@ -2492,6 +2519,11 @@ type Member { """ crossSellV2(input: CrossSellInput!): CrossSellV2! """ + Young Pet Guide stories for the member. + Returns a list of educational content stories for young pet owners. + """ + puppyGuideStories: [PuppyGuideStory!]! + """ Fetch all the active contracts for this member. Active contracts include all insurances that are either active today, or to-be-active in the future. """ @@ -2744,6 +2776,22 @@ type MemberPaymentAvailablePaymentMethod { True if the member can set up this payment method for payout. """ supportsPayout: Boolean! + """ + True if this method is already ACTIVE for member and can be chosen as default directly without setup, false if + this is a new payment method that the member has not yet set up. + If this is true, then the `details` field will be populated with the payment method details. If this is false, then + the `details` field will be null since the member has not yet set up this payment method. + If true then this method can be set up as default directly by calling `paymentMethodSetDefaultPayin` or + `paymentMethodSetDefaultPayout` mutation depending on if it's a payin or payout method. If false, then the + corresponding mutation for setting up this payment method should be called, eg. `paymentMethodSetupTrustly`, + `paymentMethodSetupSwishPayin` etc. + """ + isActive: Boolean! + """ + For already connected and ACTIVE methods, ie isActive=true, specific details of the actual connection - e.g. a bank + account reference, phone number for swish, or email/kivra for invoice. + """ + details: PaymentMethodDetails } type MemberPaymentChargeMethodInfo { """ @@ -2816,48 +2864,76 @@ type MemberPaymentInformation { } type MemberPaymentMethod { """ - The unique id of the payment method. This id is used for switching default and revoking payment methods. - """ - id: ID! - """ - Payment provider, eg Trustly, Swish, Nordea, Kivra etc. + Payment provider, eg Trustly, Swish, Nordea, Kivra etc. + This is used as the "identifier" of the payment method since there can only be one ACTIVE or PENDING payment method + per provider. """ provider: MemberPaymentProvider! """ - The payment method status - ACTIVE, PENDING, or PENDING_DEFAULT. - PENDING_DEFAULT means the payment method is awaiting activation and will become default once activated. + The payment method status - ACTIVE, PENDING. + If ACTIVE, the payment method is ready to use for payins or payouts depending on if it's a payin or payout method. + If PENDING, the payment method has been set up but is still awaiting activation and cannot be used for payins or + payouts until then. Once activated, the status will change to ACTIVE. """ status: MemberPaymentMethodStatus! """ - True if this is the default payment method. Only one ACTIVE payment method can be default at a time. - If status is PENDING then payment method will become the default once activated. + This is 'true' for only one of the members ACTIVE methods which is the default payment method that will be used for + charging or payout the member. For PENDING methods, this can also be 'true' if the member has chosen to set up this + payment method as default during the setup process. """ isDefault: Boolean! """ - Specific details of the actual connection - e.g. a bank account reference, phone number for swish, - or email/kivra for invoice. + Specific details of the actual connection if method is ACTIVE - e.g. a bank account reference, phone number for swish, + or email/kivra for invoice. If method is PENDING, then this field will be null since the member has not yet set up + this payment method. """ - details: PaymentMethodDetails! + details: PaymentMethodDetails } type MemberPaymentMethods { """ - List of active and pending payment payin methods for this member. + List of all member's ACTIVE and PENDING payment payin methods. + A member can have multiple ACTIVE payment methods with these constraints: + - Only one ACTIVE payment method per provider, eg. one Trustly, one Swish, one Nordea etc. + - Only one ACTIVE payment method can be default at a time. + A member can have multiple PENDING payment methods with these constraints: + - Only one PENDING payment method per provider, eg. one Trustly, one Swish, one Nordea etc. + So there can exist max two payment methods per provider, one ACTIVE and one PENDING. + If a PENDING payment method has isDefault=true, then it will become the default ACTIVE payment method once activated. """ payinMethods: [MemberPaymentMethod!]! """ - List of active and pending payment payout methods for this member. + List of all member's ACTIVE and PENDING payment payout methods. + A member can have multiple ACTIVE payment methods with these constraints: + - Only one ACTIVE payment method per provider, eg. one Trustly, one Swish, one Nordea etc. + - Only one ACTIVE payment method can be default at a time. + A member can have multiple PENDING payment methods with these constraints: + - Only one PENDING payment method per provider, eg. one Trustly, one Swish, one Nordea etc. + So there can exist max two payment methods per provider, one ACTIVE and one PENDING. + If a PENDING payment method has isDefault=true, then it will become the default ACTIVE payment method once activated. """ payoutMethods: [MemberPaymentMethod!]! """ - The default payment method for payin if any. + The default payment method to use for payins if any. + Note that there can exist a PENDING payment method in `payinMethods` list with `isDefault`=true, in that case this default + payment method will be replaced by it once the pending method is activated. """ defaultPayinMethod: MemberPaymentMethod """ - The default payment method for payout if any. + The default payment method to use for payouts if any. + Note that there can exist a PENDING payment method in `payoutMethods` list with `isDefault`=true, in that case this default + payment method will be replaced by it once the pending method is activated. """ defaultPayoutMethod: MemberPaymentMethod """ - The available payment methods that the member can choose from when setting up a new payment method. + The available payment methods that the member can choose from when setting up a new payment method. + This list can include both payment methods that the member has already set up and new payment methods that the + member has not yet set up but are available to them. For already set up payment methods, the `isActive` field will + be true and the `details` field will be populated with the payment method details. For new payment methods that the + member has not yet set up, the `isActive` field will be false and the `details` field will be null. + If member picks a new payment method to set up, the corresponding mutation for setting up that payment method should + be called, eg. `paymentMethodSetupTrustly`, `paymentMethodSetupSwishPayin` etc. + If member picks an already set up payment method to set up as default, then `paymentMethodSetDefaultPayin` or + `paymentMethodSetDefaultPayout` mutation should be called depending on if it's a payin or payout method. """ availableMethods: [MemberPaymentAvailablePaymentMethod!]! """ @@ -3407,9 +3483,9 @@ type Mutation { """ Setup invoice payment method for the member. Kivra will be used as the provider if supported, else mail. """ - paymentMethodSetupInvoicePayin(input: PaymentMethodSetupInvoicePayinInput!): PaymentMethodSetupOutput! + paymentMethodSetupInvoicePayin: PaymentMethodSetupOutput! """ - Setup Trustly payment payin and payout method for the member. + Setup Trustly payment payin and payout method for the member. Requires member consent via redirect to Trustly URL in response. """ paymentMethodSetupTrustly(input: PaymentMethodSetupTrustlyInput!): PaymentMethodSetupOutput! """ @@ -3421,17 +3497,19 @@ type Mutation { """ paymentMethodSetupSwishPayout(input: PaymentMethodSetupSwishInput!): PaymentMethodSetupOutput! """ - Setup Swish payin method for the member. + Setup Swish payin method for the member. Requires member consent in Swish app. """ paymentMethodSetupSwishPayin(input: PaymentMethodSetupSwishInput!): PaymentMethodSetupOutput! """ - Revoke an active payment method. The member will be required to set up a new payment method if they revoke their default one. + A member can have multiple ACTIVE payment methods where one of those is default. This mutation changes the + members default payment method for charging to any of his/hers other active payment methods. """ - paymentMethodRevoke(id: ID!): UserError + paymentMethodSetDefaultPayin(provider: MemberPaymentProvider!): UserError """ - Set an active payment method as default. + A member can have multiple ACTIVE payment methods where one of those is default. This mutation changes the + members default payment method for payouts to any of his/hers other active payment methods. """ - paymentMethodSetDefault(id: ID!): UserError + paymentMethodSetDefaultPayout(provider: MemberPaymentProvider!): UserError """ Start a conversation. This is effectively creating one, but with two slight differences from a regular "create something"-mutation: @@ -3560,7 +3638,7 @@ type Mutation { Update the raw insurance-related data for this `PriceIntent`. This data is mostly related to the insured object itself, and not the "holder" of the insurance. """ - priceIntentDataUpdate(priceIntentId: UUID!, data: PricingFormData!): PriceIntentMutationOutput! + priceIntentDataUpdate(priceIntentId: UUID!, data: PricingFormData!, applySuggestedData: Boolean): PriceIntentMutationOutput! """ Associate a specific Insurely `dataCollectionId` from lookup-service with this PriceIntent. """ @@ -3591,6 +3669,10 @@ type Mutation { """ productOfferReprice(offerId: UUID!, data: PricingFormData!): ProductOffersMutationOutput! """ + Mark a young pet guide story as read for a specific member. + """ + puppyGuideEngagement(engagement: PuppyEngagementInput!): PuppyGuideStoryMutationOutput! + """ Update the customer of the shop session. Only non-null fields will be changed. Can trigger automatic lookup of other information. The session can be placed in a "point of no return" state where it is no longer legal to update the customer, @@ -3691,6 +3773,22 @@ type Mutation { """ upsellTravelAddonActivate(quoteId: ID!, addonId: ID!): UpsellTravelAddonActivationOutput! } +type PartnerClaim { + id: ID! + externalId: String! + exposureDisplayName: String + status: ClaimStatus + submittedAt: Date + payoutAmount: Money + associatedTypeOfContract: String + claimType: String + handlerEmail: String + displayItems: [ClaimDisplayItem!]! + """ + Terms & conditions for the claim found using claims contractId and dateOfOccurrence, otherwise null. + """ + productVariant: ProductVariant +} type PartnerData { sas: SasPartnerData } @@ -3722,17 +3820,7 @@ type PaymentMethodInvoiceDetails { """ email: String } -input PaymentMethodSetupInvoicePayinInput { - """ - Set up invoice payment method as default. - """ - setAsDefaultPayout: Boolean! -} input PaymentMethodSetupNordeaPayoutInput { - """ - Set up Nordea payout method as default. - """ - setAsDefault: Boolean! """ The clearing number for member's bank account. """ @@ -3747,6 +3835,10 @@ type PaymentMethodSetupOutput { The status of the setup process. If FAILED the reason for failure can be found in the `error` field. """ status: PaymentMethodSetupStatus! + """ + The order id for the payment method setup order if SUCCESSFUL. + """ + orderId: ID """ Url to redirect the member to if any. """ @@ -3771,24 +3863,12 @@ enum PaymentMethodSetupStatus { FAILED } input PaymentMethodSetupSwishInput { - """ - Set up Swish payment method as default. - """ - setAsDefault: Boolean! """ The Swish mobile number to use for payout or payin. """ phoneNumber: String! } input PaymentMethodSetupTrustlyInput { - """ - Set up Trustly payment method as default for payin. - """ - setAsDefaultPayin: Boolean! - """ - Set up Trustly payment method as default for payout. - """ - setAsDefaultPayout: Boolean! """ The URL to redirect the member back to after a successful setup after Trustly onboarding. """ @@ -3888,11 +3968,11 @@ type PriceIntent { """ product: Product! """ - Submitted user form data. + UI-safe masked form data. PII fields (street, zipCode, city) are always masked. """ data: PricingFormData! """ - Data submitted in other places or inferred from other data points + Masked, uncommitted form data from automatic lookup (e.g. SPAR address, trial data). PII fields are masked the same way as 'data'. """ suggestedData: PricingFormData! """ @@ -3920,6 +4000,15 @@ type PriceIntent { When 'true' it means user has gone trough Insurely flow with that price intent """ hasCollectedInsurelyData: Boolean! + """ + List of external insurances fetched via Insurely that correspond to products Hedvig offers. + Null when no Insurely data collection has been associated with this price intent. + """ + fetchedExternalInsurances: [FetchedExternalInsurance!] + """ + When 'true' all required form data has been provided and the price intent can be confirmed. + """ + isReadyToConfirm: Boolean! } enum PriceIntentAnimal { CAT @@ -4080,7 +4169,7 @@ type ProductOffer { """ priceIntentId: UUID """ - The form data used to generate the offer + UI-safe masked form data used to generate the offer. PII fields (street, zipCode, city) are masked when address came from registration address lookup. """ priceIntentData: PricingFormData! """ @@ -4314,6 +4403,53 @@ type ProductVariantComparisonRow { """ covered: [String!]! } +input PuppyEngagementInput { + name: String! + rating: Int + opened: Boolean + read: Boolean + closed: Boolean +} +type PuppyGuideStory { + """ + The unique name/identifier of the story. + """ + name: String! + """ + The display title of the story. + """ + title: String! + """ + The subtitle or description of the story. + """ + subtitle: String! + """ + The main content of the story. + """ + content: String! + """ + The image associated with this story. + """ + image: String! + """ + Categories this story belongs to. + """ + categories: [String!]! + """ + The date when the story was marked as read by the user. + """ + read: Boolean! + """ + The user's rating of the story. + """ + rating: Int +} +type PuppyGuideStoryMutationOutput { + """ + Indicates whether the mutation was successful. + """ + success: Boolean! +} type Query { """ Return a conversation for a given ID. @@ -4630,6 +4766,12 @@ type ShopSessionOutcome { Note that this will not contain and `PendingContract`s. """ createdContracts: [Contract!]! + """ + Pre-hashed Facebook CAPI user data (SHA-256 hex, lowercase) for this signed session. + Fields follow Meta's Conversions API customer information parameter spec. + Requires authentication (inherits MemberAccessible from the outcome). + """ + capiUserData: CapiUserData! } type ShopSessionSigning { id: UUID! From 221c87cb05140d5d585fe0add6c15c376b2913cb Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 12:55:38 +0200 Subject: [PATCH 15/30] chore: run ktlint formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../hedvig/android/app/navigation/HedvigNavHost.kt | 14 +++++++------- .../claimhistory/ClaimHistoryDestination.kt | 6 +++++- .../details/navigation/PartnerClaimDetailGraph.kt | 5 +---- .../details/ui/PartnerClaimDetailsDestination.kt | 5 +---- .../details/ui/PartnerClaimDetailsViewModel.kt | 2 ++ .../android/ui/claimstatus/model/ClaimPillType.kt | 1 + 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 24dc791c02..975843927b 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -32,8 +32,6 @@ import com.hedvig.android.feature.chip.id.navigation.ChipIdGraphDestination import com.hedvig.android.feature.chip.id.navigation.chipIdGraph import com.hedvig.android.feature.claim.details.navigation.ClaimDetailDestination import com.hedvig.android.feature.claim.details.navigation.claimDetailsGraph -import com.hedvig.android.feature.partner.claim.details.navigation.PartnerClaimOverviewDestination -import com.hedvig.android.feature.partner.claim.details.navigation.partnerClaimDetailsGraph import com.hedvig.android.feature.claimhistory.nav.ClaimHistoryDestination import com.hedvig.android.feature.claimhistory.nav.claimHistoryGraph import com.hedvig.android.feature.connect.payment.connectPaymentGraph @@ -69,6 +67,8 @@ import com.hedvig.android.feature.insurances.navigation.insuranceGraph import com.hedvig.android.feature.login.navigation.loginGraph import com.hedvig.android.feature.movingflow.SelectContractForMoving import com.hedvig.android.feature.movingflow.movingFlowGraph +import com.hedvig.android.feature.partner.claim.details.navigation.PartnerClaimOverviewDestination +import com.hedvig.android.feature.partner.claim.details.navigation.partnerClaimDetailsGraph import com.hedvig.android.feature.payments.navigation.paymentsGraph import com.hedvig.android.feature.profile.navigation.ProfileDestination import com.hedvig.android.feature.profile.tab.profileGraph @@ -391,7 +391,7 @@ internal fun HedvigNavHost( navigateToChipId = { navController.navigate(ChipIdGraphDestination()) }, - languageService = languageService + languageService = languageService, ) cbmChatGraph( hedvigDeepLinkContainer = hedvigDeepLinkContainer, @@ -430,10 +430,10 @@ internal fun HedvigNavHost( hedvigDeepLinkContainer = hedvigDeepLinkContainer, popBackStackOrFinish = popBackStackOrFinish, goHome = { - navController.navigate(HomeDestination.Graph) { - popUpTo(ChipIdGraphDestination::class) { inclusive = true } - } - } + navController.navigate(HomeDestination.Graph) { + popUpTo(ChipIdGraphDestination::class) { inclusive = true } + } + }, ) movingFlowGraph( navController = navController, diff --git a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt index 998fc8582b..c302bb2d6e 100644 --- a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt +++ b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt @@ -111,7 +111,11 @@ private fun ClaimHistoryScreen( .fillMaxWidth(), ) - is ClaimHistoryUiState.Content -> ClaimHistoryContent(uiState, navigateToClaimDetails, navigateToPartnerClaimDetails) + is ClaimHistoryUiState.Content -> ClaimHistoryContent( + uiState, + navigateToClaimDetails, + navigateToPartnerClaimDetails, + ) } } } diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt index 68514acb54..08fb5b7391 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt @@ -7,10 +7,7 @@ import com.hedvig.android.navigation.compose.navdestination import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf -fun NavGraphBuilder.partnerClaimDetailsGraph( - navigateUp: () -> Unit, - openUrl: (String) -> Unit, -) { +fun NavGraphBuilder.partnerClaimDetailsGraph(navigateUp: () -> Unit, openUrl: (String) -> Unit) { navdestination { val viewModel: PartnerClaimDetailsViewModel = koinViewModel { parametersOf(claimId) } PartnerClaimDetailsDestination( diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt index 79a7c1cd9a..a265c6beaa 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt @@ -111,10 +111,7 @@ private fun PartnerClaimDetailScreen( } @Composable -private fun PartnerClaimDetailContent( - uiState: PartnerClaimDetailUiState.Content, - openUrl: (String) -> Unit, -) { +private fun PartnerClaimDetailContent(uiState: PartnerClaimDetailUiState.Content, openUrl: (String) -> Unit) { val explanationBottomSheetState = rememberHedvigBottomSheetState() ClaimExplanationBottomSheet(explanationBottomSheetState) Column( diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt index aed91b9eea..f6e041a8b7 100644 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt +++ b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt @@ -58,7 +58,9 @@ internal sealed interface PartnerClaimDetailEvent { internal sealed interface PartnerClaimDetailUiState { data object Loading : PartnerClaimDetailUiState + data object Error : PartnerClaimDetailUiState + data class Content( val claimStatusCardUiState: ClaimStatusCardUiState, val claimStatus: ClaimStatus?, diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt index 522e862ed3..4bea816734 100644 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimPillType.kt @@ -28,6 +28,7 @@ sealed interface ClaimPillType { fun fromPartnerClaim(status: ClaimStatus?): List { return when (status) { ClaimStatus.CLOSED -> listOf(Closed.GenericClosed) + ClaimStatus.CREATED, ClaimStatus.IN_PROGRESS, ClaimStatus.REOPENED, From 94dd5cbb0853e24f5735f59eb55837eeb6737ec1 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Thu, 23 Apr 2026 14:09:38 +0200 Subject: [PATCH 16/30] fix: handle empty claims list in home status cards and add lint baseline The claims/claimsActive fields are mutually exclusive via @skip/@include directives, but `?:` treats an empty list as non-null, picking it over the populated field. Use `.orEmpty()` concatenation instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../home/home/data/GetHomeDataUseCase.kt | 5 +++-- ...-baseline-feature-partner-claim-details.xml | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt index 2478bd3d04..b6a910de89 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt @@ -267,8 +267,9 @@ internal class GetHomeDataUseCaseImpl( } private fun HomeQuery.Data.claimStatusCards(): HomeData.ClaimStatusCardsData? { - val regularCards = (this.currentMember.claims ?: this.currentMember.claimsActive).orEmpty() - .map(ClaimStatusCardUiState::fromClaimStatusCardsQuery) + val regularCards = + this.currentMember.claims.orEmpty().map(ClaimStatusCardUiState::fromClaimStatusCardsQuery) + + this.currentMember.claimsActive.orEmpty().map(ClaimStatusCardUiState::fromClaimStatusCardsQuery) val partnerClaims = this.currentMember.partnerClaimsActive val partnerCards = partnerClaims.map(ClaimStatusCardUiState::fromPartnerClaim) diff --git a/hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml b/hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml new file mode 100644 index 0000000000..bdfed02b17 --- /dev/null +++ b/hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + From a69ea1e08915d6d0c515c40916a4a2d6dbe07112 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:39:50 +0200 Subject: [PATCH 17/30] docs: add design spec for unifying partner claim into regular claim detail Co-Authored-By: Claude Opus 4.6 (1M context) --- ...04-24-unify-partner-claim-detail-design.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md diff --git a/docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md b/docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md new file mode 100644 index 0000000000..e76a238a25 --- /dev/null +++ b/docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md @@ -0,0 +1,100 @@ +# Unify Partner Claim Detail Into Regular Claim Detail Destination + +## Problem + +PR #2930 introduces a separate `feature-partner-claim-details` module with its own destination, ViewModel, presenter, use case, and navigation wiring for partner claims. This creates dual navigation paths (home screen and claim history both need `navigateToPartnerClaimDetails` alongside `navigateToClaimDetails`), a separate module to maintain, and duplicated UI that is a subset of the regular claim detail screen. + +## Goal + +Remove `feature-partner-claim-details` and reuse the existing `feature-claim-details` destination for both regular and partner claims. The regular claim detail screen already conditionally renders most sections (chat, files, upload, audio), so partner claims can be represented as `ClaimDetailUiState.Content` with null/empty/disabled values for unsupported features. + +## Approach + +Extend the existing claim detail flow to handle partner claims by: +1. Adding an `isPartnerClaim` flag to the navigation destination +2. Branching in the use case to call the appropriate GraphQL query +3. Mapping partner claim data into the existing UI state model +4. Removing the partner claim module entirely + +## Design + +### 1. Navigation Destination + +Add `isPartnerClaim: Boolean = false` to `ClaimOverviewDestination`: + +```kotlin +data class ClaimOverviewDestination( + val claimId: String, + val isPartnerClaim: Boolean = false, +) : ClaimDetailDestination, Destination +``` + +Default `false` preserves backward compatibility with deep links and existing callers. + +### 2. Use Case Changes + +`GetClaimDetailUiStateUseCase` receives `isPartnerClaim` alongside `claimId` and branches: + +- **Regular claim:** Calls `ClaimQuery(claimId)` as today, maps via `fromClaim()` +- **Partner claim:** Calls `PartnerClaimDetailQuery(claimId)`, maps into `ClaimDetailUiState.Content` with: + - `conversationId = null` + - `hasUnreadMessages = false` + - `submittedContent = null` + - `files = emptyList()` + - `claimOutcome = UNKNOWN` + - `uploadUri = ""`, `isUploadingFile = false`, `isUploadingFilesEnabled = false` + - `appealInstructionsUrl = null`, `infoText = null` + - `claimStatus`, `displayItems`, `termsConditionsUrl`, `claimStatusCardUiState` mapped from `PartnerClaimFragment` + +### 3. UI Changes + +Minimal. The existing screen already guards most sections: + +- Chat link: `if (navigateToConversation != null)` -- null conversationId means no chat +- Files grid: `if (uiState.files.isNotEmpty())` -- empty list means no grid +- File upload: `if (uiState.isUploadingFilesEnabled)` -- false means no upload +- Submitted content: `when (uiState.submittedContent)` with `else -> {}` -- null means nothing +- Appeal instructions: `if (uiState.appealInstructionsUrl != null)` +- Info notification: `if (uiState.infoText != null)` +- T&C card: `if (uiState.termsConditionsUrl != null)` + +One fix needed: guard the "uploaded files" section header with `if (uiState.submittedContent != null || uiState.files.isNotEmpty())` to avoid showing an empty header for partner claims. + +### 4. Navigation Cleanup + +- Remove `navigateToPartnerClaimDetails` from `homeGraph()`, `HomeDestination`, `HomeScreen`, `HomeScreenSuccess`, `claimHistoryGraph()`, `ClaimHistoryDestination`, `HedvigNavHost` +- Callers pass `isPartnerClaim = true` to the existing `navigateToClaimDetails` callback instead +- Keep `partnerClaimIds` in `ClaimStatusCardsData` and `isPartnerClaim` in `ClaimHistory` -- still needed to pass the flag + +### 5. Module Deletion + +Delete entirely: +- `app/feature/feature-partner-claim-details/` directory +- `hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml` + +Remove references: +- `partnerClaimDetailsModule` from `ApplicationModule.kt` +- `projects.featurePartnerClaimDetails` from `app/build.gradle.kts` +- `partnerClaimDetailsGraph` from `HedvigNavHost.kt` + +## Files to Modify + +| Area | File | Change | +|------|------|--------| +| Nav destination | `ClaimDetailDestinations.kt` | Add `isPartnerClaim` param | +| Nav graph | `ClaimDetailDestinationGraph.kt` | Pass `isPartnerClaim` to ViewModel | +| DI | `FeatureClaimDetailsModule.kt` | Pass `isPartnerClaim` through | +| ViewModel | `ClaimDetailsViewModel.kt` | Accept + forward `isPartnerClaim` | +| Use case | `GetClaimDetailUiStateUseCase.kt` | Branch on `isPartnerClaim`, add partner claim mapping | +| UI | `ClaimDetailsDestination.kt` | Guard "uploaded files" header | +| Home nav | `HomeGraph.kt`, `HomeDestination.kt` | Remove partner callback, pass flag | +| Claim history | `ClaimHistoryDestination.kt` (nav + ui) | Remove partner callback, pass flag | +| App nav host | `HedvigNavHost.kt` | Remove partner graph, update callbacks | +| App module | `ApplicationModule.kt`, `build.gradle.kts` | Remove partner module refs | +| Delete | `feature-partner-claim-details/`, lint baseline | Full removal | + +## Out of Scope + +- No changes to GraphQL queries/fragments +- No changes to `ui-claim-status` shared components +- No changes to `GetHomeDataUseCase` or `GetClaimsHistoryUseCase` From 4cbe5797ebcc21544b19560985c4fad906c92008 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:44:42 +0200 Subject: [PATCH 18/30] docs: add implementation plan for unifying partner claim detail Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-24-unify-partner-claim-detail.md | 753 ++++++++++++++++++ 1 file changed, 753 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md diff --git a/docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md b/docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md new file mode 100644 index 0000000000..8e7ed56c7f --- /dev/null +++ b/docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md @@ -0,0 +1,753 @@ +# Unify Partner Claim Detail Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove the `feature-partner-claim-details` module and reuse the existing `feature-claim-details` destination for both regular and partner claims. + +**Architecture:** Add `isPartnerClaim` flag to navigation destination. Branch in use case to call either `ClaimQuery` or `PartnerClaimDetailQuery`, mapping both into `ClaimDetailUiState.Content`. Remove all partner-specific navigation wiring and delete the partner module. + +**Tech Stack:** Kotlin, Jetpack Compose Navigation, Apollo GraphQL, Koin DI, Molecule + +--- + +### Task 1: Add `isPartnerClaim` to navigation destination and wire through ViewModel/DI + +**Files:** +- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinations.kt` +- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinationGraph.kt` +- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt` +- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt` + +- [ ] **Step 1: Add `isPartnerClaim` to `ClaimOverviewDestination`** + +In `ClaimDetailDestinations.kt`, add the parameter: + +```kotlin +sealed interface ClaimDetailDestination { + @Serializable + data class ClaimOverviewDestination( + @SerialName("claimId") + val claimId: String, + val isPartnerClaim: Boolean = false, + ) : ClaimDetailDestination, Destination +} +``` + +- [ ] **Step 2: Pass `isPartnerClaim` from nav destination to ViewModel** + +In `ClaimDetailDestinationGraph.kt`, update the ViewModel instantiation at line 34 to pass `isPartnerClaim`: + +```kotlin +navdestination( + deepLinks = navDeepLinks(hedvigDeepLinkContainer.claimDetails), +) { + val viewModel: ClaimDetailsViewModel = koinViewModel { parametersOf(claimId, isPartnerClaim) } +``` + +- [ ] **Step 3: Update ViewModel and Presenter to accept `isPartnerClaim`** + +In `ClaimDetailsViewModel.kt`, update the ViewModel class to accept the new parameter and forward it to the presenter: + +```kotlin +internal class ClaimDetailsViewModel( + claimId: String, + isPartnerClaim: Boolean, + getClaimDetailUiStateUseCase: GetClaimDetailUiStateUseCase, + claimsServiceUploadFileUseCase: ClaimsServiceUploadFileUseCase, + downloadPdfUseCase: DownloadPdfUseCase, +) : MoleculeViewModel( + ClaimDetailUiState.Loading, + ClaimDetailPresenter(claimId, isPartnerClaim, getClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase, downloadPdfUseCase), + ) +``` + +Update `ClaimDetailPresenter`: + +```kotlin +private class ClaimDetailPresenter( + private val claimId: String, + private val isPartnerClaim: Boolean, + private val getClaimDetailUiStateUseCase: GetClaimDetailUiStateUseCase, + private val claimsServiceUploadFileUseCase: ClaimsServiceUploadFileUseCase, + private val downloadPdfUseCase: DownloadPdfUseCase, +) : MoleculePresenter { +``` + +In the `present()` function, pass `isPartnerClaim` to the use case invocation at line 63: + +```kotlin +getClaimDetailUiStateUseCase.invoke(claimId, isPartnerClaim).collect { result -> +``` + +- [ ] **Step 4: Update Koin module to pass `isPartnerClaim`** + +In `FeatureClaimDetailsModule.kt`, update the ViewModel factory: + +```kotlin +viewModel { (claimId: String, isPartnerClaim: Boolean) -> + ClaimDetailsViewModel( + claimId = claimId, + isPartnerClaim = isPartnerClaim, + getClaimDetailUiStateUseCase = get(), + claimsServiceUploadFileUseCase = get(), + downloadPdfUseCase = get(), + ) +} +``` + +- [ ] **Step 5: Commit** + +```bash +git add app/feature/feature-claim-details/ +git commit -m "feat: add isPartnerClaim flag to claim detail destination and wire through ViewModel" +``` + +--- + +### Task 2: Add partner claim query support to `GetClaimDetailUiStateUseCase` + +**Files:** +- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt` + +- [ ] **Step 1: Add `isPartnerClaim` parameter to `invoke()` and add partner claim mapping** + +Update `GetClaimDetailUiStateUseCase` to accept `isPartnerClaim` and branch on it. Add a new `partnerQueryFlow` method and a `fromPartnerClaim` mapping function. + +The full updated file: + +```kotlin +package com.hedvig.android.feature.claim.details.data + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensureNotNull +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.cache.normalized.FetchPolicy +import com.apollographql.apollo.cache.normalized.fetchPolicy +import com.hedvig.android.apollo.safeFlow +import com.hedvig.android.core.uidata.UiFile +import com.hedvig.android.data.cross.sell.after.claim.closed.CrossSellAfterClaimClosedRepository +import com.hedvig.android.data.display.items.DisplayItem +import com.hedvig.android.feature.claim.details.ui.ClaimDetailUiState +import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState +import com.hedvig.audio.player.data.SignedAudioUrl +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn +import kotlinx.datetime.toLocalDateTime +import octopus.ClaimQuery +import octopus.PartnerClaimDetailQuery +import octopus.fragment.ClaimFragment +import octopus.fragment.PartnerClaimFragment +import octopus.type.ClaimOutcome +import octopus.type.ClaimStatus +import octopus.type.InsuranceDocumentType + +internal class GetClaimDetailUiStateUseCase( + private val apolloClient: ApolloClient, + private val crossSellAfterClaimClosedRepository: CrossSellAfterClaimClosedRepository, +) { + fun invoke(claimId: String, isPartnerClaim: Boolean = false): Flow> { + return flow { + while (currentCoroutineContext().isActive) { + if (isPartnerClaim) { + emitAll(partnerQueryFlow(claimId)) + } else { + emitAll(queryFlow(claimId)) + } + delay(POLL_INTERVAL) + } + } + } + + private fun queryFlow(claimId: String): Flow> { + return apolloClient + .query(ClaimQuery(claimId)) + .fetchPolicy(FetchPolicy.CacheAndNetwork) + .safeFlow { Error.NetworkError } + .map { response -> + either { + val claim = response.bind().claim + ensureNotNull(claim) { Error.NoClaimFound } + if (claim.showClaimClosedFlow) { + crossSellAfterClaimClosedRepository.acknowledgeClaimClosedStatus(claim) + } + ClaimDetailUiState.Content.fromClaim(claim, claim.conversation?.id, claim.conversation?.unreadMessageCount) + } + } + } + + private fun partnerQueryFlow(claimId: String): Flow> { + return apolloClient + .query(PartnerClaimDetailQuery(claimId)) + .fetchPolicy(FetchPolicy.NetworkOnly) + .safeFlow { Error.NetworkError } + .map { response -> + either { + val claim = response.bind().partnerClaim + ensureNotNull(claim) { Error.NoClaimFound } + fromPartnerClaim(claim) + } + } + } + + private fun fromPartnerClaim(claim: PartnerClaimFragment): ClaimDetailUiState.Content { + val termsConditionsUrl = claim.productVariant?.documents + ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS }?.url + val submittedAt = claim.submittedAt + ?.atStartOfDayIn(TimeZone.UTC) + ?.toLocalDateTime(TimeZone.UTC) + ?: kotlinx.datetime.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + return ClaimDetailUiState.Content( + claimId = claim.id, + conversationId = null, + hasUnreadMessages = false, + submittedContent = null, + files = emptyList(), + claimStatusCardUiState = ClaimStatusCardUiState.fromPartnerClaim(claim), + claimStatus = when (claim.status) { + ClaimStatus.CREATED -> ClaimDetailUiState.Content.ClaimStatus.CREATED + ClaimStatus.IN_PROGRESS -> ClaimDetailUiState.Content.ClaimStatus.IN_PROGRESS + ClaimStatus.CLOSED -> ClaimDetailUiState.Content.ClaimStatus.CLOSED + ClaimStatus.REOPENED -> ClaimDetailUiState.Content.ClaimStatus.REOPENED + ClaimStatus.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimStatus.UNKNOWN + }, + claimOutcome = ClaimDetailUiState.Content.ClaimOutcome.UNKNOWN, + uploadUri = "", + isUploadingFile = false, + uploadError = null, + claimType = claim.claimType, + insuranceDisplayName = claim.exposureDisplayName ?: claim.productVariant?.displayName, + submittedAt = submittedAt, + termsConditionsUrl = termsConditionsUrl, + savedFileUri = null, + downloadError = null, + isLoadingPdf = null, + appealInstructionsUrl = null, + isUploadingFilesEnabled = false, + infoText = null, + displayItems = claim.displayItems.map { + DisplayItem.fromStrings(it.displayTitle, it.displayValue) + }, + ) + } + + private fun ClaimDetailUiState.Content.Companion.fromClaim( + claim: ClaimFragment, + conversationId: String?, + conversationUnreadMessageCount: Int?, + ): ClaimDetailUiState.Content { + val audioUrl = claim.audioUrl + val memberFreeText = claim.memberFreeText + + val claimType: String? = claim.claimType + val submittedAt = claim.submittedAt.toLocalDateTime(TimeZone.currentSystemDefault()) + val insuranceDisplayName = claim.productVariant?.displayName + val termsConditionsUrl = + claim.productVariant + ?.documents + ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS } + ?.url + + return ClaimDetailUiState.Content( + claimId = claim.id, + conversationId = conversationId, + hasUnreadMessages = (conversationUnreadMessageCount ?: 0) > 0, + submittedContent = when { + audioUrl != null -> { + ClaimDetailUiState.Content.SubmittedContent.Audio(SignedAudioUrl.fromSignedAudioUrlString(audioUrl)) + } + + memberFreeText != null -> { + ClaimDetailUiState.Content.SubmittedContent.FreeText(memberFreeText) + } + + else -> { + null + } + }, + files = claim.files.map { + UiFile( + id = it.id, + name = it.name, + mimeType = it.mimeType, + url = it.url, + localPath = null, + ) + }, + claimStatusCardUiState = ClaimStatusCardUiState.fromClaimStatusCardsQuery(claim), + claimStatus = when (claim.status) { + ClaimStatus.CREATED -> ClaimDetailUiState.Content.ClaimStatus.CREATED + ClaimStatus.IN_PROGRESS -> ClaimDetailUiState.Content.ClaimStatus.IN_PROGRESS + ClaimStatus.CLOSED -> ClaimDetailUiState.Content.ClaimStatus.CLOSED + ClaimStatus.REOPENED -> ClaimDetailUiState.Content.ClaimStatus.REOPENED + ClaimStatus.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimStatus.UNKNOWN + }, + claimOutcome = when (claim.outcome) { + ClaimOutcome.PAID -> ClaimDetailUiState.Content.ClaimOutcome.PAID + ClaimOutcome.NOT_COMPENSATED -> ClaimDetailUiState.Content.ClaimOutcome.NOT_COMPENSATED + ClaimOutcome.NOT_COVERED -> ClaimDetailUiState.Content.ClaimOutcome.NOT_COVERED + ClaimOutcome.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimOutcome.UNKNOWN + ClaimOutcome.UNRESPONSIVE -> ClaimDetailUiState.Content.ClaimOutcome.UNRESPONSIVE + }, + uploadUri = claim.targetFileUploadUri, + isUploadingFile = false, + uploadError = null, + claimType = claimType, + insuranceDisplayName = insuranceDisplayName, + submittedAt = submittedAt, + termsConditionsUrl = termsConditionsUrl, + savedFileUri = null, + downloadError = null, + isLoadingPdf = null, + appealInstructionsUrl = claim.appealInstructionsUrl, + isUploadingFilesEnabled = claim.isUploadingFilesEnabled, + infoText = claim.infoText, + displayItems = claim.displayItems.map { + DisplayItem.fromStrings(it.displayTitle, it.displayValue) + }, + ) + } + + companion object { + private val POLL_INTERVAL = 10.seconds + } +} + +sealed interface Error { + data object NetworkError : Error + data object NoClaimFound : Error +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add app/feature/feature-claim-details/ +git commit -m "feat: add partner claim query support to GetClaimDetailUiStateUseCase" +``` + +--- + +### Task 3: Guard the "uploaded files" section header in the UI + +**Files:** +- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt` + +- [ ] **Step 1: Wrap the "uploaded files" header and submitted content in a conditional** + +In `ClaimDetailsDestination.kt`, in the `BeforeGridContent` composable, the "uploaded files" section header at lines 475-496 is always shown. Wrap lines 474-496 in a condition: + +Find this block (starting after the `ClaimDisplayItemsSection`): + +```kotlin + Spacer(Modifier.height(24.dp)) + HedvigText( + stringResource(Res.string.claim_status_detail_uploaded_files_info_title), + Modifier.padding(horizontal = 2.dp), + ) + Spacer(Modifier.height(8.dp)) + when (uiState.submittedContent) { + is ClaimDetailUiState.Content.SubmittedContent.Audio -> { + ClaimDetailHedvigAudioPlayerItem(uiState.submittedContent.signedAudioURL) + } + + is ClaimDetailUiState.Content.SubmittedContent.FreeText -> { + HedvigCard(Modifier.fillMaxWidth()) { + HedvigText( + uiState.submittedContent.text, + Modifier.padding(16.dp), + ) + } + } + + else -> {} + } + Spacer(Modifier.height(8.dp)) +``` + +Replace with: + +```kotlin + if (uiState.submittedContent != null || uiState.files.isNotEmpty()) { + Spacer(Modifier.height(24.dp)) + HedvigText( + stringResource(Res.string.claim_status_detail_uploaded_files_info_title), + Modifier.padding(horizontal = 2.dp), + ) + Spacer(Modifier.height(8.dp)) + when (uiState.submittedContent) { + is ClaimDetailUiState.Content.SubmittedContent.Audio -> { + ClaimDetailHedvigAudioPlayerItem(uiState.submittedContent.signedAudioURL) + } + + is ClaimDetailUiState.Content.SubmittedContent.FreeText -> { + HedvigCard(Modifier.fillMaxWidth()) { + HedvigText( + uiState.submittedContent.text, + Modifier.padding(16.dp), + ) + } + } + + else -> {} + } + Spacer(Modifier.height(8.dp)) + } +``` + +- [ ] **Step 2: Commit** + +```bash +git add app/feature/feature-claim-details/ +git commit -m "fix: hide uploaded files header when no submitted content or files" +``` + +--- + +### Task 4: Update navigation callers to pass `isPartnerClaim` flag + +**Files:** +- Modify: `app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt` +- Modify: `app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt` +- Modify: `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt` +- Modify: `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt` +- Modify: `app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt` + +- [ ] **Step 1: Update `HomeGraph.kt` — remove `navigateToPartnerClaimDetails`, change `navigateToClaimDetails` signature** + +Replace the function signature to change `navigateToClaimDetails` to accept both `claimId` and `isPartnerClaim`, and remove `navigateToPartnerClaimDetails`: + +```kotlin +fun NavGraphBuilder.homeGraph( + nestedGraphs: NavGraphBuilder.() -> Unit, + hedvigDeepLinkContainer: HedvigDeepLinkContainer, + navController: NavController, + onNavigateToInbox: () -> Unit, + onNavigateToNewConversation: () -> Unit, + navigateToClaimDetails: (claimId: String, isPartnerClaim: Boolean) -> Unit, + navigateToConnectPayment: () -> Unit, + navigateToContactInfo: () -> Unit, + navigateToMissingInfo: (String, CoInsuredFlowType) -> Unit, + navigateToHelpCenter: () -> Unit, + navigateToClaimChat: () -> Unit, + navigateToClaimChatInDevMode: () -> Unit, + navigateToChipIdScreen: () -> Unit, + openAppSettings: () -> Unit, + openUrl: (String) -> Unit, + openCrossSellUrl: (String) -> Unit, + imageLoader: ImageLoader, +) { +``` + +Inside the function body, update the `HomeDestination` call. Replace lines 54-59: + +```kotlin + onClaimDetailCardClicked = dropUnlessResumed { claimId: String -> + navigateToClaimDetails(claimId) + }, + onPartnerClaimDetailCardClicked = dropUnlessResumed { claimId: String -> + navigateToPartnerClaimDetails(claimId) + }, +``` + +With: + +```kotlin + onClaimDetailCardClicked = dropUnlessResumed { claimId: String, isPartnerClaim: Boolean -> + navigateToClaimDetails(claimId, isPartnerClaim) + }, +``` + +- [ ] **Step 2: Update `HomeDestination.kt` — merge two callbacks into one** + +In `HomeDestination.kt`, throughout all the composable function signatures, replace: + +```kotlin +onClaimDetailCardClicked: (String) -> Unit, +onPartnerClaimDetailCardClicked: (String) -> Unit, +``` + +With: + +```kotlin +onClaimDetailCardClicked: (claimId: String, isPartnerClaim: Boolean) -> Unit, +``` + +This appears in `HomeDestination` (line 159-160), `HomeScreen` (line 211-212), and `HomeScreenSuccess` (line 428-429). + +In `HomeScreenSuccess`, update the `ClaimStatusCards` click handler (lines 482-488): + +```kotlin +ClaimStatusCards( + onClick = { claimId -> + onClaimDetailCardClicked( + claimId, + claimId in (uiState.claimStatusCardsData.partnerClaimIds), + ) + }, +``` + +Update all preview composables to use the new signature — replace each `onClaimDetailCardClicked = {},` + `onPartnerClaimDetailCardClicked = {},` pair with `onClaimDetailCardClicked = { _, _ -> },`. + +- [ ] **Step 3: Update claim history nav `ClaimHistoryDestination.kt` (nav file)** + +In `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt`, update: + +```kotlin +fun NavGraphBuilder.claimHistoryGraph( + navigateUp: () -> Unit, + navigateToClaimDetails: (claimId: String, isPartnerClaim: Boolean) -> Unit, +) { + navdestination { + ClaimHistoryDestination( + claimHistoryViewModel = koinViewModel(), + navigateUp = navigateUp, + navigateToClaimDetails = navigateToClaimDetails, + ) + } +} +``` + +- [ ] **Step 4: Update claim history UI `ClaimHistoryDestination.kt`** + +In `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt`, remove `navigateToPartnerClaimDetails` from all function signatures: + +`ClaimHistoryDestination`: +```kotlin +@Composable +internal fun ClaimHistoryDestination( + claimHistoryViewModel: ClaimHistoryViewModel, + navigateUp: () -> Unit, + navigateToClaimDetails: (String, Boolean) -> Unit, +) +``` + +`ClaimHistoryScreen`: +```kotlin +@Composable +private fun ClaimHistoryScreen( + uiState: ClaimHistoryUiState, + navigateUp: () -> Unit, + navigateToClaimDetails: (String, Boolean) -> Unit, + reload: () -> Unit, +) +``` + +`ClaimHistoryContent` — remove `navigateToPartnerClaimDetails` parameter: +```kotlin +@Composable +private fun ColumnScope.ClaimHistoryContent( + uiState: ClaimHistoryUiState.Content, + navigateToClaimDetails: (String, Boolean) -> Unit, +) +``` + +`ClaimHistoryItem` — remove `navigateToPartnerClaimDetails` and update click handler: +```kotlin +@Composable +private fun ClaimHistoryItem( + index: Int, + claim: ClaimHistory, + navigateToClaimDetails: (String, Boolean) -> Unit, +) +``` + +Update the click handler in `ClaimHistoryItem` (lines 193-201): + +```kotlin + modifier = Modifier + .fillMaxWidth() + .clickable( + onClick = dropUnlessResumed { + navigateToClaimDetails(claim.id, claim.isPartnerClaim) + }, + ) +``` + +Update the `forEachIndexed` in `ClaimHistoryContent` to pass only `navigateToClaimDetails`: +```kotlin +uiState.claims.forEachIndexed { index, claim -> + ClaimHistoryItem(index, claim, navigateToClaimDetails) +} +``` + +Update the preview at the bottom. Replace: +```kotlin +ClaimHistoryScreen( + uiState = uiState, + {}, + {}, + {}, + {}, +) +``` + +With: +```kotlin +ClaimHistoryScreen( + uiState = uiState, + {}, + { _, _ -> }, + {}, +) +``` + +- [ ] **Step 5: Update `HedvigNavHost.kt`** + +Remove the `PartnerClaimOverviewDestination` import and the `partnerClaimDetailsGraph` import. + +Update the `homeGraph` call (around line 185-190). Replace: + +```kotlin +navigateToClaimDetails = { claimId -> + navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) +}, +navigateToPartnerClaimDetails = { claimId -> + navController.navigate(PartnerClaimOverviewDestination(claimId)) +}, +``` + +With: + +```kotlin +navigateToClaimDetails = { claimId, isPartnerClaim -> + navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId, isPartnerClaim)) +}, +``` + +Update the `claimHistoryGraph` call (around lines 357-365). Replace: + +```kotlin +claimHistoryGraph( + navigateUp = navController::navigateUp, + navigateToClaimDetails = { claimId -> + navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) + }, + navigateToPartnerClaimDetails = { claimId -> + navController.navigate(PartnerClaimOverviewDestination(claimId)) + }, +) +``` + +With: + +```kotlin +claimHistoryGraph( + navigateUp = navController::navigateUp, + navigateToClaimDetails = { claimId, isPartnerClaim -> + navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId, isPartnerClaim)) + }, +) +``` + +Remove the `partnerClaimDetailsGraph` call (around lines 566-569): + +```kotlin +partnerClaimDetailsGraph( + navigateUp = navController::navigateUp, + openUrl = openUrl, +) +``` + +Delete those 4 lines entirely. + +- [ ] **Step 6: Commit** + +```bash +git add app/feature/feature-home/ app/feature/feature-claim-history/ app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +git commit -m "refactor: unify claim detail navigation — remove partner-specific callbacks" +``` + +--- + +### Task 5: Delete `feature-partner-claim-details` module and clean up references + +**Files:** +- Delete: `app/feature/feature-partner-claim-details/` (entire directory) +- Delete: `hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml` +- Modify: `app/app/build.gradle.kts` (line 193) +- Modify: `app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt` (lines 81, 348) + +- [ ] **Step 1: Delete the partner claim details module directory** + +```bash +rm -rf app/feature/feature-partner-claim-details +``` + +- [ ] **Step 2: Delete the lint baseline file** + +```bash +rm hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml +``` + +- [ ] **Step 3: Remove dependency from `app/build.gradle.kts`** + +Remove line 193: +```kotlin + implementation(projects.featurePartnerClaimDetails) +``` + +- [ ] **Step 4: Remove from `ApplicationModule.kt`** + +Remove the import at line 81: +```kotlin +import com.hedvig.android.feature.partner.claim.details.di.partnerClaimDetailsModule +``` + +Remove the module inclusion at line 348: +```kotlin + partnerClaimDetailsModule, +``` + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "refactor: delete feature-partner-claim-details module and all references" +``` + +--- + +### Task 6: Format and verify build + +- [ ] **Step 1: Run ktlint formatting** + +```bash +./gradlew ktlintFormat +``` + +- [ ] **Step 2: Build the affected modules** + +```bash +./gradlew :app:assemble +``` + +Fix any compilation errors. + +- [ ] **Step 3: Run tests for claim details module** + +```bash +./gradlew :feature-claim-details:test +``` + +- [ ] **Step 4: Run tests for claim history module** + +```bash +./gradlew :feature-claim-history:test +``` + +- [ ] **Step 5: Commit any formatting fixes** + +```bash +git add -A +git commit -m "chore: run ktlint formatting" +``` From 16e69fb0438641ef21a69e60f835ab5d0b066386 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:47:59 +0200 Subject: [PATCH 19/30] feat: add isPartnerClaim flag to claim detail destination and wire through ViewModel Co-Authored-By: Claude Sonnet 4.6 --- .../feature/claim/details/di/FeatureClaimDetailsModule.kt | 3 ++- .../claim/details/navigation/ClaimDetailDestinationGraph.kt | 2 +- .../claim/details/navigation/ClaimDetailDestinations.kt | 1 + .../feature/claim/details/ui/ClaimDetailsViewModel.kt | 6 ++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt index 5712938434..ed9104b752 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt @@ -23,9 +23,10 @@ val claimDetailsModule = module { initialFilesUri = initialFilesUri, ) } - viewModel { (claimId: String) -> + viewModel { (claimId: String, isPartnerClaim: Boolean) -> ClaimDetailsViewModel( claimId = claimId, + isPartnerClaim = isPartnerClaim, getClaimDetailUiStateUseCase = get(), claimsServiceUploadFileUseCase = get(), downloadPdfUseCase = get(), diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinationGraph.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinationGraph.kt index 09ed0be3a1..814d8abd40 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinationGraph.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinationGraph.kt @@ -31,7 +31,7 @@ fun NavGraphBuilder.claimDetailsGraph( navdestination( deepLinks = navDeepLinks(hedvigDeepLinkContainer.claimDetails), ) { - val viewModel: ClaimDetailsViewModel = koinViewModel { parametersOf(claimId) } + val viewModel: ClaimDetailsViewModel = koinViewModel { parametersOf(claimId, isPartnerClaim) } val context = LocalContext.current ClaimDetailsDestination( viewModel = viewModel, diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinations.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinations.kt index fb294faa5d..4404e5a59e 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinations.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinations.kt @@ -12,6 +12,7 @@ sealed interface ClaimDetailDestination { */ @SerialName("claimId") val claimId: String, + val isPartnerClaim: Boolean = false, ) : ClaimDetailDestination, Destination } diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt index 4276b6be30..783d8c23cc 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt @@ -32,16 +32,18 @@ import kotlinx.datetime.LocalDateTime internal class ClaimDetailsViewModel( claimId: String, + isPartnerClaim: Boolean, getClaimDetailUiStateUseCase: GetClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase: ClaimsServiceUploadFileUseCase, downloadPdfUseCase: DownloadPdfUseCase, ) : MoleculeViewModel( ClaimDetailUiState.Loading, - ClaimDetailPresenter(claimId, getClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase, downloadPdfUseCase), + ClaimDetailPresenter(claimId, isPartnerClaim, getClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase, downloadPdfUseCase), ) private class ClaimDetailPresenter( private val claimId: String, + private val isPartnerClaim: Boolean, private val getClaimDetailUiStateUseCase: GetClaimDetailUiStateUseCase, private val claimsServiceUploadFileUseCase: ClaimsServiceUploadFileUseCase, private val downloadPdfUseCase: DownloadPdfUseCase, @@ -60,7 +62,7 @@ private class ClaimDetailPresenter( LaunchedEffect(loadIteration) { isLoading = true hasError = false - getClaimDetailUiStateUseCase.invoke(claimId).collect { result -> + getClaimDetailUiStateUseCase.invoke(claimId, isPartnerClaim).collect { result -> isLoading = false result.fold( ifLeft = { From 9759d7d65a3f11db98350d33c7632c37ab72bada Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:51:04 +0200 Subject: [PATCH 20/30] feat: add partner claim query support to GetClaimDetailUiStateUseCase Co-Authored-By: Claude Sonnet 4.6 --- .../main/graphql/QueryPartnerClaim.graphql | 5 ++ .../data/GetClaimDetailUiStateUseCase.kt | 68 ++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 app/feature/feature-claim-details/src/main/graphql/QueryPartnerClaim.graphql diff --git a/app/feature/feature-claim-details/src/main/graphql/QueryPartnerClaim.graphql b/app/feature/feature-claim-details/src/main/graphql/QueryPartnerClaim.graphql new file mode 100644 index 0000000000..d6da1d153a --- /dev/null +++ b/app/feature/feature-claim-details/src/main/graphql/QueryPartnerClaim.graphql @@ -0,0 +1,5 @@ +query PartnerClaimDetail($claimId: ID!) { + partnerClaim(id: $claimId) { + ...PartnerClaimFragment + } +} diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt index c24506a857..aa79619061 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt @@ -22,9 +22,12 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn import kotlinx.datetime.toLocalDateTime import octopus.ClaimQuery +import octopus.PartnerClaimDetailQuery import octopus.fragment.ClaimFragment +import octopus.fragment.PartnerClaimFragment import octopus.type.ClaimOutcome import octopus.type.ClaimStatus import octopus.type.InsuranceDocumentType @@ -33,11 +36,14 @@ internal class GetClaimDetailUiStateUseCase( private val apolloClient: ApolloClient, private val crossSellAfterClaimClosedRepository: CrossSellAfterClaimClosedRepository, ) { - fun invoke(claimId: String): Flow> { + fun invoke(claimId: String, isPartnerClaim: Boolean = false): Flow> { return flow { while (currentCoroutineContext().isActive) { - val queryFlow = queryFlow(claimId) - emitAll(queryFlow) + if (isPartnerClaim) { + emitAll(partnerQueryFlow(claimId)) + } else { + emitAll(queryFlow(claimId)) + } delay(POLL_INTERVAL) } } @@ -60,6 +66,62 @@ internal class GetClaimDetailUiStateUseCase( } } + private fun partnerQueryFlow(claimId: String): Flow> { + return apolloClient + .query(PartnerClaimDetailQuery(claimId)) + .fetchPolicy(FetchPolicy.NetworkOnly) + .safeFlow { Error.NetworkError } + .map { response -> + either { + val claim = response.bind().partnerClaim + ensureNotNull(claim) { Error.NoClaimFound } + fromPartnerClaim(claim) + } + } + } + + private fun fromPartnerClaim(claim: PartnerClaimFragment): ClaimDetailUiState.Content { + val termsConditionsUrl = claim.productVariant?.documents + ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS }?.url + val submittedAt = claim.submittedAt + ?.atStartOfDayIn(TimeZone.UTC) + ?.toLocalDateTime(TimeZone.UTC) + ?: kotlinx.datetime.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + return ClaimDetailUiState.Content( + claimId = claim.id, + conversationId = null, + hasUnreadMessages = false, + submittedContent = null, + files = emptyList(), + claimStatusCardUiState = ClaimStatusCardUiState.fromPartnerClaim(claim), + claimStatus = when (claim.status) { + ClaimStatus.CREATED -> ClaimDetailUiState.Content.ClaimStatus.CREATED + ClaimStatus.IN_PROGRESS -> ClaimDetailUiState.Content.ClaimStatus.IN_PROGRESS + ClaimStatus.CLOSED -> ClaimDetailUiState.Content.ClaimStatus.CLOSED + ClaimStatus.REOPENED -> ClaimDetailUiState.Content.ClaimStatus.REOPENED + ClaimStatus.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimStatus.UNKNOWN + }, + claimOutcome = ClaimDetailUiState.Content.ClaimOutcome.UNKNOWN, + uploadUri = "", + isUploadingFile = false, + uploadError = null, + claimType = claim.claimType, + insuranceDisplayName = claim.exposureDisplayName ?: claim.productVariant?.displayName, + submittedAt = submittedAt, + termsConditionsUrl = termsConditionsUrl, + savedFileUri = null, + downloadError = null, + isLoadingPdf = null, + appealInstructionsUrl = null, + isUploadingFilesEnabled = false, + infoText = null, + displayItems = claim.displayItems.map { + DisplayItem.fromStrings(it.displayTitle, it.displayValue) + }, + ) + } + private fun ClaimDetailUiState.Content.Companion.fromClaim( claim: ClaimFragment, conversationId: String?, From 4e19eea11726c05a486f8940258d4cee13440fa7 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:51:42 +0200 Subject: [PATCH 21/30] fix: hide uploaded files header when no submitted content or files --- .../details/ui/ClaimDetailsDestination.kt | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt index 79806be362..29c67ae35f 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt @@ -471,29 +471,31 @@ private fun BeforeGridContent( .fillMaxWidth() .padding(horizontal = 2.dp), ) - Spacer(Modifier.height(24.dp)) - HedvigText( - stringResource(Res.string.claim_status_detail_uploaded_files_info_title), - Modifier.padding(horizontal = 2.dp), - ) - Spacer(Modifier.height(8.dp)) - when (uiState.submittedContent) { - is ClaimDetailUiState.Content.SubmittedContent.Audio -> { - ClaimDetailHedvigAudioPlayerItem(uiState.submittedContent.signedAudioURL) - } + if (uiState.submittedContent != null || uiState.files.isNotEmpty()) { + Spacer(Modifier.height(24.dp)) + HedvigText( + stringResource(Res.string.claim_status_detail_uploaded_files_info_title), + Modifier.padding(horizontal = 2.dp), + ) + Spacer(Modifier.height(8.dp)) + when (uiState.submittedContent) { + is ClaimDetailUiState.Content.SubmittedContent.Audio -> { + ClaimDetailHedvigAudioPlayerItem(uiState.submittedContent.signedAudioURL) + } - is ClaimDetailUiState.Content.SubmittedContent.FreeText -> { - HedvigCard(Modifier.fillMaxWidth()) { - HedvigText( - uiState.submittedContent.text, - Modifier.padding(16.dp), - ) + is ClaimDetailUiState.Content.SubmittedContent.FreeText -> { + HedvigCard(Modifier.fillMaxWidth()) { + HedvigText( + uiState.submittedContent.text, + Modifier.padding(16.dp), + ) + } } - } - else -> {} + else -> {} + } + Spacer(Modifier.height(8.dp)) } - Spacer(Modifier.height(8.dp)) } @Composable From 26953451c531dc6e03e7b2785e88a18e4432d7ac Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:55:32 +0200 Subject: [PATCH 22/30] =?UTF-8?q?refactor:=20unify=20claim=20detail=20navi?= =?UTF-8?q?gation=20=E2=80=94=20remove=20partner-specific=20callbacks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../android/app/navigation/HedvigNavHost.kt | 20 +++---------- .../claimhistory/ClaimHistoryDestination.kt | 25 +++++----------- .../nav/ClaimHistoryDestination.kt | 4 +-- .../feature/home/home/navigation/HomeGraph.kt | 10 ++----- .../feature/home/home/ui/HomeDestination.kt | 29 +++++++------------ 5 files changed, 25 insertions(+), 63 deletions(-) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt index 975843927b..3a1292c054 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt @@ -67,8 +67,6 @@ import com.hedvig.android.feature.insurances.navigation.insuranceGraph import com.hedvig.android.feature.login.navigation.loginGraph import com.hedvig.android.feature.movingflow.SelectContractForMoving import com.hedvig.android.feature.movingflow.movingFlowGraph -import com.hedvig.android.feature.partner.claim.details.navigation.PartnerClaimOverviewDestination -import com.hedvig.android.feature.partner.claim.details.navigation.partnerClaimDetailsGraph import com.hedvig.android.feature.payments.navigation.paymentsGraph import com.hedvig.android.feature.profile.navigation.ProfileDestination import com.hedvig.android.feature.profile.tab.profileGraph @@ -182,11 +180,8 @@ internal fun HedvigNavHost( onNavigateToNewConversation = { navigateToNewConversation() }, - navigateToClaimDetails = { claimId -> - navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) - }, - navigateToPartnerClaimDetails = { claimId -> - navController.navigate(PartnerClaimOverviewDestination(claimId)) + navigateToClaimDetails = { claimId, isPartnerClaim -> + navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId, isPartnerClaim)) }, navigateToConnectPayment = navigateToConnectPayment, navigateToMissingInfo = { contractId: String, type: CoInsuredFlowType -> @@ -356,11 +351,8 @@ internal fun HedvigNavHost( nestedGraphs = { claimHistoryGraph( navigateUp = navController::navigateUp, - navigateToClaimDetails = { claimId -> - navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) - }, - navigateToPartnerClaimDetails = { claimId -> - navController.navigate(PartnerClaimOverviewDestination(claimId)) + navigateToClaimDetails = { claimId, isPartnerClaim -> + navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId, isPartnerClaim)) }, ) }, @@ -563,10 +555,6 @@ private fun NavGraphBuilder.nestedHomeGraphs( applicationId = hedvigBuildConstants.appPackageId, hedvigDeepLinkContainer = hedvigDeepLinkContainer, ) - partnerClaimDetailsGraph( - navigateUp = navController::navigateUp, - openUrl = openUrl, - ) travelCertificateGraph( navController = navController, applicationId = hedvigBuildConstants.appPackageId, diff --git a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt index c302bb2d6e..eac7166861 100644 --- a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt +++ b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt @@ -59,15 +59,13 @@ import org.jetbrains.compose.resources.stringResource internal fun ClaimHistoryDestination( claimHistoryViewModel: ClaimHistoryViewModel, navigateUp: () -> Unit, - navigateToClaimDetails: (String) -> Unit, - navigateToPartnerClaimDetails: (String) -> Unit, + navigateToClaimDetails: (String, Boolean) -> Unit, ) { val uiState by claimHistoryViewModel.uiState.collectAsStateWithLifecycle() ClaimHistoryScreen( uiState = uiState, navigateUp = navigateUp, navigateToClaimDetails = navigateToClaimDetails, - navigateToPartnerClaimDetails = navigateToPartnerClaimDetails, reload = { claimHistoryViewModel.emit(ClaimHistoryEvent.Reload) }, ) } @@ -76,8 +74,7 @@ internal fun ClaimHistoryDestination( private fun ClaimHistoryScreen( uiState: ClaimHistoryUiState, navigateUp: () -> Unit, - navigateToClaimDetails: (String) -> Unit, - navigateToPartnerClaimDetails: (String) -> Unit, + navigateToClaimDetails: (String, Boolean) -> Unit, reload: () -> Unit, ) { HedvigScaffold( @@ -114,7 +111,6 @@ private fun ClaimHistoryScreen( is ClaimHistoryUiState.Content -> ClaimHistoryContent( uiState, navigateToClaimDetails, - navigateToPartnerClaimDetails, ) } } @@ -124,11 +120,10 @@ private fun ClaimHistoryScreen( @Composable private fun ColumnScope.ClaimHistoryContent( uiState: ClaimHistoryUiState.Content, - navigateToClaimDetails: (String) -> Unit, - navigateToPartnerClaimDetails: (String) -> Unit, + navigateToClaimDetails: (String, Boolean) -> Unit, ) { uiState.claims.forEachIndexed { index, claim -> - ClaimHistoryItem(index, claim, navigateToClaimDetails, navigateToPartnerClaimDetails) + ClaimHistoryItem(index, claim, navigateToClaimDetails) } } @@ -136,8 +131,7 @@ private fun ColumnScope.ClaimHistoryContent( private fun ClaimHistoryItem( index: Int, claim: ClaimHistory, - navigateToClaimDetails: (String) -> Unit, - navigateToPartnerClaimDetails: (String) -> Unit, + navigateToClaimDetails: (String, Boolean) -> Unit, ) { val hedvigDateTimeFormatter = rememberHedvigDateTimeFormatter() HorizontalItemsWithMaximumSpaceTaken( @@ -192,11 +186,7 @@ private fun ClaimHistoryItem( .fillMaxWidth() .clickable( onClick = dropUnlessResumed { - if (claim.isPartnerClaim) { - navigateToPartnerClaimDetails(claim.id) - } else { - navigateToClaimDetails(claim.id) - } + navigateToClaimDetails(claim.id, claim.isPartnerClaim) }, ) .horizontalDivider(DividerPosition.Top, show = index != 0, horizontalPadding = 18.dp) @@ -214,8 +204,7 @@ private fun PreviewClaimHistoryScreen( ClaimHistoryScreen( uiState = uiState, {}, - {}, - {}, + { _, _ -> }, {}, ) } diff --git a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt index 92de3a7d7e..2bcecbd7b5 100644 --- a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt +++ b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt @@ -13,15 +13,13 @@ data object ClaimHistoryDestination : Destination fun NavGraphBuilder.claimHistoryGraph( navigateUp: () -> Unit, - navigateToClaimDetails: (String) -> Unit, - navigateToPartnerClaimDetails: (String) -> Unit, + navigateToClaimDetails: (claimId: String, isPartnerClaim: Boolean) -> Unit, ) { navdestination { ClaimHistoryDestination( claimHistoryViewModel = koinViewModel(), navigateUp = navigateUp, navigateToClaimDetails = navigateToClaimDetails, - navigateToPartnerClaimDetails = navigateToPartnerClaimDetails, ) } } diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt index 018f7ed416..9ef6db5136 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt @@ -22,8 +22,7 @@ fun NavGraphBuilder.homeGraph( navController: NavController, onNavigateToInbox: () -> Unit, onNavigateToNewConversation: () -> Unit, - navigateToClaimDetails: (claimId: String) -> Unit, - navigateToPartnerClaimDetails: (claimId: String) -> Unit, + navigateToClaimDetails: (claimId: String, isPartnerClaim: Boolean) -> Unit, navigateToConnectPayment: () -> Unit, navigateToContactInfo: () -> Unit, navigateToMissingInfo: (String, CoInsuredFlowType) -> Unit, @@ -51,11 +50,8 @@ fun NavGraphBuilder.homeGraph( onNavigateToNewConversation = dropUnlessResumed { onNavigateToNewConversation() }, navigateToClaimChat = dropUnlessResumed { navigateToClaimChat() }, navigateToClaimChatInDevMode = dropUnlessResumed { navigateToClaimChatInDevMode() }, - onClaimDetailCardClicked = dropUnlessResumed { claimId: String -> - navigateToClaimDetails(claimId) - }, - onPartnerClaimDetailCardClicked = dropUnlessResumed { claimId: String -> - navigateToPartnerClaimDetails(claimId) + onClaimDetailCardClicked = dropUnlessResumed { claimId: String, isPartnerClaim: Boolean -> + navigateToClaimDetails(claimId, isPartnerClaim) }, navigateToConnectPayment = dropUnlessResumed { navigateToConnectPayment() }, navigateToMissingInfo = dropUnlessResumed { contractId, type -> navigateToMissingInfo(contractId, type) }, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 48f743a11a..cb54a0b14e 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -156,8 +156,7 @@ internal fun HomeDestination( onNavigateToNewConversation: () -> Unit, navigateToClaimChat: () -> Unit, navigateToClaimChatInDevMode: () -> Unit, - onClaimDetailCardClicked: (String) -> Unit, - onPartnerClaimDetailCardClicked: (String) -> Unit, + onClaimDetailCardClicked: (claimId: String, isPartnerClaim: Boolean) -> Unit, navigateToConnectPayment: () -> Unit, navigateToHelpCenter: () -> Unit, openUrl: (String) -> Unit, @@ -180,7 +179,6 @@ internal fun HomeDestination( navigateToClaimChat = navigateToClaimChat, navigateToClaimChatInDevMode = navigateToClaimChatInDevMode, onClaimDetailCardClicked = onClaimDetailCardClicked, - onPartnerClaimDetailCardClicked = onPartnerClaimDetailCardClicked, navigateToConnectPayment = navigateToConnectPayment, navigateToHelpCenter = navigateToHelpCenter, openUrl = openUrl, @@ -208,8 +206,7 @@ private fun HomeScreen( onNavigateToNewConversation: () -> Unit, navigateToClaimChat: () -> Unit, navigateToClaimChatInDevMode: () -> Unit, - onClaimDetailCardClicked: (String) -> Unit, - onPartnerClaimDetailCardClicked: (String) -> Unit, + onClaimDetailCardClicked: (claimId: String, isPartnerClaim: Boolean) -> Unit, navigateToConnectPayment: () -> Unit, navigateToHelpCenter: () -> Unit, openUrl: (String) -> Unit, @@ -278,7 +275,6 @@ private fun HomeScreen( toolbarHeight = toolbarHeight, notificationPermissionState = notificationPermissionState, onClaimDetailCardClicked = onClaimDetailCardClicked, - onPartnerClaimDetailCardClicked = onPartnerClaimDetailCardClicked, navigateToConnectPayment = navigateToConnectPayment, navigateToHelpCenter = navigateToHelpCenter, openClaimFlowSheet = startClaimBottomSheetState::show, @@ -425,8 +421,7 @@ private fun HomeScreenSuccess( pullRefreshState: PullRefreshState, toolbarHeight: Dp, notificationPermissionState: NotificationPermissionState, - onClaimDetailCardClicked: (claimId: String) -> Unit, - onPartnerClaimDetailCardClicked: (claimId: String) -> Unit, + onClaimDetailCardClicked: (claimId: String, isPartnerClaim: Boolean) -> Unit, navigateToConnectPayment: () -> Unit, navigateToHelpCenter: () -> Unit, openClaimFlowSheet: () -> Unit, @@ -480,11 +475,10 @@ private fun HomeScreenSuccess( if (uiState.claimStatusCardsData != null) { ClaimStatusCards( onClick = { claimId -> - if (claimId in (uiState.claimStatusCardsData.partnerClaimIds)) { - onPartnerClaimDetailCardClicked(claimId) - } else { - onClaimDetailCardClicked(claimId) - } + onClaimDetailCardClicked( + claimId, + claimId in (uiState.claimStatusCardsData.partnerClaimIds), + ) }, claimStatusCardsUiState = uiState.claimStatusCardsData.claimStatusCardsUiState, contentPadding = PaddingValues(horizontal = 16.dp) + horizontalInsets, @@ -810,8 +804,7 @@ private fun PreviewHomeScreen( onNavigateToInbox = {}, onNavigateToNewConversation = {}, navigateToClaimChat = {}, - onClaimDetailCardClicked = {}, - onPartnerClaimDetailCardClicked = {}, + onClaimDetailCardClicked = { _, _ -> }, navigateToConnectPayment = {}, navigateToHelpCenter = {}, openUrl = {}, @@ -843,8 +836,7 @@ private fun PreviewHomeScreenWithError() { onNavigateToInbox = {}, onNavigateToNewConversation = {}, navigateToClaimChat = {}, - onClaimDetailCardClicked = {}, - onPartnerClaimDetailCardClicked = {}, + onClaimDetailCardClicked = { _, _ -> }, navigateToConnectPayment = {}, navigateToHelpCenter = {}, openUrl = {}, @@ -897,8 +889,7 @@ private fun PreviewHomeScreenAllHomeTextTypes( onNavigateToInbox = {}, onNavigateToNewConversation = {}, navigateToClaimChat = {}, - onClaimDetailCardClicked = {}, - onPartnerClaimDetailCardClicked = {}, + onClaimDetailCardClicked = { _, _ -> }, navigateToConnectPayment = {}, navigateToHelpCenter = {}, openUrl = {}, From 75a1517e561e8b96155388b397f4ef14cbabab0e Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:58:56 +0200 Subject: [PATCH 23/30] refactor: delete feature-partner-claim-details module and all references --- .worktrees/fix-termination-review | 1 + app/app/build.gradle.kts | 1 - .../android/app/di/ApplicationModule.kt | 2 - .../build.gradle.kts | 34 ---- .../claim/details/QueryPartnerClaim.graphql | 5 - .../data/GetPartnerClaimDetailUseCase.kt | 57 ------ .../details/di/PartnerClaimDetailsModule.kt | 19 -- .../PartnerClaimDetailDestinations.kt | 9 - .../navigation/PartnerClaimDetailGraph.kt | 19 -- .../ui/PartnerClaimDetailsDestination.kt | 187 ------------------ .../ui/PartnerClaimDetailsViewModel.kt | 70 ------- ...baseline-feature-partner-claim-details.xml | 18 -- 12 files changed, 1 insertion(+), 421 deletions(-) create mode 160000 .worktrees/fix-termination-review delete mode 100644 app/feature/feature-partner-claim-details/build.gradle.kts delete mode 100644 app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql delete mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt delete mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt delete mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt delete mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt delete mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt delete mode 100644 app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt delete mode 100644 hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml diff --git a/.worktrees/fix-termination-review b/.worktrees/fix-termination-review new file mode 160000 index 0000000000..71f79ff28e --- /dev/null +++ b/.worktrees/fix-termination-review @@ -0,0 +1 @@ +Subproject commit 71f79ff28e8a6dc2c6c5e0b22054b1f3c288f363 diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 018eacaa46..53719f71d6 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -190,7 +190,6 @@ dependencies { implementation(projects.featureClaimChat) implementation(projects.featureClaimDetails) implementation(projects.featureClaimHistory) - implementation(projects.featurePartnerClaimDetails) implementation(projects.featureConnectPaymentTrustly) implementation(projects.featureCrossSellSheet) implementation(projects.featureDeleteAccount) diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt b/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt index 661b573eea..b4d78717c5 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt @@ -78,7 +78,6 @@ import com.hedvig.android.feature.insurance.certificate.di.insuranceEvidenceModu import com.hedvig.android.feature.insurances.di.insurancesModule import com.hedvig.android.feature.login.di.loginModule import com.hedvig.android.feature.movingflow.di.movingFlowModule -import com.hedvig.android.feature.partner.claim.details.di.partnerClaimDetailsModule import com.hedvig.android.feature.payments.di.paymentsModule import com.hedvig.android.feature.profile.di.profileModule import com.hedvig.android.feature.terminateinsurance.di.terminateInsuranceModule @@ -345,7 +344,6 @@ val applicationModule = module { networkModule, notificationBadgeModule, notificationModule, - partnerClaimDetailsModule, paymentsModule, profileModule, settingsDatastoreModule, diff --git a/app/feature/feature-partner-claim-details/build.gradle.kts b/app/feature/feature-partner-claim-details/build.gradle.kts deleted file mode 100644 index e0845494a9..0000000000 --- a/app/feature/feature-partner-claim-details/build.gradle.kts +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - id("hedvig.android.library") - id("hedvig.gradle.plugin") -} - -hedvig { - apollo("octopus") - serialization() - compose() -} - -dependencies { - implementation(libs.androidx.navigation.compose) - implementation(libs.apollo.normalizedCache) - implementation(libs.apollo.runtime) - implementation(libs.arrow.core) - implementation(libs.jetbrains.lifecycle.runtime.compose) - implementation(libs.koin.composeViewModel) - implementation(libs.koin.core) - implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.core) - implementation(projects.apolloCore) - implementation(projects.apolloOctopusPublic) - implementation(projects.claimStatus) - implementation(projects.composeUi) - implementation(projects.coreCommonPublic) - implementation(projects.coreResources) - implementation(projects.coreUiData) - implementation(projects.dataDisplayItems) - implementation(projects.designSystemHedvig) - implementation(projects.moleculePublic) - implementation(projects.navigationCommon) - implementation(projects.navigationCompose) -} diff --git a/app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql b/app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql deleted file mode 100644 index d6da1d153a..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/graphql/com/hedvig/android/feature/partner/claim/details/QueryPartnerClaim.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query PartnerClaimDetail($claimId: ID!) { - partnerClaim(id: $claimId) { - ...PartnerClaimFragment - } -} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt deleted file mode 100644 index b65e468dcc..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/data/GetPartnerClaimDetailUseCase.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.hedvig.android.feature.partner.claim.details.data - -import arrow.core.Either -import arrow.core.raise.either -import arrow.core.raise.ensureNotNull -import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.cache.normalized.FetchPolicy -import com.apollographql.apollo.cache.normalized.fetchPolicy -import com.hedvig.android.apollo.safeFlow -import com.hedvig.android.data.display.items.DisplayItem -import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailUiState -import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.isActive -import octopus.PartnerClaimDetailQuery -import octopus.type.InsuranceDocumentType - -internal class GetPartnerClaimDetailUseCase( - private val apolloClient: ApolloClient, -) { - fun invoke(claimId: String): Flow> { - return flow { - while (currentCoroutineContext().isActive) { - emitAll( - apolloClient.query(PartnerClaimDetailQuery(claimId)) - .fetchPolicy(FetchPolicy.NetworkOnly) - .safeFlow { Error } - .map { result -> - either { - val data = result.bind() - val claim = data.partnerClaim - ensureNotNull(claim) { Error } - PartnerClaimDetailUiState.Content( - claimStatusCardUiState = ClaimStatusCardUiState.fromPartnerClaim(claim), - claimStatus = claim.status, - displayItems = claim.displayItems.map { - DisplayItem.fromStrings(it.displayTitle, it.displayValue) - }, - termsConditionsUrl = claim.productVariant?.documents - ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS }?.url, - ) - } - }, - ) - delay(10.seconds) - } - } - } - - data object Error -} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt deleted file mode 100644 index 537fa67aff..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/di/PartnerClaimDetailsModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.hedvig.android.feature.partner.claim.details.di - -import com.apollographql.apollo.ApolloClient -import com.hedvig.android.feature.partner.claim.details.data.GetPartnerClaimDetailUseCase -import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailsViewModel -import org.koin.core.module.dsl.viewModel -import org.koin.dsl.module - -val partnerClaimDetailsModule = module { - single { - GetPartnerClaimDetailUseCase(get()) - } - viewModel { (claimId: String) -> - PartnerClaimDetailsViewModel( - claimId = claimId, - getPartnerClaimDetailUseCase = get(), - ) - } -} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt deleted file mode 100644 index b7ccb4999b..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailDestinations.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.hedvig.android.feature.partner.claim.details.navigation - -import com.hedvig.android.navigation.common.Destination -import kotlinx.serialization.Serializable - -@Serializable -data class PartnerClaimOverviewDestination( - val claimId: String, -) : Destination diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt deleted file mode 100644 index 08fb5b7391..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/navigation/PartnerClaimDetailGraph.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.hedvig.android.feature.partner.claim.details.navigation - -import androidx.navigation.NavGraphBuilder -import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailsDestination -import com.hedvig.android.feature.partner.claim.details.ui.PartnerClaimDetailsViewModel -import com.hedvig.android.navigation.compose.navdestination -import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.parameter.parametersOf - -fun NavGraphBuilder.partnerClaimDetailsGraph(navigateUp: () -> Unit, openUrl: (String) -> Unit) { - navdestination { - val viewModel: PartnerClaimDetailsViewModel = koinViewModel { parametersOf(claimId) } - PartnerClaimDetailsDestination( - viewModel = viewModel, - navigateUp = navigateUp, - openUrl = openUrl, - ) - } -} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt deleted file mode 100644 index a265c6beaa..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsDestination.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.hedvig.android.feature.partner.claim.details.ui - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.asPaddingValues -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.size -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.hedvig.android.compose.ui.plus -import com.hedvig.android.design.system.hedvig.HedvigCard -import com.hedvig.android.design.system.hedvig.HedvigErrorSection -import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgressDebounced -import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken -import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.IconButton -import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.TopAppBar -import com.hedvig.android.design.system.hedvig.TopAppBarActionType.BACK -import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.InfoFilled -import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.show -import com.hedvig.android.ui.claimstatus.ClaimDisplayItemsSection -import com.hedvig.android.ui.claimstatus.ClaimExplanationBottomSheet -import com.hedvig.android.ui.claimstatus.ClaimStatusCard -import com.hedvig.android.ui.claimstatus.ClaimTermsConditionsCard -import hedvig.resources.CLAIMS_YOUR_CLAIM -import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION -import hedvig.resources.Res -import hedvig.resources.claim_status_being_handled_support_text -import hedvig.resources.claim_status_claim_details_title -import hedvig.resources.claim_status_detail_documents_title -import octopus.type.ClaimStatus -import org.jetbrains.compose.resources.stringResource - -@Composable -internal fun PartnerClaimDetailsDestination( - viewModel: PartnerClaimDetailsViewModel, - navigateUp: () -> Unit, - openUrl: (String) -> Unit, -) { - val viewState by viewModel.uiState.collectAsStateWithLifecycle() - PartnerClaimDetailScreen( - uiState = viewState, - navigateUp = navigateUp, - openUrl = openUrl, - retry = { viewModel.emit(PartnerClaimDetailEvent.Retry) }, - ) -} - -@Composable -private fun PartnerClaimDetailScreen( - uiState: PartnerClaimDetailUiState, - navigateUp: () -> Unit, - openUrl: (String) -> Unit, - retry: () -> Unit, -) { - Surface( - color = HedvigTheme.colorScheme.backgroundPrimary, - modifier = Modifier.fillMaxSize(), - ) { - Column(Modifier.fillMaxSize()) { - TopAppBar( - title = stringResource(Res.string.CLAIMS_YOUR_CLAIM), - actionType = BACK, - onActionClick = navigateUp, - ) - when (uiState) { - is PartnerClaimDetailUiState.Content -> { - PartnerClaimDetailContent( - uiState = uiState, - openUrl = openUrl, - ) - } - - PartnerClaimDetailUiState.Error -> { - Spacer(Modifier.weight(1f)) - HedvigErrorSection(onButtonClick = retry) - Spacer(Modifier.weight(1f)) - } - - PartnerClaimDetailUiState.Loading -> { - HedvigFullScreenCenterAlignedProgressDebounced() - } - } - } - } -} - -@Composable -private fun PartnerClaimDetailContent(uiState: PartnerClaimDetailUiState.Content, openUrl: (String) -> Unit) { - val explanationBottomSheetState = rememberHedvigBottomSheetState() - ClaimExplanationBottomSheet(explanationBottomSheetState) - Column( - Modifier - .padding( - PaddingValues(horizontal = 16.dp) + WindowInsets.safeDrawing - .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom) - .asPaddingValues(), - ) - .verticalScroll(rememberScrollState()), - ) { - Spacer(Modifier.height(8.dp)) - ClaimStatusCard(uiState = uiState.claimStatusCardUiState) - if (uiState.claimStatus == ClaimStatus.IN_PROGRESS) { - Spacer(Modifier.height(8.dp)) - HedvigCard { - Column(modifier = Modifier.padding(16.dp)) { - HedvigText( - text = stringResource(Res.string.claim_status_being_handled_support_text), - style = HedvigTheme.typography.bodySmall, - ) - } - } - } - Spacer(Modifier.height(24.dp)) - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - Row(verticalAlignment = Alignment.CenterVertically) { - HedvigText( - stringResource(Res.string.claim_status_claim_details_title), - Modifier.padding(horizontal = 2.dp), - ) - } - }, - endSlot = { - Row(horizontalArrangement = Arrangement.End) { - IconButton( - onClick = { explanationBottomSheetState.show(Unit) }, - modifier = Modifier.size(40.dp), - ) { - Icon( - imageVector = HedvigIcons.InfoFilled, - contentDescription = stringResource(Res.string.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION), - modifier = Modifier.size(24.dp), - ) - } - } - }, - spaceBetween = 8.dp, - ) - Spacer(Modifier.height(8.dp)) - ClaimDisplayItemsSection( - displayItems = uiState.displayItems, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 2.dp), - ) - if (uiState.termsConditionsUrl != null) { - Spacer(Modifier.height(24.dp)) - HedvigText( - stringResource(Res.string.claim_status_detail_documents_title), - Modifier.padding(horizontal = 2.dp), - ) - Spacer(Modifier.height(8.dp)) - ClaimTermsConditionsCard( - onClick = { openUrl(uiState.termsConditionsUrl) }, - isLoading = false, - modifier = Modifier.padding(16.dp), - ) - } - Spacer(Modifier.height(16.dp)) - } -} diff --git a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt b/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt deleted file mode 100644 index f6e041a8b7..0000000000 --- a/app/feature/feature-partner-claim-details/src/main/kotlin/com/hedvig/android/feature/partner/claim/details/ui/PartnerClaimDetailsViewModel.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.hedvig.android.feature.partner.claim.details.ui - -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 arrow.core.merge -import arrow.core.raise.either -import com.hedvig.android.data.display.items.DisplayItem -import com.hedvig.android.feature.partner.claim.details.data.GetPartnerClaimDetailUseCase -import com.hedvig.android.molecule.public.MoleculePresenter -import com.hedvig.android.molecule.public.MoleculePresenterScope -import com.hedvig.android.molecule.public.MoleculeViewModel -import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState -import kotlinx.coroutines.flow.collectLatest -import octopus.type.ClaimStatus - -internal class PartnerClaimDetailsViewModel( - claimId: String, - getPartnerClaimDetailUseCase: GetPartnerClaimDetailUseCase, -) : MoleculeViewModel( - PartnerClaimDetailUiState.Loading, - PartnerClaimDetailPresenter(claimId, getPartnerClaimDetailUseCase), - ) - -private class PartnerClaimDetailPresenter( - private val claimId: String, - private val getPartnerClaimDetailUseCase: GetPartnerClaimDetailUseCase, -) : MoleculePresenter { - @Composable - override fun MoleculePresenterScope.present( - lastState: PartnerClaimDetailUiState, - ): PartnerClaimDetailUiState { - var state by remember { mutableStateOf(lastState) } - var loadIteration by remember { mutableIntStateOf(0) } - CollectEvents { event -> - when (event) { - PartnerClaimDetailEvent.Retry -> loadIteration++ - } - } - LaunchedEffect(loadIteration) { - getPartnerClaimDetailUseCase.invoke(claimId).collectLatest { result -> - state = either { - result.mapLeft { PartnerClaimDetailUiState.Error }.bind() - }.merge() - } - } - return state - } -} - -internal sealed interface PartnerClaimDetailEvent { - data object Retry : PartnerClaimDetailEvent -} - -internal sealed interface PartnerClaimDetailUiState { - data object Loading : PartnerClaimDetailUiState - - data object Error : PartnerClaimDetailUiState - - data class Content( - val claimStatusCardUiState: ClaimStatusCardUiState, - val claimStatus: ClaimStatus?, - val displayItems: List, - val termsConditionsUrl: String?, - ) : PartnerClaimDetailUiState -} diff --git a/hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml b/hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml deleted file mode 100644 index bdfed02b17..0000000000 --- a/hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - From dffcca6541e63adfe350ec9d132b9e11bec51799 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 14:59:04 +0200 Subject: [PATCH 24/30] chore: remove accidentally staged worktree --- .worktrees/fix-termination-review | 1 - 1 file changed, 1 deletion(-) delete mode 160000 .worktrees/fix-termination-review diff --git a/.worktrees/fix-termination-review b/.worktrees/fix-termination-review deleted file mode 160000 index 71f79ff28e..0000000000 --- a/.worktrees/fix-termination-review +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 71f79ff28e8a6dc2c6c5e0b22054b1f3c288f363 From 6bc0a311368bb012bf8847abcd966d08830d9763 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 15:02:02 +0200 Subject: [PATCH 25/30] chore: run ktlint formatting --- .../feature/claim/details/ui/ClaimDetailsViewModel.kt | 8 +++++++- .../feature/claimhistory/ClaimHistoryDestination.kt | 6 +----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt index 783d8c23cc..68ca781376 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt @@ -38,7 +38,13 @@ internal class ClaimDetailsViewModel( downloadPdfUseCase: DownloadPdfUseCase, ) : MoleculeViewModel( ClaimDetailUiState.Loading, - ClaimDetailPresenter(claimId, isPartnerClaim, getClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase, downloadPdfUseCase), + ClaimDetailPresenter( + claimId, + isPartnerClaim, + getClaimDetailUiStateUseCase, + claimsServiceUploadFileUseCase, + downloadPdfUseCase, + ), ) private class ClaimDetailPresenter( diff --git a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt index eac7166861..70feccdbd4 100644 --- a/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt +++ b/app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt @@ -128,11 +128,7 @@ private fun ColumnScope.ClaimHistoryContent( } @Composable -private fun ClaimHistoryItem( - index: Int, - claim: ClaimHistory, - navigateToClaimDetails: (String, Boolean) -> Unit, -) { +private fun ClaimHistoryItem(index: Int, claim: ClaimHistory, navigateToClaimDetails: (String, Boolean) -> Unit) { val hedvigDateTimeFormatter = rememberHedvigDateTimeFormatter() HorizontalItemsWithMaximumSpaceTaken( { From 042cabea99fe68ed81b507bb00fe814f03ddeff9 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Fri, 24 Apr 2026 15:08:15 +0200 Subject: [PATCH 26/30] fix: use kotlin.time.Clock instead of kotlinx.datetime.Clock --- .../feature/claim/details/data/GetClaimDetailUiStateUseCase.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt index aa79619061..c86d1d3af0 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive +import kotlin.time.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.atStartOfDayIn import kotlinx.datetime.toLocalDateTime @@ -86,7 +87,7 @@ internal class GetClaimDetailUiStateUseCase( val submittedAt = claim.submittedAt ?.atStartOfDayIn(TimeZone.UTC) ?.toLocalDateTime(TimeZone.UTC) - ?: kotlinx.datetime.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + ?: Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) return ClaimDetailUiState.Content( claimId = claim.id, From 474926b974476fff59ad9dbe78d742fb28527b77 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 27 Apr 2026 11:11:56 +0200 Subject: [PATCH 27/30] fix: show submitted date instead of exposure name on partner claim cards --- .../android/ui/claimstatus/model/ClaimStatusCardUiState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt index d19bdefeeb..4163c0ebb6 100644 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/model/ClaimStatusCardUiState.kt @@ -20,7 +20,7 @@ data class ClaimStatusCardUiState( return ClaimStatusCardUiState( id = claim.id, claimType = claim.claimType, - insuranceDisplayName = claim.exposureDisplayName ?: claim.productVariant?.displayName, + insuranceDisplayName = null, submittedDate = claim.submittedAt?.atStartOfDayIn(TimeZone.UTC) ?: Clock.System.now(), pillTypes = ClaimPillType.fromPartnerClaim(claim.status), claimProgressItemsUiState = ClaimProgressSegment.fromPartnerClaim(claim.status), From ed5daf412f5b5b3e71fadba2d290955b618c6bca Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 27 Apr 2026 11:14:04 +0200 Subject: [PATCH 28/30] chore: remove design spec and implementation plan docs --- .../2026-04-24-unify-partner-claim-detail.md | 753 ------------------ ...04-24-unify-partner-claim-detail-design.md | 100 --- 2 files changed, 853 deletions(-) delete mode 100644 docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md delete mode 100644 docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md diff --git a/docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md b/docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md deleted file mode 100644 index 8e7ed56c7f..0000000000 --- a/docs/superpowers/plans/2026-04-24-unify-partner-claim-detail.md +++ /dev/null @@ -1,753 +0,0 @@ -# Unify Partner Claim Detail Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Remove the `feature-partner-claim-details` module and reuse the existing `feature-claim-details` destination for both regular and partner claims. - -**Architecture:** Add `isPartnerClaim` flag to navigation destination. Branch in use case to call either `ClaimQuery` or `PartnerClaimDetailQuery`, mapping both into `ClaimDetailUiState.Content`. Remove all partner-specific navigation wiring and delete the partner module. - -**Tech Stack:** Kotlin, Jetpack Compose Navigation, Apollo GraphQL, Koin DI, Molecule - ---- - -### Task 1: Add `isPartnerClaim` to navigation destination and wire through ViewModel/DI - -**Files:** -- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinations.kt` -- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/navigation/ClaimDetailDestinationGraph.kt` -- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/di/FeatureClaimDetailsModule.kt` -- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsViewModel.kt` - -- [ ] **Step 1: Add `isPartnerClaim` to `ClaimOverviewDestination`** - -In `ClaimDetailDestinations.kt`, add the parameter: - -```kotlin -sealed interface ClaimDetailDestination { - @Serializable - data class ClaimOverviewDestination( - @SerialName("claimId") - val claimId: String, - val isPartnerClaim: Boolean = false, - ) : ClaimDetailDestination, Destination -} -``` - -- [ ] **Step 2: Pass `isPartnerClaim` from nav destination to ViewModel** - -In `ClaimDetailDestinationGraph.kt`, update the ViewModel instantiation at line 34 to pass `isPartnerClaim`: - -```kotlin -navdestination( - deepLinks = navDeepLinks(hedvigDeepLinkContainer.claimDetails), -) { - val viewModel: ClaimDetailsViewModel = koinViewModel { parametersOf(claimId, isPartnerClaim) } -``` - -- [ ] **Step 3: Update ViewModel and Presenter to accept `isPartnerClaim`** - -In `ClaimDetailsViewModel.kt`, update the ViewModel class to accept the new parameter and forward it to the presenter: - -```kotlin -internal class ClaimDetailsViewModel( - claimId: String, - isPartnerClaim: Boolean, - getClaimDetailUiStateUseCase: GetClaimDetailUiStateUseCase, - claimsServiceUploadFileUseCase: ClaimsServiceUploadFileUseCase, - downloadPdfUseCase: DownloadPdfUseCase, -) : MoleculeViewModel( - ClaimDetailUiState.Loading, - ClaimDetailPresenter(claimId, isPartnerClaim, getClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase, downloadPdfUseCase), - ) -``` - -Update `ClaimDetailPresenter`: - -```kotlin -private class ClaimDetailPresenter( - private val claimId: String, - private val isPartnerClaim: Boolean, - private val getClaimDetailUiStateUseCase: GetClaimDetailUiStateUseCase, - private val claimsServiceUploadFileUseCase: ClaimsServiceUploadFileUseCase, - private val downloadPdfUseCase: DownloadPdfUseCase, -) : MoleculePresenter { -``` - -In the `present()` function, pass `isPartnerClaim` to the use case invocation at line 63: - -```kotlin -getClaimDetailUiStateUseCase.invoke(claimId, isPartnerClaim).collect { result -> -``` - -- [ ] **Step 4: Update Koin module to pass `isPartnerClaim`** - -In `FeatureClaimDetailsModule.kt`, update the ViewModel factory: - -```kotlin -viewModel { (claimId: String, isPartnerClaim: Boolean) -> - ClaimDetailsViewModel( - claimId = claimId, - isPartnerClaim = isPartnerClaim, - getClaimDetailUiStateUseCase = get(), - claimsServiceUploadFileUseCase = get(), - downloadPdfUseCase = get(), - ) -} -``` - -- [ ] **Step 5: Commit** - -```bash -git add app/feature/feature-claim-details/ -git commit -m "feat: add isPartnerClaim flag to claim detail destination and wire through ViewModel" -``` - ---- - -### Task 2: Add partner claim query support to `GetClaimDetailUiStateUseCase` - -**Files:** -- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt` - -- [ ] **Step 1: Add `isPartnerClaim` parameter to `invoke()` and add partner claim mapping** - -Update `GetClaimDetailUiStateUseCase` to accept `isPartnerClaim` and branch on it. Add a new `partnerQueryFlow` method and a `fromPartnerClaim` mapping function. - -The full updated file: - -```kotlin -package com.hedvig.android.feature.claim.details.data - -import arrow.core.Either -import arrow.core.raise.either -import arrow.core.raise.ensureNotNull -import com.apollographql.apollo.ApolloClient -import com.apollographql.apollo.cache.normalized.FetchPolicy -import com.apollographql.apollo.cache.normalized.fetchPolicy -import com.hedvig.android.apollo.safeFlow -import com.hedvig.android.core.uidata.UiFile -import com.hedvig.android.data.cross.sell.after.claim.closed.CrossSellAfterClaimClosedRepository -import com.hedvig.android.data.display.items.DisplayItem -import com.hedvig.android.feature.claim.details.ui.ClaimDetailUiState -import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState -import com.hedvig.audio.player.data.SignedAudioUrl -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.isActive -import kotlinx.datetime.TimeZone -import kotlinx.datetime.atStartOfDayIn -import kotlinx.datetime.toLocalDateTime -import octopus.ClaimQuery -import octopus.PartnerClaimDetailQuery -import octopus.fragment.ClaimFragment -import octopus.fragment.PartnerClaimFragment -import octopus.type.ClaimOutcome -import octopus.type.ClaimStatus -import octopus.type.InsuranceDocumentType - -internal class GetClaimDetailUiStateUseCase( - private val apolloClient: ApolloClient, - private val crossSellAfterClaimClosedRepository: CrossSellAfterClaimClosedRepository, -) { - fun invoke(claimId: String, isPartnerClaim: Boolean = false): Flow> { - return flow { - while (currentCoroutineContext().isActive) { - if (isPartnerClaim) { - emitAll(partnerQueryFlow(claimId)) - } else { - emitAll(queryFlow(claimId)) - } - delay(POLL_INTERVAL) - } - } - } - - private fun queryFlow(claimId: String): Flow> { - return apolloClient - .query(ClaimQuery(claimId)) - .fetchPolicy(FetchPolicy.CacheAndNetwork) - .safeFlow { Error.NetworkError } - .map { response -> - either { - val claim = response.bind().claim - ensureNotNull(claim) { Error.NoClaimFound } - if (claim.showClaimClosedFlow) { - crossSellAfterClaimClosedRepository.acknowledgeClaimClosedStatus(claim) - } - ClaimDetailUiState.Content.fromClaim(claim, claim.conversation?.id, claim.conversation?.unreadMessageCount) - } - } - } - - private fun partnerQueryFlow(claimId: String): Flow> { - return apolloClient - .query(PartnerClaimDetailQuery(claimId)) - .fetchPolicy(FetchPolicy.NetworkOnly) - .safeFlow { Error.NetworkError } - .map { response -> - either { - val claim = response.bind().partnerClaim - ensureNotNull(claim) { Error.NoClaimFound } - fromPartnerClaim(claim) - } - } - } - - private fun fromPartnerClaim(claim: PartnerClaimFragment): ClaimDetailUiState.Content { - val termsConditionsUrl = claim.productVariant?.documents - ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS }?.url - val submittedAt = claim.submittedAt - ?.atStartOfDayIn(TimeZone.UTC) - ?.toLocalDateTime(TimeZone.UTC) - ?: kotlinx.datetime.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) - - return ClaimDetailUiState.Content( - claimId = claim.id, - conversationId = null, - hasUnreadMessages = false, - submittedContent = null, - files = emptyList(), - claimStatusCardUiState = ClaimStatusCardUiState.fromPartnerClaim(claim), - claimStatus = when (claim.status) { - ClaimStatus.CREATED -> ClaimDetailUiState.Content.ClaimStatus.CREATED - ClaimStatus.IN_PROGRESS -> ClaimDetailUiState.Content.ClaimStatus.IN_PROGRESS - ClaimStatus.CLOSED -> ClaimDetailUiState.Content.ClaimStatus.CLOSED - ClaimStatus.REOPENED -> ClaimDetailUiState.Content.ClaimStatus.REOPENED - ClaimStatus.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimStatus.UNKNOWN - }, - claimOutcome = ClaimDetailUiState.Content.ClaimOutcome.UNKNOWN, - uploadUri = "", - isUploadingFile = false, - uploadError = null, - claimType = claim.claimType, - insuranceDisplayName = claim.exposureDisplayName ?: claim.productVariant?.displayName, - submittedAt = submittedAt, - termsConditionsUrl = termsConditionsUrl, - savedFileUri = null, - downloadError = null, - isLoadingPdf = null, - appealInstructionsUrl = null, - isUploadingFilesEnabled = false, - infoText = null, - displayItems = claim.displayItems.map { - DisplayItem.fromStrings(it.displayTitle, it.displayValue) - }, - ) - } - - private fun ClaimDetailUiState.Content.Companion.fromClaim( - claim: ClaimFragment, - conversationId: String?, - conversationUnreadMessageCount: Int?, - ): ClaimDetailUiState.Content { - val audioUrl = claim.audioUrl - val memberFreeText = claim.memberFreeText - - val claimType: String? = claim.claimType - val submittedAt = claim.submittedAt.toLocalDateTime(TimeZone.currentSystemDefault()) - val insuranceDisplayName = claim.productVariant?.displayName - val termsConditionsUrl = - claim.productVariant - ?.documents - ?.firstOrNull { it.type == InsuranceDocumentType.TERMS_AND_CONDITIONS } - ?.url - - return ClaimDetailUiState.Content( - claimId = claim.id, - conversationId = conversationId, - hasUnreadMessages = (conversationUnreadMessageCount ?: 0) > 0, - submittedContent = when { - audioUrl != null -> { - ClaimDetailUiState.Content.SubmittedContent.Audio(SignedAudioUrl.fromSignedAudioUrlString(audioUrl)) - } - - memberFreeText != null -> { - ClaimDetailUiState.Content.SubmittedContent.FreeText(memberFreeText) - } - - else -> { - null - } - }, - files = claim.files.map { - UiFile( - id = it.id, - name = it.name, - mimeType = it.mimeType, - url = it.url, - localPath = null, - ) - }, - claimStatusCardUiState = ClaimStatusCardUiState.fromClaimStatusCardsQuery(claim), - claimStatus = when (claim.status) { - ClaimStatus.CREATED -> ClaimDetailUiState.Content.ClaimStatus.CREATED - ClaimStatus.IN_PROGRESS -> ClaimDetailUiState.Content.ClaimStatus.IN_PROGRESS - ClaimStatus.CLOSED -> ClaimDetailUiState.Content.ClaimStatus.CLOSED - ClaimStatus.REOPENED -> ClaimDetailUiState.Content.ClaimStatus.REOPENED - ClaimStatus.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimStatus.UNKNOWN - }, - claimOutcome = when (claim.outcome) { - ClaimOutcome.PAID -> ClaimDetailUiState.Content.ClaimOutcome.PAID - ClaimOutcome.NOT_COMPENSATED -> ClaimDetailUiState.Content.ClaimOutcome.NOT_COMPENSATED - ClaimOutcome.NOT_COVERED -> ClaimDetailUiState.Content.ClaimOutcome.NOT_COVERED - ClaimOutcome.UNKNOWN__, null -> ClaimDetailUiState.Content.ClaimOutcome.UNKNOWN - ClaimOutcome.UNRESPONSIVE -> ClaimDetailUiState.Content.ClaimOutcome.UNRESPONSIVE - }, - uploadUri = claim.targetFileUploadUri, - isUploadingFile = false, - uploadError = null, - claimType = claimType, - insuranceDisplayName = insuranceDisplayName, - submittedAt = submittedAt, - termsConditionsUrl = termsConditionsUrl, - savedFileUri = null, - downloadError = null, - isLoadingPdf = null, - appealInstructionsUrl = claim.appealInstructionsUrl, - isUploadingFilesEnabled = claim.isUploadingFilesEnabled, - infoText = claim.infoText, - displayItems = claim.displayItems.map { - DisplayItem.fromStrings(it.displayTitle, it.displayValue) - }, - ) - } - - companion object { - private val POLL_INTERVAL = 10.seconds - } -} - -sealed interface Error { - data object NetworkError : Error - data object NoClaimFound : Error -} -``` - -- [ ] **Step 2: Commit** - -```bash -git add app/feature/feature-claim-details/ -git commit -m "feat: add partner claim query support to GetClaimDetailUiStateUseCase" -``` - ---- - -### Task 3: Guard the "uploaded files" section header in the UI - -**Files:** -- Modify: `app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt` - -- [ ] **Step 1: Wrap the "uploaded files" header and submitted content in a conditional** - -In `ClaimDetailsDestination.kt`, in the `BeforeGridContent` composable, the "uploaded files" section header at lines 475-496 is always shown. Wrap lines 474-496 in a condition: - -Find this block (starting after the `ClaimDisplayItemsSection`): - -```kotlin - Spacer(Modifier.height(24.dp)) - HedvigText( - stringResource(Res.string.claim_status_detail_uploaded_files_info_title), - Modifier.padding(horizontal = 2.dp), - ) - Spacer(Modifier.height(8.dp)) - when (uiState.submittedContent) { - is ClaimDetailUiState.Content.SubmittedContent.Audio -> { - ClaimDetailHedvigAudioPlayerItem(uiState.submittedContent.signedAudioURL) - } - - is ClaimDetailUiState.Content.SubmittedContent.FreeText -> { - HedvigCard(Modifier.fillMaxWidth()) { - HedvigText( - uiState.submittedContent.text, - Modifier.padding(16.dp), - ) - } - } - - else -> {} - } - Spacer(Modifier.height(8.dp)) -``` - -Replace with: - -```kotlin - if (uiState.submittedContent != null || uiState.files.isNotEmpty()) { - Spacer(Modifier.height(24.dp)) - HedvigText( - stringResource(Res.string.claim_status_detail_uploaded_files_info_title), - Modifier.padding(horizontal = 2.dp), - ) - Spacer(Modifier.height(8.dp)) - when (uiState.submittedContent) { - is ClaimDetailUiState.Content.SubmittedContent.Audio -> { - ClaimDetailHedvigAudioPlayerItem(uiState.submittedContent.signedAudioURL) - } - - is ClaimDetailUiState.Content.SubmittedContent.FreeText -> { - HedvigCard(Modifier.fillMaxWidth()) { - HedvigText( - uiState.submittedContent.text, - Modifier.padding(16.dp), - ) - } - } - - else -> {} - } - Spacer(Modifier.height(8.dp)) - } -``` - -- [ ] **Step 2: Commit** - -```bash -git add app/feature/feature-claim-details/ -git commit -m "fix: hide uploaded files header when no submitted content or files" -``` - ---- - -### Task 4: Update navigation callers to pass `isPartnerClaim` flag - -**Files:** -- Modify: `app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeGraph.kt` -- Modify: `app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt` -- Modify: `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt` -- Modify: `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt` -- Modify: `app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt` - -- [ ] **Step 1: Update `HomeGraph.kt` — remove `navigateToPartnerClaimDetails`, change `navigateToClaimDetails` signature** - -Replace the function signature to change `navigateToClaimDetails` to accept both `claimId` and `isPartnerClaim`, and remove `navigateToPartnerClaimDetails`: - -```kotlin -fun NavGraphBuilder.homeGraph( - nestedGraphs: NavGraphBuilder.() -> Unit, - hedvigDeepLinkContainer: HedvigDeepLinkContainer, - navController: NavController, - onNavigateToInbox: () -> Unit, - onNavigateToNewConversation: () -> Unit, - navigateToClaimDetails: (claimId: String, isPartnerClaim: Boolean) -> Unit, - navigateToConnectPayment: () -> Unit, - navigateToContactInfo: () -> Unit, - navigateToMissingInfo: (String, CoInsuredFlowType) -> Unit, - navigateToHelpCenter: () -> Unit, - navigateToClaimChat: () -> Unit, - navigateToClaimChatInDevMode: () -> Unit, - navigateToChipIdScreen: () -> Unit, - openAppSettings: () -> Unit, - openUrl: (String) -> Unit, - openCrossSellUrl: (String) -> Unit, - imageLoader: ImageLoader, -) { -``` - -Inside the function body, update the `HomeDestination` call. Replace lines 54-59: - -```kotlin - onClaimDetailCardClicked = dropUnlessResumed { claimId: String -> - navigateToClaimDetails(claimId) - }, - onPartnerClaimDetailCardClicked = dropUnlessResumed { claimId: String -> - navigateToPartnerClaimDetails(claimId) - }, -``` - -With: - -```kotlin - onClaimDetailCardClicked = dropUnlessResumed { claimId: String, isPartnerClaim: Boolean -> - navigateToClaimDetails(claimId, isPartnerClaim) - }, -``` - -- [ ] **Step 2: Update `HomeDestination.kt` — merge two callbacks into one** - -In `HomeDestination.kt`, throughout all the composable function signatures, replace: - -```kotlin -onClaimDetailCardClicked: (String) -> Unit, -onPartnerClaimDetailCardClicked: (String) -> Unit, -``` - -With: - -```kotlin -onClaimDetailCardClicked: (claimId: String, isPartnerClaim: Boolean) -> Unit, -``` - -This appears in `HomeDestination` (line 159-160), `HomeScreen` (line 211-212), and `HomeScreenSuccess` (line 428-429). - -In `HomeScreenSuccess`, update the `ClaimStatusCards` click handler (lines 482-488): - -```kotlin -ClaimStatusCards( - onClick = { claimId -> - onClaimDetailCardClicked( - claimId, - claimId in (uiState.claimStatusCardsData.partnerClaimIds), - ) - }, -``` - -Update all preview composables to use the new signature — replace each `onClaimDetailCardClicked = {},` + `onPartnerClaimDetailCardClicked = {},` pair with `onClaimDetailCardClicked = { _, _ -> },`. - -- [ ] **Step 3: Update claim history nav `ClaimHistoryDestination.kt` (nav file)** - -In `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/nav/ClaimHistoryDestination.kt`, update: - -```kotlin -fun NavGraphBuilder.claimHistoryGraph( - navigateUp: () -> Unit, - navigateToClaimDetails: (claimId: String, isPartnerClaim: Boolean) -> Unit, -) { - navdestination { - ClaimHistoryDestination( - claimHistoryViewModel = koinViewModel(), - navigateUp = navigateUp, - navigateToClaimDetails = navigateToClaimDetails, - ) - } -} -``` - -- [ ] **Step 4: Update claim history UI `ClaimHistoryDestination.kt`** - -In `app/feature/feature-claim-history/src/androidMain/kotlin/com/hedvig/android/feature/claimhistory/ClaimHistoryDestination.kt`, remove `navigateToPartnerClaimDetails` from all function signatures: - -`ClaimHistoryDestination`: -```kotlin -@Composable -internal fun ClaimHistoryDestination( - claimHistoryViewModel: ClaimHistoryViewModel, - navigateUp: () -> Unit, - navigateToClaimDetails: (String, Boolean) -> Unit, -) -``` - -`ClaimHistoryScreen`: -```kotlin -@Composable -private fun ClaimHistoryScreen( - uiState: ClaimHistoryUiState, - navigateUp: () -> Unit, - navigateToClaimDetails: (String, Boolean) -> Unit, - reload: () -> Unit, -) -``` - -`ClaimHistoryContent` — remove `navigateToPartnerClaimDetails` parameter: -```kotlin -@Composable -private fun ColumnScope.ClaimHistoryContent( - uiState: ClaimHistoryUiState.Content, - navigateToClaimDetails: (String, Boolean) -> Unit, -) -``` - -`ClaimHistoryItem` — remove `navigateToPartnerClaimDetails` and update click handler: -```kotlin -@Composable -private fun ClaimHistoryItem( - index: Int, - claim: ClaimHistory, - navigateToClaimDetails: (String, Boolean) -> Unit, -) -``` - -Update the click handler in `ClaimHistoryItem` (lines 193-201): - -```kotlin - modifier = Modifier - .fillMaxWidth() - .clickable( - onClick = dropUnlessResumed { - navigateToClaimDetails(claim.id, claim.isPartnerClaim) - }, - ) -``` - -Update the `forEachIndexed` in `ClaimHistoryContent` to pass only `navigateToClaimDetails`: -```kotlin -uiState.claims.forEachIndexed { index, claim -> - ClaimHistoryItem(index, claim, navigateToClaimDetails) -} -``` - -Update the preview at the bottom. Replace: -```kotlin -ClaimHistoryScreen( - uiState = uiState, - {}, - {}, - {}, - {}, -) -``` - -With: -```kotlin -ClaimHistoryScreen( - uiState = uiState, - {}, - { _, _ -> }, - {}, -) -``` - -- [ ] **Step 5: Update `HedvigNavHost.kt`** - -Remove the `PartnerClaimOverviewDestination` import and the `partnerClaimDetailsGraph` import. - -Update the `homeGraph` call (around line 185-190). Replace: - -```kotlin -navigateToClaimDetails = { claimId -> - navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) -}, -navigateToPartnerClaimDetails = { claimId -> - navController.navigate(PartnerClaimOverviewDestination(claimId)) -}, -``` - -With: - -```kotlin -navigateToClaimDetails = { claimId, isPartnerClaim -> - navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId, isPartnerClaim)) -}, -``` - -Update the `claimHistoryGraph` call (around lines 357-365). Replace: - -```kotlin -claimHistoryGraph( - navigateUp = navController::navigateUp, - navigateToClaimDetails = { claimId -> - navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId)) - }, - navigateToPartnerClaimDetails = { claimId -> - navController.navigate(PartnerClaimOverviewDestination(claimId)) - }, -) -``` - -With: - -```kotlin -claimHistoryGraph( - navigateUp = navController::navigateUp, - navigateToClaimDetails = { claimId, isPartnerClaim -> - navController.navigate(ClaimDetailDestination.ClaimOverviewDestination(claimId, isPartnerClaim)) - }, -) -``` - -Remove the `partnerClaimDetailsGraph` call (around lines 566-569): - -```kotlin -partnerClaimDetailsGraph( - navigateUp = navController::navigateUp, - openUrl = openUrl, -) -``` - -Delete those 4 lines entirely. - -- [ ] **Step 6: Commit** - -```bash -git add app/feature/feature-home/ app/feature/feature-claim-history/ app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigNavHost.kt -git commit -m "refactor: unify claim detail navigation — remove partner-specific callbacks" -``` - ---- - -### Task 5: Delete `feature-partner-claim-details` module and clean up references - -**Files:** -- Delete: `app/feature/feature-partner-claim-details/` (entire directory) -- Delete: `hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml` -- Modify: `app/app/build.gradle.kts` (line 193) -- Modify: `app/app/src/main/kotlin/com/hedvig/android/app/di/ApplicationModule.kt` (lines 81, 348) - -- [ ] **Step 1: Delete the partner claim details module directory** - -```bash -rm -rf app/feature/feature-partner-claim-details -``` - -- [ ] **Step 2: Delete the lint baseline file** - -```bash -rm hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml -``` - -- [ ] **Step 3: Remove dependency from `app/build.gradle.kts`** - -Remove line 193: -```kotlin - implementation(projects.featurePartnerClaimDetails) -``` - -- [ ] **Step 4: Remove from `ApplicationModule.kt`** - -Remove the import at line 81: -```kotlin -import com.hedvig.android.feature.partner.claim.details.di.partnerClaimDetailsModule -``` - -Remove the module inclusion at line 348: -```kotlin - partnerClaimDetailsModule, -``` - -- [ ] **Step 5: Commit** - -```bash -git add -A -git commit -m "refactor: delete feature-partner-claim-details module and all references" -``` - ---- - -### Task 6: Format and verify build - -- [ ] **Step 1: Run ktlint formatting** - -```bash -./gradlew ktlintFormat -``` - -- [ ] **Step 2: Build the affected modules** - -```bash -./gradlew :app:assemble -``` - -Fix any compilation errors. - -- [ ] **Step 3: Run tests for claim details module** - -```bash -./gradlew :feature-claim-details:test -``` - -- [ ] **Step 4: Run tests for claim history module** - -```bash -./gradlew :feature-claim-history:test -``` - -- [ ] **Step 5: Commit any formatting fixes** - -```bash -git add -A -git commit -m "chore: run ktlint formatting" -``` diff --git a/docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md b/docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md deleted file mode 100644 index e76a238a25..0000000000 --- a/docs/superpowers/specs/2026-04-24-unify-partner-claim-detail-design.md +++ /dev/null @@ -1,100 +0,0 @@ -# Unify Partner Claim Detail Into Regular Claim Detail Destination - -## Problem - -PR #2930 introduces a separate `feature-partner-claim-details` module with its own destination, ViewModel, presenter, use case, and navigation wiring for partner claims. This creates dual navigation paths (home screen and claim history both need `navigateToPartnerClaimDetails` alongside `navigateToClaimDetails`), a separate module to maintain, and duplicated UI that is a subset of the regular claim detail screen. - -## Goal - -Remove `feature-partner-claim-details` and reuse the existing `feature-claim-details` destination for both regular and partner claims. The regular claim detail screen already conditionally renders most sections (chat, files, upload, audio), so partner claims can be represented as `ClaimDetailUiState.Content` with null/empty/disabled values for unsupported features. - -## Approach - -Extend the existing claim detail flow to handle partner claims by: -1. Adding an `isPartnerClaim` flag to the navigation destination -2. Branching in the use case to call the appropriate GraphQL query -3. Mapping partner claim data into the existing UI state model -4. Removing the partner claim module entirely - -## Design - -### 1. Navigation Destination - -Add `isPartnerClaim: Boolean = false` to `ClaimOverviewDestination`: - -```kotlin -data class ClaimOverviewDestination( - val claimId: String, - val isPartnerClaim: Boolean = false, -) : ClaimDetailDestination, Destination -``` - -Default `false` preserves backward compatibility with deep links and existing callers. - -### 2. Use Case Changes - -`GetClaimDetailUiStateUseCase` receives `isPartnerClaim` alongside `claimId` and branches: - -- **Regular claim:** Calls `ClaimQuery(claimId)` as today, maps via `fromClaim()` -- **Partner claim:** Calls `PartnerClaimDetailQuery(claimId)`, maps into `ClaimDetailUiState.Content` with: - - `conversationId = null` - - `hasUnreadMessages = false` - - `submittedContent = null` - - `files = emptyList()` - - `claimOutcome = UNKNOWN` - - `uploadUri = ""`, `isUploadingFile = false`, `isUploadingFilesEnabled = false` - - `appealInstructionsUrl = null`, `infoText = null` - - `claimStatus`, `displayItems`, `termsConditionsUrl`, `claimStatusCardUiState` mapped from `PartnerClaimFragment` - -### 3. UI Changes - -Minimal. The existing screen already guards most sections: - -- Chat link: `if (navigateToConversation != null)` -- null conversationId means no chat -- Files grid: `if (uiState.files.isNotEmpty())` -- empty list means no grid -- File upload: `if (uiState.isUploadingFilesEnabled)` -- false means no upload -- Submitted content: `when (uiState.submittedContent)` with `else -> {}` -- null means nothing -- Appeal instructions: `if (uiState.appealInstructionsUrl != null)` -- Info notification: `if (uiState.infoText != null)` -- T&C card: `if (uiState.termsConditionsUrl != null)` - -One fix needed: guard the "uploaded files" section header with `if (uiState.submittedContent != null || uiState.files.isNotEmpty())` to avoid showing an empty header for partner claims. - -### 4. Navigation Cleanup - -- Remove `navigateToPartnerClaimDetails` from `homeGraph()`, `HomeDestination`, `HomeScreen`, `HomeScreenSuccess`, `claimHistoryGraph()`, `ClaimHistoryDestination`, `HedvigNavHost` -- Callers pass `isPartnerClaim = true` to the existing `navigateToClaimDetails` callback instead -- Keep `partnerClaimIds` in `ClaimStatusCardsData` and `isPartnerClaim` in `ClaimHistory` -- still needed to pass the flag - -### 5. Module Deletion - -Delete entirely: -- `app/feature/feature-partner-claim-details/` directory -- `hedvig-lint/lint-baseline/lint-baseline-feature-partner-claim-details.xml` - -Remove references: -- `partnerClaimDetailsModule` from `ApplicationModule.kt` -- `projects.featurePartnerClaimDetails` from `app/build.gradle.kts` -- `partnerClaimDetailsGraph` from `HedvigNavHost.kt` - -## Files to Modify - -| Area | File | Change | -|------|------|--------| -| Nav destination | `ClaimDetailDestinations.kt` | Add `isPartnerClaim` param | -| Nav graph | `ClaimDetailDestinationGraph.kt` | Pass `isPartnerClaim` to ViewModel | -| DI | `FeatureClaimDetailsModule.kt` | Pass `isPartnerClaim` through | -| ViewModel | `ClaimDetailsViewModel.kt` | Accept + forward `isPartnerClaim` | -| Use case | `GetClaimDetailUiStateUseCase.kt` | Branch on `isPartnerClaim`, add partner claim mapping | -| UI | `ClaimDetailsDestination.kt` | Guard "uploaded files" header | -| Home nav | `HomeGraph.kt`, `HomeDestination.kt` | Remove partner callback, pass flag | -| Claim history | `ClaimHistoryDestination.kt` (nav + ui) | Remove partner callback, pass flag | -| App nav host | `HedvigNavHost.kt` | Remove partner graph, update callbacks | -| App module | `ApplicationModule.kt`, `build.gradle.kts` | Remove partner module refs | -| Delete | `feature-partner-claim-details/`, lint baseline | Full removal | - -## Out of Scope - -- No changes to GraphQL queries/fragments -- No changes to `ui-claim-status` shared components -- No changes to `GetHomeDataUseCase` or `GetClaimsHistoryUseCase` From 2d90eb655303ac48aa80f5e12e6468e78a3f6959 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 27 Apr 2026 11:29:31 +0200 Subject: [PATCH 29/30] refactor: move shared claim detail composables back to feature-claim-details Since partner claims and regular claims share the same feature module, there's no need to extract these components to ui-claim-status. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../data/GetClaimDetailUiStateUseCase.kt | 2 +- .../details/ui/ClaimDetailsDestination.kt | 142 +++++++++++++++- app/ui/claim-status/build.gradle.kts | 2 - .../ClaimDetailSharedComponents.kt | 157 ------------------ 4 files changed, 134 insertions(+), 169 deletions(-) delete mode 100644 app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt index c86d1d3af0..e6af1602ae 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/data/GetClaimDetailUiStateUseCase.kt @@ -13,6 +13,7 @@ import com.hedvig.android.data.display.items.DisplayItem import com.hedvig.android.feature.claim.details.ui.ClaimDetailUiState import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState import com.hedvig.audio.player.data.SignedAudioUrl +import kotlin.time.Clock import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay @@ -21,7 +22,6 @@ import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive -import kotlin.time.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.atStartOfDayIn import kotlinx.datetime.toLocalDateTime diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt index 29c67ae35f..ef358e3c1b 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -27,6 +28,7 @@ import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -45,15 +47,20 @@ import com.hedvig.android.compose.photo.capture.state.rememberPhotoCaptureState import com.hedvig.android.compose.ui.LayoutWithoutPlacement import com.hedvig.android.compose.ui.plus import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider +import com.hedvig.android.compose.ui.stringWithShiftedLabel import com.hedvig.android.core.common.safeCast import com.hedvig.android.core.uidata.UiCurrencyCode import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.core.uidata.UiMoney import com.hedvig.android.data.display.items.DisplayItem +import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Date +import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.DateTime import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Text +import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Medium import com.hedvig.android.design.system.hedvig.ErrorDialog import com.hedvig.android.design.system.hedvig.File +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigErrorSection @@ -62,30 +69,32 @@ import com.hedvig.android.design.system.hedvig.HedvigMultiScreenPreview import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText +import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HedvigThreeDotsProgressIndicator 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.IconButton +import com.hedvig.android.design.system.hedvig.LocalContentColor +import com.hedvig.android.design.system.hedvig.LocalTextStyle import com.hedvig.android.design.system.hedvig.NotificationDefaults import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.TopAppBar import com.hedvig.android.design.system.hedvig.TopAppBarActionType.BACK +import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast import com.hedvig.android.design.system.hedvig.icon.Chat import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.InfoFilled import com.hedvig.android.design.system.hedvig.notificationCircle import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState +import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader import com.hedvig.android.design.system.hedvig.show import com.hedvig.android.logger.logcat import com.hedvig.android.shared.file.upload.ui.FilePickerBottomSheet -import com.hedvig.android.ui.claimstatus.ClaimDisplayItemsSection -import com.hedvig.android.ui.claimstatus.ClaimDocumentCard -import com.hedvig.android.ui.claimstatus.ClaimExplanationBottomSheet import com.hedvig.android.ui.claimstatus.ClaimStatusCard -import com.hedvig.android.ui.claimstatus.ClaimTermsConditionsCard import com.hedvig.android.ui.claimstatus.model.ClaimPillType import com.hedvig.android.ui.claimstatus.model.ClaimProgressSegment import com.hedvig.android.ui.claimstatus.model.ClaimStatusCardUiState @@ -93,12 +102,15 @@ import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.audio.player.data.SignedAudioUrl import hedvig.resources.CLAIMS_YOUR_CLAIM import hedvig.resources.DASHBOARD_OPEN_CHAT +import hedvig.resources.MY_DOCUMENTS_INSURANCE_TERMS import hedvig.resources.REFERRALS_INFO_BUTTON_CONTENT_DESCRIPTION import hedvig.resources.Res +import hedvig.resources.TALKBACK_OPEN_EXTERNAL_LINK import hedvig.resources.claim_outcome_unresponsive_support_text import hedvig.resources.claim_status_appeal_instruction_link_text import hedvig.resources.claim_status_being_handled_reopened_support_text import hedvig.resources.claim_status_being_handled_support_text +import hedvig.resources.claim_status_claim_details_info_text import hedvig.resources.claim_status_claim_details_title import hedvig.resources.claim_status_detail_add_files import hedvig.resources.claim_status_detail_add_more_files @@ -110,6 +122,7 @@ import hedvig.resources.claim_status_not_covered_support_text import hedvig.resources.claim_status_paid_support_text_short import hedvig.resources.claim_status_submitted_support_text import hedvig.resources.claim_status_uploaded_files_upload_text +import hedvig.resources.general_close_button import hedvig.resources.general_error import hedvig.resources.something_went_wrong import hedvig.resources.travel_certificate_downloading_error @@ -310,7 +323,7 @@ private fun NonDynamicGrid( navigateToConversation: (() -> Unit)?, ) { val explanationBottomSheetState = rememberHedvigBottomSheetState() - ClaimExplanationBottomSheet(explanationBottomSheetState) + ExplanationBottomSheet(explanationBottomSheetState) Column( Modifier .padding( @@ -465,7 +478,7 @@ private fun BeforeGridContent( ) Spacer(Modifier.height(8.dp)) - ClaimDisplayItemsSection( + DisplayItemsSection( displayItems = uiState.displayItems, modifier = Modifier .fillMaxWidth() @@ -551,7 +564,7 @@ private fun AfterGridContent( Spacer(Modifier.height(8.dp)) } if (uiState.termsConditionsUrl != null) { - ClaimTermsConditionsCard( + TermsConditionsCard( onClick = { downloadFromUrl(uiState.termsConditionsUrl) }, modifier = Modifier.padding(16.dp), isLoading = uiState.termsConditionsUrl == uiState.isLoadingPdf, @@ -579,7 +592,7 @@ private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modif if (isLoading) { LayoutWithoutPlacement( sizeAdjustingContent = { - ClaimDocumentCard( + DocumentCard( title = stringResource(Res.string.claim_status_appeal_instruction_link_text), ) }, @@ -593,7 +606,7 @@ private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modif } } } else { - ClaimDocumentCard( + DocumentCard( title = stringResource(Res.string.claim_status_appeal_instruction_link_text), ) } @@ -659,6 +672,117 @@ private fun ClaimDetailHedvigAudioPlayerItem(signedAudioUrl: SignedAudioUrl, mod } } +@Composable +private fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { + HedvigBottomSheet(sheetState) { _ -> + HedvigText( + text = stringResource(Res.string.claim_status_claim_details_info_text), + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(32.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_close_button), + buttonSize = Large, + onClick = { sheetState.dismiss() }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } +} + +@Composable +private fun DisplayItemsSection(displayItems: List, modifier: Modifier = Modifier) { + CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { + Column(modifier) { + for (displayItem in displayItems) { + HorizontalItemsWithMaximumSpaceTaken( + spaceBetween = 8.dp, + startSlot = { + HedvigText(text = displayItem.title) + }, + endSlot = { + val formatter = rememberHedvigDateTimeFormatter() + HedvigText( + text = when (val item = displayItem.value) { + is Date -> formatter.format(item.date) + is DateTime -> formatter.format(item.localDateTime) + is Text -> item.text + }, + textAlign = TextAlign.End, + ) + }, + ) + } + } + } +} + +@Composable +private fun TermsConditionsCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { + HedvigCard(onClick = onClick) { + Row( + modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + if (isLoading) { + LayoutWithoutPlacement( + sizeAdjustingContent = { + DocumentCard( + title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), + ) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + HedvigThreeDotsProgressIndicator() + } + } + } else { + DocumentCard( + title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), + ) + } + } + } +} + +@Composable +private fun DocumentCard(title: String) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + HorizontalItemsWithMaximumSpaceTaken( + startSlot = { + Column { + HedvigText( + text = stringWithShiftedLabel( + text = title, + labelText = "PDF", + labelFontSize = HedvigTheme.typography.label.fontSize, + textColor = LocalContentColor.current, + textFontSize = LocalTextStyle.current.fontSize, + ), + ) + } + }, + endSlot = { + Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = HedvigIcons.ArrowNorthEast, + contentDescription = stringResource(Res.string.TALKBACK_OPEN_EXTERNAL_LINK), + modifier = Modifier.size(16.dp), + ) + } + }, + spaceBetween = 8.dp, + ) + } +} + @HedvigPreview @Composable private fun PreviewClaimDetailScreen( diff --git a/app/ui/claim-status/build.gradle.kts b/app/ui/claim-status/build.gradle.kts index 4ccc8d7215..26f86c7139 100644 --- a/app/ui/claim-status/build.gradle.kts +++ b/app/ui/claim-status/build.gradle.kts @@ -14,9 +14,7 @@ dependencies { implementation(libs.jetbrains.compose.ui) implementation(projects.apolloOctopusPublic) implementation(projects.composePagerIndicator) - implementation(projects.composeUi) implementation(projects.coreResources) implementation(projects.coreUiData) - implementation(projects.dataDisplayItems) implementation(projects.designSystemHedvig) } diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt deleted file mode 100644 index d6eae4516e..0000000000 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimDetailSharedComponents.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.hedvig.android.ui.claimstatus - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsBottomHeight -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.hedvig.android.compose.ui.LayoutWithoutPlacement -import com.hedvig.android.compose.ui.stringWithShiftedLabel -import com.hedvig.android.data.display.items.DisplayItem -import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Date -import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.DateTime -import com.hedvig.android.data.display.items.DisplayItem.DisplayItemValue.Text -import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large -import com.hedvig.android.design.system.hedvig.HedvigBottomSheet -import com.hedvig.android.design.system.hedvig.HedvigCard -import com.hedvig.android.design.system.hedvig.HedvigText -import com.hedvig.android.design.system.hedvig.HedvigTextButton -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.HedvigThreeDotsProgressIndicator -import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken -import com.hedvig.android.design.system.hedvig.Icon -import com.hedvig.android.design.system.hedvig.LocalContentColor -import com.hedvig.android.design.system.hedvig.LocalTextStyle -import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState -import com.hedvig.android.design.system.hedvig.icon.ArrowNorthEast -import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.rememberHedvigDateTimeFormatter -import hedvig.resources.MY_DOCUMENTS_INSURANCE_TERMS -import hedvig.resources.Res -import hedvig.resources.TALKBACK_OPEN_EXTERNAL_LINK -import hedvig.resources.claim_status_claim_details_info_text -import hedvig.resources.general_close_button -import org.jetbrains.compose.resources.stringResource - -@Composable -fun ClaimExplanationBottomSheet(sheetState: HedvigBottomSheetState) { - HedvigBottomSheet(sheetState) { _ -> - HedvigText( - text = stringResource(Res.string.claim_status_claim_details_info_text), - modifier = Modifier - .fillMaxWidth(), - ) - Spacer(Modifier.height(32.dp)) - HedvigTextButton( - text = stringResource(Res.string.general_close_button), - buttonSize = Large, - onClick = { sheetState.dismiss() }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(8.dp)) - Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) - } -} - -@Composable -fun ClaimDisplayItemsSection(displayItems: List, modifier: Modifier = Modifier) { - CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { - Column(modifier) { - for (displayItem in displayItems) { - HorizontalItemsWithMaximumSpaceTaken( - spaceBetween = 8.dp, - startSlot = { - HedvigText(text = displayItem.title) - }, - endSlot = { - val formatter = rememberHedvigDateTimeFormatter() - HedvigText( - text = when (val item = displayItem.value) { - is Date -> formatter.format(item.date) - is DateTime -> formatter.format(item.localDateTime) - is Text -> item.text - }, - textAlign = TextAlign.End, - ) - }, - ) - } - } - } -} - -@Composable -fun ClaimTermsConditionsCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { - HedvigCard(onClick = onClick) { - Row( - modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - if (isLoading) { - LayoutWithoutPlacement( - sizeAdjustingContent = { - ClaimDocumentCard( - title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), - ) - }, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - HedvigThreeDotsProgressIndicator() - } - } - } else { - ClaimDocumentCard( - title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), - ) - } - } - } -} - -@Composable -fun ClaimDocumentCard(title: String) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - Column { - HedvigText( - text = stringWithShiftedLabel( - text = title, - labelText = "PDF", - labelFontSize = HedvigTheme.typography.label.fontSize, - textColor = LocalContentColor.current, - textFontSize = LocalTextStyle.current.fontSize, - ), - ) - } - }, - endSlot = { - Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = HedvigIcons.ArrowNorthEast, - contentDescription = stringResource(Res.string.TALKBACK_OPEN_EXTERNAL_LINK), - modifier = Modifier.size(16.dp), - ) - } - }, - spaceBetween = 8.dp, - ) - } -} From 3089a8121ed6086ad14e907bc6a2385b85c6a6a4 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 27 Apr 2026 11:37:53 +0200 Subject: [PATCH 30/30] chore: restore original function ordering in ClaimDetailsDestination to reduce diff noise Co-Authored-By: Claude Opus 4.6 (1M context) --- .../details/ui/ClaimDetailsDestination.kt | 168 +++++++++--------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt index ef358e3c1b..7b7199c1a9 100644 --- a/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt +++ b/app/feature/feature-claim-details/src/main/kotlin/com/hedvig/android/feature/claim/details/ui/ClaimDetailsDestination.kt @@ -379,6 +379,25 @@ private fun NonDynamicGrid( } } +@Composable +private fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { + HedvigBottomSheet(sheetState) { _ -> + HedvigText( + text = stringResource(Res.string.claim_status_claim_details_info_text), + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(32.dp)) + HedvigTextButton( + text = stringResource(Res.string.general_close_button), + buttonSize = Large, + onClick = { sheetState.dismiss() }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(8.dp)) + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } +} + @Composable private fun BeforeGridContent( uiState: ClaimDetailUiState.Content, @@ -582,6 +601,38 @@ private fun AfterGridContent( } } +@Composable +private fun TermsConditionsCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { + HedvigCard(onClick = onClick) { + Row( + modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + if (isLoading) { + LayoutWithoutPlacement( + sizeAdjustingContent = { + DocumentCard( + title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), + ) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + HedvigThreeDotsProgressIndicator() + } + } + } else { + DocumentCard( + title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), + ) + } + } + } +} + @Composable private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { HedvigCard(onClick = onClick) { @@ -614,6 +665,39 @@ private fun AppealInstructionCard(onClick: () -> Unit, isLoading: Boolean, modif } } +@Composable +private fun DocumentCard(title: String) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + HorizontalItemsWithMaximumSpaceTaken( + startSlot = { + Column { + HedvigText( + text = stringWithShiftedLabel( + text = title, + labelText = "PDF", + labelFontSize = HedvigTheme.typography.label.fontSize, + textColor = LocalContentColor.current, + textFontSize = LocalTextStyle.current.fontSize, + ), + ) + } + }, + endSlot = { + Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = HedvigIcons.ArrowNorthEast, + contentDescription = stringResource(Res.string.TALKBACK_OPEN_EXTERNAL_LINK), + modifier = Modifier.size(16.dp), + ) + } + }, + spaceBetween = 8.dp, + ) + } +} + @Composable private fun statusParagraphText( claimStatus: ClaimDetailUiState.Content.ClaimStatus, @@ -672,25 +756,6 @@ private fun ClaimDetailHedvigAudioPlayerItem(signedAudioUrl: SignedAudioUrl, mod } } -@Composable -private fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { - HedvigBottomSheet(sheetState) { _ -> - HedvigText( - text = stringResource(Res.string.claim_status_claim_details_info_text), - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(32.dp)) - HedvigTextButton( - text = stringResource(Res.string.general_close_button), - buttonSize = Large, - onClick = { sheetState.dismiss() }, - modifier = Modifier.fillMaxWidth(), - ) - Spacer(Modifier.height(8.dp)) - Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) - } -} - @Composable private fun DisplayItemsSection(displayItems: List, modifier: Modifier = Modifier) { CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { @@ -718,71 +783,6 @@ private fun DisplayItemsSection(displayItems: List, modifier: Modif } } -@Composable -private fun TermsConditionsCard(onClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier) { - HedvigCard(onClick = onClick) { - Row( - modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - if (isLoading) { - LayoutWithoutPlacement( - sizeAdjustingContent = { - DocumentCard( - title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), - ) - }, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - HedvigThreeDotsProgressIndicator() - } - } - } else { - DocumentCard( - title = stringResource(Res.string.MY_DOCUMENTS_INSURANCE_TERMS), - ) - } - } - } -} - -@Composable -private fun DocumentCard(title: String) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - HorizontalItemsWithMaximumSpaceTaken( - startSlot = { - Column { - HedvigText( - text = stringWithShiftedLabel( - text = title, - labelText = "PDF", - labelFontSize = HedvigTheme.typography.label.fontSize, - textColor = LocalContentColor.current, - textFontSize = LocalTextStyle.current.fontSize, - ), - ) - } - }, - endSlot = { - Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = HedvigIcons.ArrowNorthEast, - contentDescription = stringResource(Res.string.TALKBACK_OPEN_EXTERNAL_LINK), - modifier = Modifier.size(16.dp), - ) - } - }, - spaceBetween = 8.dp, - ) - } -} - @HedvigPreview @Composable private fun PreviewClaimDetailScreen(