From e7b75a1643a296a687c77f6085b12df33ea1b78b Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 13 Jul 2026 13:58:13 +0200 Subject: [PATCH 1/8] feat: add keyDetails and answers to claim-intent summary data layer Backend adds structured keyDetails/answers (Text/Audio/Files union) to ClaimIntentStepContentSummary. Hand-add the identical SDL to the local schema (dev router doesn't have it yet), extend SummaryFragment, StepContent.Summary, and the union mapping so the data layer is ready for the UI to consume. Co-Authored-By: Claude Fable 5 --- .../graphql/FragmentClaimIntent.graphql | 23 +++++++++++++++ .../feature/claim/chat/data/ClaimIntent.kt | 12 ++++++++ .../feature/claim/chat/data/ClaimIntentExt.kt | 28 +++++++++++++++++++ 3 files changed, 63 insertions(+) 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..255006dbe4 100644 --- a/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql +++ b/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql @@ -117,6 +117,29 @@ fragment SummaryFragment on ClaimIntentStepContentSummary { 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..d389c3fe2b 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 @@ -156,6 +156,8 @@ internal sealed interface StepContent { val audioRecordings: List, val fileUploads: List, val freeTexts: List, + val keyDetails: List, + val answers: List, ) : StepContent { override val isSkippable: Boolean = false @@ -164,6 +166,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..0fb2953ef2 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 @@ -178,6 +178,34 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): ) }, freeTexts = freeTexts, + keyDetails = keyDetails.map { StepContent.Summary.Item(it.title, it.value) }, + answers = answers.map { answer -> + StepContent.Summary.Answer( + title = answer.title, + 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 { "SummaryFragment.Answer: Unknown answer value type" } + raise(ClaimChatErrorMessage.NeedsUpdate) + } + }, + ) + }, ) } From 94ffc8e3c55df3561f7ef67fa08802cedcabe155 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 13 Jul 2026 14:14:23 +0200 Subject: [PATCH 2/8] feat: redesign claim-intent summary with key details and answers sheet Render the collapsed "Claim details" card from the structured keyDetails, add a "Show all answers" control that opens a HedvigBottomSheet listing each answer (text / inline audio player / files), and keep the existing Recording and Uploaded files sections. Falls back to items when keyDetails is empty so older backend responses do not regress. Adds a jvmTest ImageComposeScene render harness for PNG verification (KMP modules are unsupported by the AGP screenshot plugin) and a no-op JVM CommonMediaPlayer so audio-backed composables render headlessly. Co-Authored-By: Claude Fable 5 --- .../claim/chat/ui/ClaimChatDestination.kt | 4 +- .../chat/ui/step/ChatClaimSummaryStep.kt | 176 +++++++++++++++--- .../chat/ui/step/ClaimSummaryRenderTest.kt | 84 +++++++++ 3 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt 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..5449775286 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,24 +22,28 @@ 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.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard 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.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 org.jetbrains.compose.resources.stringResource @Composable @@ -64,14 +68,22 @@ 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, + ) + } Column(modifier) { HedvigCard( color = HedvigTheme.colorScheme.fillNegative, @@ -87,7 +99,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 +109,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 +127,14 @@ internal fun ChatClaimSummaryTopContent( } } } + if (answers.isNotEmpty()) { + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + 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 +154,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 +175,81 @@ internal fun ChatClaimSummaryTopContent( } } +@Composable +internal fun ClaimSummaryAnswersContent( + answers: List, + imageLoader: ImageLoader, + onNavigateToImageViewer: (imageUrl: String, cacheKey: String) -> Unit, + modifier: Modifier = Modifier, +) { + Column(modifier.fillMaxWidth()) { + 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)) + } + } + } +} + +@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) + value.transcript?.let { transcript -> + Spacer(Modifier.height(8.dp)) + HedvigText(text = transcript) + } + } + + 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 +262,55 @@ 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 = { _, _ -> }, + 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.", + ), + ), +) diff --git a/app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt b/app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt new file mode 100644 index 0000000000..2c8498ddef --- /dev/null +++ b/app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt @@ -0,0 +1,84 @@ +package com.hedvig.feature.claim.chat.ui.step + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.ImageComposeScene +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import com.hedvig.android.core.uidata.UiFile +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.Surface +import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader +import com.hedvig.feature.claim.chat.data.StepContent +import java.io.File +import kotlin.test.Test +import org.jetbrains.skia.EncodedImageFormat + +class ClaimSummaryRenderTest { + @Test + fun renderCollapsedClaimDetailsCard() { + renderToPng("claim-summary-collapsed-card", width = 1080, height = 1500) { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + Column(Modifier.padding(16.dp)) { + ChatClaimSummaryTopContent( + 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"), + ), + imageLoader = rememberPreviewImageLoader(), + onNavigateToImageViewer = { _, _ -> }, + ) + } + } + } + } + + @Test + fun renderExpandedAnswersContent() { + renderToPng("claim-summary-expanded-answers", width = 1080, height = 1200) { + Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { + ClaimSummaryAnswersContent( + answers = previewAnswers(), + imageLoader = rememberPreviewImageLoader(), + onNavigateToImageViewer = { _, _ -> }, + modifier = Modifier.padding(16.dp), + ) + } + } + } + + private fun renderToPng( + name: String, + width: Int, + height: Int, + content: @Composable () -> Unit, + ) { + val scene = ImageComposeScene( + width = width, + height = height, + density = Density(3f), + ) + val bytes = try { + scene.setContent { + HedvigTheme { + content() + } + } + scene.render().encodeToData(EncodedImageFormat.PNG)!!.bytes + } finally { + scene.close() + } + val outDir = File("build/renders").apply { mkdirs() } + val outFile = File(outDir, "$name.png") + outFile.writeBytes(bytes) + println("RENDERED_PNG: ${outFile.absolutePath} (${bytes.size} bytes)") + } +} From 981c9e9991826d1647905d5a6632d467e6f32151 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 13:54:15 +0200 Subject: [PATCH 3/8] Remove ClaimSummaryRenderTest Co-Authored-By: Claude Opus 4.8 --- .../chat/ui/step/ClaimSummaryRenderTest.kt | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt diff --git a/app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt b/app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt deleted file mode 100644 index 2c8498ddef..0000000000 --- a/app/feature/feature-claim-chat/src/jvmTest/kotlin/com/hedvig/feature/claim/chat/ui/step/ClaimSummaryRenderTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.hedvig.feature.claim.chat.ui.step - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.ImageComposeScene -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp -import com.hedvig.android.core.uidata.UiFile -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.rememberPreviewImageLoader -import com.hedvig.feature.claim.chat.data.StepContent -import java.io.File -import kotlin.test.Test -import org.jetbrains.skia.EncodedImageFormat - -class ClaimSummaryRenderTest { - @Test - fun renderCollapsedClaimDetailsCard() { - renderToPng("claim-summary-collapsed-card", width = 1080, height = 1500) { - Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - Column(Modifier.padding(16.dp)) { - ChatClaimSummaryTopContent( - 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"), - ), - imageLoader = rememberPreviewImageLoader(), - onNavigateToImageViewer = { _, _ -> }, - ) - } - } - } - } - - @Test - fun renderExpandedAnswersContent() { - renderToPng("claim-summary-expanded-answers", width = 1080, height = 1200) { - Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { - ClaimSummaryAnswersContent( - answers = previewAnswers(), - imageLoader = rememberPreviewImageLoader(), - onNavigateToImageViewer = { _, _ -> }, - modifier = Modifier.padding(16.dp), - ) - } - } - } - - private fun renderToPng( - name: String, - width: Int, - height: Int, - content: @Composable () -> Unit, - ) { - val scene = ImageComposeScene( - width = width, - height = height, - density = Density(3f), - ) - val bytes = try { - scene.setContent { - HedvigTheme { - content() - } - } - scene.render().encodeToData(EncodedImageFormat.PNG)!!.bytes - } finally { - scene.close() - } - val outDir = File("build/renders").apply { mkdirs() } - val outFile = File(outDir, "$name.png") - outFile.writeBytes(bytes) - println("RENDERED_PNG: ${outFile.absolutePath} (${bytes.size} bytes)") - } -} From f9dba878a6b547c31bce278cf9f4dcf4e52322ac Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 14:02:40 +0200 Subject: [PATCH 4/8] chore: polish claim summary answers UI Outline the show-all-answers button, hide the audio transcript, and use 16dp top spacing above the button. Co-Authored-By: Claude Fable 5 --- .../feature/claim/chat/ui/step/ChatClaimSummaryStep.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) 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 5449775286..4038f907df 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 @@ -24,10 +24,10 @@ import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer import com.hedvig.android.core.uidata.UiFile 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 -import com.hedvig.android.design.system.hedvig.HedvigTextButton import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.LocalContentColor @@ -128,8 +128,8 @@ internal fun ChatClaimSummaryTopContent( } } if (answers.isNotEmpty()) { - Spacer(Modifier.height(8.dp)) - HedvigTextButton( + 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), @@ -224,10 +224,6 @@ private fun AnswerValue( ), ) HedvigAudioPlayer(audioPlayer = audioPlayer) - value.transcript?.let { transcript -> - Spacer(Modifier.height(8.dp)) - HedvigText(text = transcript) - } } is StepContent.Summary.Answer.Value.Files -> { From 8f2136c211a5aff8aad4cb613325b881d6f2a292 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 14:31:18 +0200 Subject: [PATCH 5/8] chore: add close button to claim summary answers sheet Add a full-width secondary Close button at the bottom of the answers sheet (dismisses it), and give the header title more top spacing. Co-Authored-By: Claude Fable 5 --- .../claim/chat/ui/step/ChatClaimSummaryStep.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 4038f907df..1ae909ad07 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 @@ -24,6 +24,7 @@ import com.hedvig.android.audio.player.audioplayer.rememberAudioPlayer import com.hedvig.android.core.uidata.UiFile import com.hedvig.android.design.system.hedvig.HedvigBottomSheet import com.hedvig.android.design.system.hedvig.HedvigButton +import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigButtonGhostWithBorder import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview @@ -44,6 +45,7 @@ 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 @@ -82,6 +84,7 @@ internal fun ChatClaimSummaryTopContent( answers = sheetAnswers, imageLoader = imageLoader, onNavigateToImageViewer = onNavigateToImageViewer, + onClose = { answersSheetState.dismiss() }, ) } Column(modifier) { @@ -180,9 +183,11 @@ 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, @@ -203,6 +208,14 @@ internal fun ClaimSummaryAnswersContent( Spacer(Modifier.height(24.dp)) } } + Spacer(Modifier.height(24.dp)) + HedvigButton( + text = stringResource(Res.string.general_close_button), + onClick = onClose, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Secondary, + modifier = Modifier.fillMaxWidth(), + ) } } @@ -287,6 +300,7 @@ private fun PreviewSummaryAnswersContent() { answers = previewAnswers(), imageLoader = rememberPreviewImageLoader(), onNavigateToImageViewer = { _, _ -> }, + onClose = {}, modifier = Modifier.padding(16.dp), ) } From 2b98bb13fcbc953784ae63bad79fa9a7c356664c Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 14:59:32 +0200 Subject: [PATCH 6/8] chore: increase spacing before close button in answers sheet Co-Authored-By: Claude Fable 5 --- .../hedvig/feature/claim/chat/ui/step/ChatClaimSummaryStep.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1ae909ad07..adb3b9c251 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 @@ -208,7 +208,7 @@ internal fun ClaimSummaryAnswersContent( Spacer(Modifier.height(24.dp)) } } - Spacer(Modifier.height(24.dp)) + Spacer(Modifier.height(32.dp)) HedvigButton( text = stringResource(Res.string.general_close_button), onClick = onClose, From ab3fb5cb5f9baaf7e7183367d4874644feb0c9c8 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Wed, 29 Jul 2026 10:47:17 +0200 Subject: [PATCH 7/8] chore: fix import ordering in ChatClaimSummaryStep Co-Authored-By: Claude Opus 5 --- .../hedvig/feature/claim/chat/ui/step/ChatClaimSummaryStep.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 adb3b9c251..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,9 +22,9 @@ 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.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigButtonGhostWithBorder import com.hedvig.android.design.system.hedvig.HedvigCard import com.hedvig.android.design.system.hedvig.HedvigPreview From 8913722e8960a6d85c1c0ff545941a4b8e4c8afb Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Wed, 29 Jul 2026 10:47:27 +0200 Subject: [PATCH 8/8] chore: skip unrenderable summary answers instead of failing the step An unknown answer value type raised NeedsUpdate from inside toStepContent, which aborts mapping for the whole claim intent and errors out the entire screen. The summary is a read-only recap, so drop just that answer and render whatever else came back. Logged at WARN since it is now a silent degradation. Also drop freeTexts: the answers sheet replaced the free-text pill list, so the field was selected and mapped but never read. Free-text answers now arrive as Answer.Value.Text. Co-Authored-By: Claude Opus 5 --- .../graphql/FragmentClaimIntent.graphql | 1 - .../feature/claim/chat/data/ClaimIntent.kt | 1 - .../feature/claim/chat/data/ClaimIntentExt.kt | 54 +++++++++---------- 3 files changed, 27 insertions(+), 29 deletions(-) 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 255006dbe4..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,6 @@ fragment SummaryFragment on ClaimIntentStepContentSummary { contentType fileName } - freeTexts keyDetails { title value 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 d389c3fe2b..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,6 @@ internal sealed interface StepContent { val items: List, val audioRecordings: List, val fileUploads: List, - val freeTexts: List, val keyDetails: List, val answers: List, ) : StepContent { 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 0fb2953ef2..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,34 +178,33 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): it.fileName, ) }, - freeTexts = freeTexts, keyDetails = keyDetails.map { StepContent.Summary.Item(it.title, it.value) }, - answers = answers.map { answer -> - StepContent.Summary.Answer( - title = answer.title, - 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 { "SummaryFragment.Answer: Unknown answer value type" } - raise(ClaimChatErrorMessage.NeedsUpdate) - } - }, - ) + // 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) }, ) }