diff --git a/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql b/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql index 971cf5639e..b9c20fbf37 100644 --- a/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql +++ b/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql @@ -116,7 +116,29 @@ fragment SummaryFragment on ClaimIntentStepContentSummary { contentType fileName } - freeTexts + keyDetails { + title + value + } + answers { + title + value { + ... on ClaimIntentStepContentSummaryAnswerText { + text + } + ... on ClaimIntentStepContentSummaryAnswerAudio { + url + transcript + } + ... on ClaimIntentStepContentSummaryAnswerFiles { + files { + url + contentType + fileName + } + } + } + } } fragment DeflectionFragment on ClaimIntentStepContentDeflection { diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt index e6acf85fd5..06bf89204b 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntent.kt @@ -155,7 +155,8 @@ internal sealed interface StepContent { val items: List, val audioRecordings: List, val fileUploads: List, - val freeTexts: List, + val keyDetails: List, + val answers: List, ) : StepContent { override val isSkippable: Boolean = false @@ -164,6 +165,16 @@ internal sealed interface StepContent { data class AudioRecording(val url: String) data class FileUpload(val url: String, val contentType: String, val fileName: String) + + data class Answer(val title: String, val value: Value) { + sealed interface Value { + data class Text(val text: String) : Value + + data class Audio(val url: String, val transcript: String?) : Value + + data class Files(val files: List) : Value + } + } } @Serializable diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt index b771df9794..3636262559 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ClaimIntentExt.kt @@ -6,6 +6,7 @@ import com.hedvig.android.core.common.ErrorMessage import com.hedvig.android.core.locale.CommonLocale import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.design.system.hedvig.DatePickerUiState +import com.hedvig.android.logger.LogPriority import com.hedvig.android.logger.logcat import com.hedvig.android.shared.partners.deflect.DeflectData import kotlinx.datetime.LocalDate @@ -177,7 +178,34 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): it.fileName, ) }, - freeTexts = freeTexts, + keyDetails = keyDetails.map { StepContent.Summary.Item(it.title, it.value) }, + // An answer value type this version can't render is dropped rather than raised on, so the + // rest of the summary still shows. + answers = answers.mapNotNull { answer -> + val value = when (val value = answer.value) { + is SummaryFragment.Answer.ClaimIntentStepContentSummaryAnswerTextValue -> { + StepContent.Summary.Answer.Value.Text(value.text) + } + + is SummaryFragment.Answer.ClaimIntentStepContentSummaryAnswerAudioValue -> { + StepContent.Summary.Answer.Value.Audio(value.url, value.transcript) + } + + is SummaryFragment.Answer.ClaimIntentStepContentSummaryAnswerFilesValue -> { + StepContent.Summary.Answer.Value.Files( + value.files.map { + StepContent.Summary.FileUpload(it.url, it.contentType, it.fileName) + }, + ) + } + + else -> { + logcat(LogPriority.WARN) { "SummaryFragment.Answer: Unknown answer value type, skipping answer" } + null + } + } ?: return@mapNotNull null + StepContent.Summary.Answer(title = answer.title, value = value) + }, ) } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt index cb0b2f6e53..11feef0156 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatDestination.kt @@ -762,8 +762,9 @@ private fun StepTopContent( Spacer(Modifier.height(16.dp)) if (stepItem.stepContent is StepContent.Summary) { ChatClaimSummaryTopContent( + keyDetails = stepItem.stepContent.keyDetails.ifEmpty { stepItem.stepContent.items }, + answers = stepItem.stepContent.answers, recordingUrls = stepItem.stepContent.audioRecordings.map { it.url }, - displayItems = stepItem.stepContent.items.map { (title, value) -> title to value }, onNavigateToImageViewer = onNavigateToImageViewer, imageLoader = imageLoader, fileUploads = stepItem.stepContent.fileUploads.map { @@ -775,7 +776,6 @@ private fun StepTopContent( id = it.url, ) }, - freeTexts = stepItem.stepContent.freeTexts, ) } } diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/ChatClaimSummaryStep.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/ChatClaimSummaryStep.kt index 06e5cfae63..7a0ebfd6a4 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/ChatClaimSummaryStep.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/ChatClaimSummaryStep.kt @@ -22,7 +22,10 @@ import coil3.ImageLoader import com.hedvig.android.audio.player.HedvigAudioPlayer import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer import com.hedvig.android.core.uidata.UiFile +import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.HedvigButtonGhostWithBorder import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigText @@ -30,16 +33,19 @@ import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.LocalContentColor import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.rememberHedvigBottomSheetState import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.audio.player.data.SignedAudioUrl +import com.hedvig.feature.claim.chat.data.StepContent import com.hedvig.feature.claim.chat.ui.common.FilesRow -import com.hedvig.feature.claim.chat.ui.common.RoundCornersPill import hedvig.resources.CLAIM_CHAT_FILE_TITLE import hedvig.resources.CLAIM_CHAT_RECORDING_TITLE import hedvig.resources.EMBARK_SUBMIT_CLAIM import hedvig.resources.Res import hedvig.resources.claim_status_claim_details_title +import hedvig.resources.claim_status_show_all_answers +import hedvig.resources.general_close_button import org.jetbrains.compose.resources.stringResource @Composable @@ -64,14 +70,23 @@ internal fun ChatClaimSummaryBottomContent( @Composable internal fun ChatClaimSummaryTopContent( + keyDetails: List, + answers: List, recordingUrls: List, fileUploads: List, - freeTexts: List, - displayItems: List>, imageLoader: ImageLoader, onNavigateToImageViewer: (imageUrl: String, cacheKey: String) -> Unit, modifier: Modifier = Modifier, ) { + val answersSheetState = rememberHedvigBottomSheetState>() + HedvigBottomSheet(answersSheetState) { sheetAnswers -> + ClaimSummaryAnswersContent( + answers = sheetAnswers, + imageLoader = imageLoader, + onNavigateToImageViewer = onNavigateToImageViewer, + onClose = { answersSheetState.dismiss() }, + ) + } Column(modifier) { HedvigCard( color = HedvigTheme.colorScheme.fillNegative, @@ -87,7 +102,7 @@ internal fun ChatClaimSummaryTopContent( ), ) { Column(Modifier.padding(vertical = 16.dp)) { - if (displayItems.isNotEmpty()) { + if (keyDetails.isNotEmpty()) { HedvigText( stringResource(Res.string.claim_status_claim_details_title), modifier = Modifier.padding(horizontal = 16.dp).semantics { @@ -97,15 +112,15 @@ internal fun ChatClaimSummaryTopContent( Spacer(Modifier.height(8.dp)) CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { Column(Modifier.padding(horizontal = 16.dp)) { - for (displayItem in displayItems) { + for (keyDetail in keyDetails) { HorizontalItemsWithMaximumSpaceTaken( spaceBetween = 8.dp, startSlot = { - HedvigText(text = displayItem.first) + HedvigText(text = keyDetail.title) }, endSlot = { HedvigText( - text = displayItem.second, + text = keyDetail.value, textAlign = TextAlign.End, ) }, @@ -115,6 +130,14 @@ internal fun ChatClaimSummaryTopContent( } } } + if (answers.isNotEmpty()) { + Spacer(Modifier.height(16.dp)) + HedvigButtonGhostWithBorder( + text = stringResource(Res.string.claim_status_show_all_answers), + onClick = { answersSheetState.show(answers) }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) + } if (recordingUrls.isNotEmpty()) { Spacer(Modifier.height(24.dp)) HedvigText( @@ -134,23 +157,6 @@ internal fun ChatClaimSummaryTopContent( } } } - if (freeTexts.isNotEmpty()) { - Column( - Modifier.padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - freeTexts.forEach { string -> - Spacer(Modifier.height(24.dp)) - RoundCornersPill( - modifier = Modifier.fillMaxWidth(), - onClick = null, - isSelected = false, - ) { - HedvigText(string) - } - } - } - } if (fileUploads.isNotEmpty()) { Spacer(Modifier.height(24.dp)) HedvigText( @@ -172,6 +178,87 @@ internal fun ChatClaimSummaryTopContent( } } +@Composable +internal fun ClaimSummaryAnswersContent( + answers: List, + imageLoader: ImageLoader, + onNavigateToImageViewer: (imageUrl: String, cacheKey: String) -> Unit, + onClose: () -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier.fillMaxWidth()) { + Spacer(Modifier.height(16.dp)) + HedvigText( + stringResource(Res.string.claim_status_claim_details_title), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth().semantics { heading() }, + ) + Spacer(Modifier.height(24.dp)) + answers.forEachIndexed { index, answer -> + HedvigText(text = answer.title) + Spacer(Modifier.height(4.dp)) + CompositionLocalProvider(LocalContentColor provides HedvigTheme.colorScheme.textSecondary) { + AnswerValue( + value = answer.value, + imageLoader = imageLoader, + onNavigateToImageViewer = onNavigateToImageViewer, + ) + } + if (index != answers.lastIndex) { + Spacer(Modifier.height(24.dp)) + } + } + Spacer(Modifier.height(32.dp)) + HedvigButton( + text = stringResource(Res.string.general_close_button), + onClick = onClose, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun AnswerValue( + value: StepContent.Summary.Answer.Value, + imageLoader: ImageLoader, + onNavigateToImageViewer: (imageUrl: String, cacheKey: String) -> Unit, +) { + when (value) { + is StepContent.Summary.Answer.Value.Text -> { + HedvigText(text = value.text) + } + + is StepContent.Summary.Answer.Value.Audio -> { + val audioPlayer = rememberAudioPlayer( + PlayableAudioSource.RemoteUrl( + SignedAudioUrl.fromSignedAudioUrlString(value.url), + ), + ) + HedvigAudioPlayer(audioPlayer = audioPlayer) + } + + is StepContent.Summary.Answer.Value.Files -> { + FilesRow( + uiFiles = value.files.map { + UiFile( + name = it.fileName, + localPath = null, + url = it.url, + mimeType = it.contentType, + id = it.url, + ) + }, + imageLoader = imageLoader, + onNavigateToImageViewer = onNavigateToImageViewer, + onRemoveFile = null, + alignment = Alignment.Start, + ) + } + } +} + @HedvigPreview @Composable private fun PreviewSummaryTopContent() { @@ -184,17 +271,56 @@ private fun PreviewSummaryTopContent() { modifier = Modifier.padding(horizontal = 16.dp), ) { ChatClaimSummaryTopContent( - recordingUrls = listOf("", ""), - displayItems = listOf( - "Locked" to "Yes", - "Electric bike" to "Yes", + keyDetails = listOf( + StepContent.Summary.Item("Type of claim", "Theft"), + StepContent.Summary.Item("Date", "2026-07-13"), + StepContent.Summary.Item("Location", "Stockholm"), + ), + answers = previewAnswers(), + recordingUrls = listOf(""), + fileUploads = listOf( + UiFile("receipt.pdf", null, "https://example.com/receipt.pdf", "application/pdf", "file-1"), ), - fileUploads = listOf(), imageLoader = rememberPreviewImageLoader(), onNavigateToImageViewer = { _, _ -> }, - freeTexts = listOf("A quite short text short text short text short text"), ) } } } } + +@HedvigPreview +@Composable +private fun PreviewSummaryAnswersContent() { + HedvigTheme { + Surface( + color = HedvigTheme.colorScheme.backgroundPrimary, + ) { + ClaimSummaryAnswersContent( + answers = previewAnswers(), + imageLoader = rememberPreviewImageLoader(), + onNavigateToImageViewer = { _, _ -> }, + onClose = {}, + modifier = Modifier.padding(16.dp), + ) + } + } +} + +internal fun previewAnswers(): List = listOf( + StepContent.Summary.Answer( + title = "Was the bike locked?", + value = StepContent.Summary.Answer.Value.Text("No"), + ), + StepContent.Summary.Answer( + title = "Where did it happen?", + value = StepContent.Summary.Answer.Value.Text("Outside the central station in Stockholm"), + ), + StepContent.Summary.Answer( + title = "Describe what happened", + value = StepContent.Summary.Answer.Value.Audio( + url = "", + transcript = "I parked my bike and when I came back it was gone.", + ), + ), +)