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/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/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)
+ }
+ },
+ )
+ },
)
}
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..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
@@ -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.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
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.",
+ ),
+ ),
+)