From b44e35f4cd51e201798ea618016efac83a359cb3 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 13 Jul 2026 13:58:13 +0200 Subject: [PATCH 1/6] 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 --- .../android/apollo/octopus/schema.graphqls | 17 +++++++++++ .../graphql/FragmentClaimIntent.graphql | 23 +++++++++++++++ .../feature/claim/chat/data/ClaimIntent.kt | 12 ++++++++ .../feature/claim/chat/data/ClaimIntentExt.kt | 28 +++++++++++++++++++ 4 files changed, 80 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 f33ce82da4..b094965ce3 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 @@ -1280,6 +1280,8 @@ type ClaimIntentStepContentSummary { audioRecordings: [ClaimIntentStepContentSummaryAudioRecording!]! freeTexts: [String!]! fileUploads: [ClaimIntentStepContentSummaryFileUpload!]! + keyDetails: [ClaimIntentStepContentSummaryItem!]! + answers: [ClaimIntentStepContentSummaryAnswer!]! } type ClaimIntentStepContentSummaryAudioRecording { url: Url! @@ -1293,6 +1295,21 @@ type ClaimIntentStepContentSummaryItem { title: String! value: String! } +type ClaimIntentStepContentSummaryAnswer { + title: String! + value: ClaimIntentStepContentSummaryAnswerValue! +} +union ClaimIntentStepContentSummaryAnswerValue = ClaimIntentStepContentSummaryAnswerText|ClaimIntentStepContentSummaryAnswerAudio|ClaimIntentStepContentSummaryAnswerFiles +type ClaimIntentStepContentSummaryAnswerText { + text: String! +} +type ClaimIntentStepContentSummaryAnswerAudio { + url: Url! + transcript: String +} +type ClaimIntentStepContentSummaryAnswerFiles { + files: [ClaimIntentStepContentSummaryFileUpload!]! +} """ A task step is one that eventually completes itself. This is meant to be polled until `isCompleted` is true, and then concluded using `Mutation.claimIntentSubmitTask`. 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 2f5e7600b64ba9fad029364d90e66406d3322b1a Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Mon, 13 Jul 2026 14:14:23 +0200 Subject: [PATCH 2/6] 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 --- .../audio/player/CommonMediaPlayer.jvm.kt | 37 +++- .../values-sv-rSE/strings.xml | 1 + .../composeResources/values/strings.xml | 1 + .../feature-claim-chat/build.gradle.kts | 3 + .../claim/chat/ui/ClaimChatDestination.kt | 4 +- .../chat/ui/step/ChatClaimSummaryStep.kt | 176 +++++++++++++++--- .../chat/ui/step/ClaimSummaryRenderTest.kt | 84 +++++++++ 7 files changed, 272 insertions(+), 34 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/audio-player-ui/src/jvmMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.jvm.kt b/app/audio-player-ui/src/jvmMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.jvm.kt index 31a4004c8b..c81ad577bd 100644 --- a/app/audio-player-ui/src/jvmMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.jvm.kt +++ b/app/audio-player-ui/src/jvmMain/kotlin/com/hedvig/android/audio/player/CommonMediaPlayer.jvm.kt @@ -1,5 +1,38 @@ package com.hedvig.android.audio.player -actual fun CommonMediaPlayer(dataSourceUrl: String): CommonMediaPlayer { - TODO("jvm") +import com.hedvig.audio.player.data.ProgressPercentage + +/** + * No JVM/desktop application target ships audio playback — the JVM target exists only for headless + * Compose render tests and previews. This no-op implementation lets audio-backed composables (e.g. + * [HedvigAudioPlayer]) compose and render on the JVM without a real media backend. It never plays + * anything and stays in the initial "preparing" state. + */ +actual fun CommonMediaPlayer(dataSourceUrl: String): CommonMediaPlayer = NoOpCommonMediaPlayer + +private object NoOpCommonMediaPlayer : CommonMediaPlayer { + override val isPlaying: Boolean = false + override val duration: Int = 0 + + override fun pause() {} + + override fun start() {} + + override fun stop() {} + + override fun release() {} + + override suspend fun seekToPercent(percentage: ProgressPercentage) {} + + override fun hasReachedTheEnd(): Boolean = false + + override fun getProgressPercentage(): ProgressPercentage = ProgressPercentage(0f) + + override fun setOnErrorListener(block: (what: Int, extra: Int) -> Boolean) {} + + override fun setOnPreparedListener(block: () -> Unit) {} + + override fun setOnCompletionListener(block: () -> Unit) {} + + override fun prepareAsync() {} } diff --git a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml index 9b00cccdb5..ac565f0233 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values-sv-rSE/strings.xml @@ -1068,6 +1068,7 @@ Saknas något? Skicka ett meddelande till oss här i appen. Inskickad Detaljer om skadeanmälan + Visa alla svar Ärendet är avslutat. Har du frågor gällande beslutet? Ställ dem direkt i chatten. Filer Du kan lyssna på din inspelade skadeanmälan här. diff --git a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml index eabd4337b2..a4ddd04181 100644 --- a/app/core/core-resources/src/commonMain/composeResources/values/strings.xml +++ b/app/core/core-resources/src/commonMain/composeResources/values/strings.xml @@ -1068,6 +1068,7 @@ Is something missing? Send us a message here in the app. Submitted Claim details + Show all answers The claim is closed. Do you have any questions regarding your claim? Please ask them directly in the chat. Files You can listen to your recorded claim audio here. diff --git a/app/feature/feature-claim-chat/build.gradle.kts b/app/feature/feature-claim-chat/build.gradle.kts index a379a3dc2b..71352d5af3 100644 --- a/app/feature/feature-claim-chat/build.gradle.kts +++ b/app/feature/feature-claim-chat/build.gradle.kts @@ -55,6 +55,9 @@ kotlin { implementation(projects.uiForceUpgrade) implementation(projects.partnersDeflect) } + jvmTest.dependencies { + implementation(org.jetbrains.compose.ComposePlugin.Dependencies(project).desktop.currentOs) + } androidMain.dependencies { implementation(libs.accompanist.permissions) implementation(libs.androidx.activity.compose) 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 255bcb99fc..9a51253505 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 @@ -786,8 +786,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 { @@ -799,7 +800,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 cadea2c8a817241974aaa23b5a3ee83a4e7f29ec Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 13:54:15 +0200 Subject: [PATCH 3/6] 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 1be3da27043ad3e91bbadc837689e25aa1271567 Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 14:02:40 +0200 Subject: [PATCH 4/6] 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 1ec741bfccaac2696b0e2a627ca0b4cff1925dbd Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 14:31:18 +0200 Subject: [PATCH 5/6] 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 023f4e744ba1f58d9b53f338e23a5f51494c8def Mon Sep 17 00:00:00 2001 From: Hugo Linder Date: Tue, 14 Jul 2026 14:59:32 +0200 Subject: [PATCH 6/6] 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,