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 + } + } +} diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 2dadbf7ab5..3140d94eb5 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.featureConnectPaymentTrustly) implementation(projects.featureCrossSellSheet) implementation(projects.featureDeleteAccount) 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 96db5c7f39..fa12d4c943 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 @@ -396,7 +396,7 @@ internal fun HedvigNavHost( navigateToChipId = { navController.navigate(ChipIdGraphDestination()) }, - languageService = languageService + languageService = languageService, ) cbmChatGraph( hedvigDeepLinkContainer = hedvigDeepLinkContainer, 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..5259a72b41 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 @@ -22,9 +23,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,14 +37,25 @@ internal class GetClaimDetailUiStateUseCase( private val apolloClient: ApolloClient, private val crossSellAfterClaimClosedRepository: CrossSellAfterClaimClosedRepository, ) { - fun invoke(claimId: String): Flow> { - return flow { - while (currentCoroutineContext().isActive) { - val queryFlow = queryFlow(claimId) - emitAll(queryFlow) - delay(POLL_INTERVAL) + fun invoke(claimId: String): Flow> = flow { + // First iteration: try the regular endpoint. A NoClaimFound here means this + // is a partner claim — fall back to the partner endpoint and remember that + // choice for the polling loop below. + var fellBackToPartner = false + queryFlow(claimId).collect { result -> + if (result == Either.Left(Error.NoClaimFound)) { + fellBackToPartner = true + } else { + emit(result) } } + if (fellBackToPartner) emitAll(partnerQueryFlow(claimId)) + + val pollEndpoint = if (fellBackToPartner) ::partnerQueryFlow else ::queryFlow + while (currentCoroutineContext().isActive) { + delay(POLL_INTERVAL) + emitAll(pollEndpoint(claimId)) + } } private fun queryFlow(claimId: String): Flow> { @@ -60,6 +75,65 @@ 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) + ?: 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) + }, + externalId = claim.externalId, + handlerEmail = claim.handlerEmail, + isPartnerClaim = true, + ) + } + private fun ClaimDetailUiState.Content.Companion.fromClaim( claim: ClaimFragment, conversationId: String?, @@ -146,6 +220,9 @@ internal class GetClaimDetailUiStateUseCase( displayItems = claim.displayItems.map { DisplayItem.fromStrings(it.displayTitle, it.displayValue) }, + externalId = null, + handlerEmail = null, + isPartnerClaim = false, ) } 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..934595ccd3 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 @@ -1,6 +1,10 @@ package com.hedvig.android.feature.claim.details.ui +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.net.Uri +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -34,10 +38,12 @@ 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.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.core.content.getSystemService import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.ImageLoader import com.eygraber.uri.toAndroidUri @@ -110,6 +116,8 @@ 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_external_id +import hedvig.resources.claim_status_claim_details_handler_email 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 @@ -120,10 +128,12 @@ import hedvig.resources.claim_status_detail_uploaded_files_info_title import hedvig.resources.claim_status_not_compensated_support_text import hedvig.resources.claim_status_not_covered_support_text import hedvig.resources.claim_status_paid_support_text_short +import hedvig.resources.claim_status_partner_support_text 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.referrals_active__toast_text import hedvig.resources.something_went_wrong import hedvig.resources.travel_certificate_downloading_error import java.io.File @@ -380,12 +390,11 @@ private fun NonDynamicGrid( } @Composable -internal fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { +private fun ExplanationBottomSheet(sheetState: HedvigBottomSheetState) { HedvigBottomSheet(sheetState) { _ -> HedvigText( text = stringResource(Res.string.claim_status_claim_details_info_text), - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(32.dp)) HedvigTextButton( @@ -423,7 +432,7 @@ private fun BeforeGridContent( Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { if (!uiState.claimIsInUndeterminedState) { HedvigText( - text = statusParagraphText(uiState.claimStatus, uiState.claimOutcome), + text = statusParagraphText(uiState.claimStatus, uiState.claimOutcome, uiState.isPartnerClaim), style = HedvigTheme.typography.bodySmall, ) } @@ -504,29 +513,38 @@ 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), + PartnerInfoRows( + externalId = uiState.externalId, + handlerEmail = uiState.handlerEmail, + modifier = Modifier + .fillMaxWidth() + .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 @@ -701,6 +719,18 @@ private fun DocumentCard(title: String) { private fun statusParagraphText( claimStatus: ClaimDetailUiState.Content.ClaimStatus, claimOutcome: ClaimDetailUiState.Content.ClaimOutcome, + isPartnerClaim: Boolean, +): String { + if (isPartnerClaim && claimStatus != ClaimDetailUiState.Content.ClaimStatus.CLOSED) { + return stringResource(Res.string.claim_status_partner_support_text) + } + return regularStatusParagraphText(claimStatus, claimOutcome) +} + +@Composable +private fun regularStatusParagraphText( + claimStatus: ClaimDetailUiState.Content.ClaimStatus, + claimOutcome: ClaimDetailUiState.Content.ClaimOutcome, ): String = when (claimStatus) { ClaimDetailUiState.Content.ClaimStatus.CREATED -> { stringResource(Res.string.claim_status_submitted_support_text) @@ -755,6 +785,53 @@ private fun ClaimDetailHedvigAudioPlayerItem(signedAudioUrl: SignedAudioUrl, mod } } +@Composable +private fun PartnerInfoRows(externalId: String?, handlerEmail: String?, modifier: Modifier = Modifier) { + if (externalId.isNullOrBlank() && handlerEmail.isNullOrBlank()) return + val context = LocalContext.current + val copiedToast = stringResource(Res.string.referrals_active__toast_text) + CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { + Column(modifier) { + if (!externalId.isNullOrBlank()) { + CopyableRow( + title = stringResource(Res.string.claim_status_claim_details_external_id), + value = externalId, + onCopy = { context.copyToClipboardAndShowToast(externalId, copiedToast) }, + ) + } + if (!handlerEmail.isNullOrBlank()) { + CopyableRow( + title = stringResource(Res.string.claim_status_claim_details_handler_email), + value = handlerEmail, + onCopy = { context.copyToClipboardAndShowToast(handlerEmail, copiedToast) }, + ) + } + } + } +} + +@Composable +private fun CopyableRow(title: String, value: String, onCopy: () -> Unit) { + HorizontalItemsWithMaximumSpaceTaken( + spaceBetween = 8.dp, + modifier = Modifier.clickable(onClick = onCopy), + startSlot = { + HedvigText(text = title) + }, + endSlot = { + HedvigText( + text = value, + textAlign = TextAlign.End, + ) + }, + ) +} + +private fun Context.copyToClipboardAndShowToast(text: String, toast: String) { + getSystemService()?.setPrimaryClip(ClipData.newPlainText(null, text)) + Toast.makeText(this, toast, Toast.LENGTH_SHORT).show() +} + @Composable private fun DisplayItemsSection(displayItems: List, modifier: Modifier = Modifier) { CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { @@ -888,6 +965,9 @@ private fun PreviewClaimDetailScreen( DisplayItem("Type", Text("Respiratory disorder")), DisplayItem("Submitted", Text("2025-02-03")), ), + externalId = "EIR-2026-000123", + handlerEmail = "claims@eir.se", + isPartnerClaim = true, ), openUrl = {}, imageLoader = rememberPreviewImageLoader(), 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..e54c05c5a2 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 @@ -37,7 +37,12 @@ internal class ClaimDetailsViewModel( downloadPdfUseCase: DownloadPdfUseCase, ) : MoleculeViewModel( ClaimDetailUiState.Loading, - ClaimDetailPresenter(claimId, getClaimDetailUiStateUseCase, claimsServiceUploadFileUseCase, downloadPdfUseCase), + ClaimDetailPresenter( + claimId, + getClaimDetailUiStateUseCase, + claimsServiceUploadFileUseCase, + downloadPdfUseCase, + ), ) private class ClaimDetailPresenter( @@ -191,6 +196,9 @@ internal sealed interface ClaimDetailUiState { val isUploadingFilesEnabled: Boolean, val infoText: String?, val displayItems: List, + val externalId: String?, + val handlerEmail: String?, + val isPartnerClaim: Boolean, ) : ClaimDetailUiState { val claimIsInUndeterminedState: Boolean = claimStatus == CLOSED && claimOutcome == UNKNOWN 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..8842a7b1b0 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 @@ -108,7 +108,10 @@ private fun ClaimHistoryScreen( .fillMaxWidth(), ) - is ClaimHistoryUiState.Content -> ClaimHistoryContent(uiState, navigateToClaimDetails) + is ClaimHistoryUiState.Content -> ClaimHistoryContent( + uiState, + navigateToClaimDetails, + ) } } } @@ -134,18 +137,17 @@ private fun ClaimHistoryItem(index: Int, claim: ClaimHistory, navigateToClaimDet text = claim.claimType ?: stringResource(Res.string.CHAT_CONVERSATION_CLAIM_TITLE), style = HedvigTheme.typography.bodySmall, ) - HedvigText( - buildString { - append(stringResource(Res.string.claim_status_claim_details_submitted)) - append(" ") - append( - hedvigDateTimeFormatter.format( - claim.submittedAt.toLocalDateTime(TimeZone.currentSystemDefault()), - ), - ) - }, - style = HedvigTheme.typography.label.copy(color = HedvigTheme.colorScheme.textSecondary), - ) + val submittedAt = claim.submittedAt + if (submittedAt != null) { + HedvigText( + buildString { + append(stringResource(Res.string.claim_status_claim_details_submitted)) + append(" ") + append(hedvigDateTimeFormatter.format(submittedAt.toLocalDateTime(TimeZone.currentSystemDefault()))) + }, + style = HedvigTheme.typography.label.copy(color = HedvigTheme.colorScheme.textSecondary), + ) + } } }, { @@ -213,7 +215,7 @@ private class ClaimHistoryUiStateCollectionPreviewParameterProvider : id = it.toString(), claimType = "$it", outcome = ClaimHistory.ClaimOutcome.entries[it], - submittedAt = Instant.fromEpochMilliseconds(100), + submittedAt = if (it == 0) null else Instant.fromEpochMilliseconds(100), ) }.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..b35dbfd304 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,7 +11,7 @@ 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: (claimId: String) -> Unit) { navdestination { ClaimHistoryDestination( claimHistoryViewModel = koinViewModel(), 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..da01233aad 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 @@ -9,6 +9,8 @@ import com.hedvig.android.core.common.ErrorMessage 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 +23,30 @@ 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 + }, + ) + } + val partnerClaims = data.currentMember.partnerClaimsHistory.map { history -> + ClaimHistory( + id = history.id, + claimType = history.claimType, + submittedAt = history.submittedAt?.atStartOfDayIn(TimeZone.UTC), + outcome = ClaimHistory.ClaimOutcome.UNKNOWN, + ) + } + (regularClaims + partnerClaims).sortedWith(compareByDescending(nullsLast()) { it.submittedAt }) } } } @@ -54,8 +56,8 @@ internal data class ClaimHistory( val id: String, // Title, of fall back to "Claim" val claimType: String?, - // Subtitle uses this date to show when the claim was submitted - val submittedAt: Instant, + // Subtitle uses this date to show when the claim was submitted; null for partner claims with no submission date. + val submittedAt: Instant?, val outcome: ClaimOutcome?, ) { enum class ClaimOutcome { 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..9e911b1ca5 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,18 @@ 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.orEmpty().map(ClaimStatusCardUiState::fromClaimStatusCardsQuery) + + this.currentMember.claimsActive.orEmpty().map(ClaimStatusCardUiState::fromClaimStatusCardsQuery) + val partnerCards = this.currentMember.partnerClaimsActive.map(ClaimStatusCardUiState::fromPartnerClaim) + + val allCards = (regularCards + partnerCards) + .sortedWith(compareByDescending(nullsLast()) { it.submittedDate }) + .toNonEmptyListOrNull() ?: return null + + return HomeData.ClaimStatusCardsData( + claimStatusCardsUiState = allCards, + ) } internal data class HomeData( 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 8095f75c6a..4192a829aa 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,7 +156,7 @@ internal fun HomeDestination( onNavigateToNewConversation: () -> Unit, navigateToClaimChat: () -> Unit, navigateToClaimChatInDevMode: () -> Unit, - onClaimDetailCardClicked: (String) -> Unit, + onClaimDetailCardClicked: (claimId: String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToConnectPayout: () -> Unit, navigateToHelpCenter: () -> Unit, @@ -208,7 +208,7 @@ private fun HomeScreen( onNavigateToNewConversation: () -> Unit, navigateToClaimChat: () -> Unit, navigateToClaimChatInDevMode: () -> Unit, - onClaimDetailCardClicked: (String) -> Unit, + onClaimDetailCardClicked: (claimId: String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToConnectPayout: () -> Unit, navigateToHelpCenter: () -> Unit, diff --git a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimStatusCard.kt b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimStatusCard.kt index fa94b32b66..24563a0e1d 100644 --- a/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimStatusCard.kt +++ b/app/ui/claim-status/src/main/kotlin/com/hedvig/android/ui/claimstatus/ClaimStatusCard.kt @@ -94,24 +94,30 @@ fun ClaimStatusCardContent(uiState: ClaimStatusCardUiState, modifier: Modifier = style = HedvigTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 2.dp), ) - val subtext = if (uiState.insuranceDisplayName != null) { - uiState.insuranceDisplayName - } else { - val formattedDate = HedvigDateTimeFormatterDefaults - .dateMonthAndYear(getLocale()) - .format( - uiState.submittedDate.toLocalDateTime( - TimeZone.currentSystemDefault(), - ), - ) - "${stringResource(Res.string.claim_status_detail_submitted)} $formattedDate" + val subtext = when { + uiState.insuranceDisplayName != null -> { + uiState.insuranceDisplayName + } + + uiState.submittedDate != null -> { + val formattedDate = HedvigDateTimeFormatterDefaults + .dateMonthAndYear(getLocale()) + .format(uiState.submittedDate.toLocalDateTime(TimeZone.currentSystemDefault())) + "${stringResource(Res.string.claim_status_detail_submitted)} $formattedDate" + } + + else -> { + null + } + } + if (subtext != null) { + HedvigText( + text = subtext, + style = HedvigTheme.typography.label, + color = HedvigTheme.colorScheme.textSecondary, + modifier = Modifier.padding(horizontal = 2.dp), + ) } - HedvigText( - text = subtext, - style = HedvigTheme.typography.label, - color = HedvigTheme.colorScheme.textSecondary, - modifier = Modifier.padding(horizontal = 2.dp), - ) Spacer(Modifier.height(18.dp)) ClaimProgressRow(claimProgressItemsUiState = uiState.claimProgressItemsUiState) } 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..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 @@ -25,6 +25,19 @@ 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..c3fd46a2a7 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,17 +1,32 @@ package com.hedvig.android.ui.claimstatus.model 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, val claimType: String?, val insuranceDisplayName: String?, - val submittedDate: Instant, + // Null for partner claims with no submission date returned by the BE. + val submittedDate: Instant?, val pillTypes: List, val claimProgressItemsUiState: List, ) { companion object { + fun fromPartnerClaim(claim: PartnerClaimFragment): ClaimStatusCardUiState { + return ClaimStatusCardUiState( + id = claim.id, + claimType = claim.claimType, + insuranceDisplayName = null, + submittedDate = claim.submittedAt?.atStartOfDayIn(TimeZone.UTC), + pillTypes = ClaimPillType.fromPartnerClaim(claim.status), + claimProgressItemsUiState = ClaimProgressSegment.fromPartnerClaim(claim.status), + ) + } + fun fromClaimStatusCardsQuery(claim: ClaimFragment): ClaimStatusCardUiState { return ClaimStatusCardUiState( id = claim.id,