Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ internal sealed interface StepContent {
val items: List<Item>,
val audioRecordings: List<AudioRecording>,
val fileUploads: List<FileUpload>,
val freeTexts: List<String>,
val keyDetails: List<Item>,
val answers: List<Answer>,
) : StepContent {
override val isSkippable: Boolean = false

Expand All @@ -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<FileUpload>) : Value
}
}
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -775,7 +776,6 @@ private fun StepTopContent(
id = it.url,
)
},
freeTexts = stepItem.stepContent.freeTexts,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,30 @@ 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
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
Expand All @@ -64,14 +70,23 @@ internal fun ChatClaimSummaryBottomContent(

@Composable
internal fun ChatClaimSummaryTopContent(
keyDetails: List<StepContent.Summary.Item>,
answers: List<StepContent.Summary.Answer>,
recordingUrls: List<String>,
fileUploads: List<UiFile>,
freeTexts: List<String>,
displayItems: List<Pair<String, String>>,
imageLoader: ImageLoader,
onNavigateToImageViewer: (imageUrl: String, cacheKey: String) -> Unit,
modifier: Modifier = Modifier,
) {
val answersSheetState = rememberHedvigBottomSheetState<List<StepContent.Summary.Answer>>()
HedvigBottomSheet(answersSheetState) { sheetAnswers ->
ClaimSummaryAnswersContent(
answers = sheetAnswers,
imageLoader = imageLoader,
onNavigateToImageViewer = onNavigateToImageViewer,
onClose = { answersSheetState.dismiss() },
)
}
Column(modifier) {
HedvigCard(
color = HedvigTheme.colorScheme.fillNegative,
Expand All @@ -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 {
Expand All @@ -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,
)
},
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -172,6 +178,87 @@ internal fun ChatClaimSummaryTopContent(
}
}

@Composable
internal fun ClaimSummaryAnswersContent(
answers: List<StepContent.Summary.Answer>,
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() {
Expand All @@ -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<StepContent.Summary.Answer> = 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.",
),
),
)
Loading