diff --git a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt index 36905da0e5..7b6f04cc6e 100644 --- a/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt +++ b/app/app/src/main/kotlin/com/hedvig/android/app/navigation/HedvigEntryProvider.kt @@ -230,8 +230,12 @@ private fun EntryProviderScope.addHomeEntries( backstack.add(CoInsuredAddInfoKey(contractId, type)) }, navigateToHelpCenter = { backstack.add(HelpCenterKey) }, - navigateToClaimChat = { - backstack.add(ClaimChatKey(messageId = null, isDevelopmentFlow = false)) + navigateToClaimChat = { resumableClaimId -> + backstack.add(ClaimChatKey( + messageId = null, + isDevelopmentFlow = false, + resumableClaimId = resumableClaimId, + )) }, navigateToChipIdScreen = { backstack.add(ChipIdKey()) }, openAppSettings = externalNavigator::openAppSettings, diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/StartClaimBottomSheet.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/StartClaimBottomSheet.kt index bdea2a83c2..6b168aeef1 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/StartClaimBottomSheet.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/StartClaimBottomSheet.kt @@ -23,8 +23,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.dropUnlessResumed +import com.hedvig.android.compose.ui.preview.BooleanCollectionPreviewParameterProvider import com.hedvig.android.design.system.hedvig.api.HedvigBottomSheetState import com.hedvig.android.design.system.hedvig.icon.Checkmark import com.hedvig.android.design.system.hedvig.icon.HedvigIcons @@ -40,8 +42,13 @@ import hedvig.resources.general_cancel_button import hedvig.resources.general_continue_button import org.jetbrains.compose.resources.stringResource +data class StartClaimSheetData(val resumableClaimId: String?) @Composable -fun StartClaimBottomSheet(state: HedvigBottomSheetState, navigateToClaimChat: () -> Unit) { +fun StartClaimBottomSheet( + state: HedvigBottomSheetState, + navigateToClaimChat: () -> Unit, + navigateToOldClaim: () -> Unit, +) { HedvigBottomSheet( hedvigBottomSheetState = state, content = { @@ -54,18 +61,27 @@ fun StartClaimBottomSheet(state: HedvigBottomSheetState, navigateToClaimCh navigateToClaimChat() } }, + navigateToOldClaim = { + state.dismiss { + navigateToOldClaim() + } + }, + resumableClaimId = state.data?.resumableClaimId ) }, ) } + @Composable -fun StartClaimPledgeScreen(navigateUp: () -> Unit, navigateToClaimChat: () -> Unit, modifier: Modifier = Modifier) { +fun StartClaimPledgeScreen( + navigateUp: () -> Unit, + navigateToClaimChat: () -> Unit, + modifier: Modifier = Modifier, +) { var isChecked by remember { mutableStateOf(false) } - Column( - modifier - .verticalScroll(rememberScrollState()), - ) { + Column(modifier + .verticalScroll(rememberScrollState())) { PledgeNotes() Spacer(Modifier.weight(1f)) Spacer(Modifier.height(8.dp)) @@ -76,6 +92,8 @@ fun StartClaimPledgeScreen(navigateUp: () -> Unit, navigateToClaimChat: () -> Un }, navigateToClaimChat = navigateToClaimChat, dismiss = navigateUp, + resumableClaimId = null, + navigateToOldClaim = {} ) Spacer(Modifier.height(8.dp)) Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) @@ -83,7 +101,12 @@ fun StartClaimPledgeScreen(navigateUp: () -> Unit, navigateToClaimChat: () -> Un } @Composable -private fun StartClaimBottomSheetContent(dismiss: () -> Unit, navigateToClaimChat: () -> Unit) { +private fun StartClaimBottomSheetContent( + dismiss: () -> Unit, + navigateToClaimChat: () -> Unit, + navigateToOldClaim: () -> Unit, + resumableClaimId: String? +) { var isChecked by remember { mutableStateOf(false) } Column { Spacer(Modifier.height(16.dp)) @@ -104,6 +127,8 @@ private fun StartClaimBottomSheetContent(dismiss: () -> Unit, navigateToClaimCha }, navigateToClaimChat = navigateToClaimChat, dismiss = dismiss, + navigateToOldClaim = navigateToOldClaim, + resumableClaimId = resumableClaimId ) Spacer(Modifier.height(8.dp)) Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) @@ -116,6 +141,8 @@ private fun StartClaimBottomContent( onCheckedChange: () -> Unit, navigateToClaimChat: () -> Unit, dismiss: () -> Unit, + navigateToOldClaim: () -> Unit, + resumableClaimId: String? ) { Column { ImportantInfoCheckBox( @@ -132,6 +159,18 @@ private fun StartClaimBottomContent( modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) + if (resumableClaimId!=null) { + HedvigButton( + buttonStyle = ButtonDefaults.ButtonStyle.PrimaryAlt, + text = "Continue with the draft claim", + enabled = true, + onClick = dropUnlessResumed { + navigateToOldClaim() + }, + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(16.dp)) + } HedvigButton( text = stringResource(Res.string.general_cancel_button), enabled = true, @@ -207,12 +246,18 @@ private fun ImportantInfoCheckBox(isChecked: Boolean, onCheckedChange: () -> Uni @HedvigPreview @Composable -private fun PreviewStartClaimBottomSheetContent() { +private fun PreviewStartClaimBottomSheetContent( + @PreviewParameter( + BooleanCollectionPreviewParameterProvider::class, + ) hasResumableClaim: Boolean, +) { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { StartClaimBottomSheetContent( {}, navigateToClaimChat = {}, + navigateToOldClaim = {}, + resumableClaimId = if (hasResumableClaim) "" else null ) } } diff --git a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/InboxDestination.kt b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/InboxDestination.kt index 390005cba1..59d8410e99 100644 --- a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/InboxDestination.kt +++ b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/InboxDestination.kt @@ -61,6 +61,7 @@ import com.hedvig.android.design.system.hedvig.HighlightLabelDefaults.HighlightS import com.hedvig.android.design.system.hedvig.HorizontalItemsWithMaximumSpaceTaken import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.StartClaimBottomSheet +import com.hedvig.android.design.system.hedvig.StartClaimSheetData import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.TopAppBar import com.hedvig.android.design.system.hedvig.TopAppBarActionType @@ -131,7 +132,7 @@ private fun InboxScreen( navigateToClaimChat: () -> Unit, ) { val newChatSelectBottomSheetState = rememberHedvigBottomSheetState() - val startClaimBottomSheetState = rememberHedvigBottomSheetState() + val startClaimBottomSheetState = rememberHedvigBottomSheetState() HedvigBottomSheet( newChatSelectBottomSheetState, content = { @@ -142,7 +143,7 @@ private fun InboxScreen( }, onStartNewClaim = { newChatSelectBottomSheetState.dismiss() - startClaimBottomSheetState.show(Unit) + startClaimBottomSheetState.show(StartClaimSheetData(null)) }, dismiss = { newChatSelectBottomSheetState.dismiss() @@ -156,6 +157,7 @@ private fun InboxScreen( startClaimBottomSheetState.dismiss() navigateToClaimChat() }, + navigateToOldClaim = {} ) Surface( color = HedvigTheme.colorScheme.backgroundPrimary, diff --git a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/data/AudioRecordingManager.kt b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/data/AudioRecordingManager.kt index f1e086ab48..63eead9f66 100644 --- a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/data/AudioRecordingManager.kt +++ b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/data/AudioRecordingManager.kt @@ -86,7 +86,7 @@ internal class AndroidAudioRecordingManager( if (!file.exists()) { onStateUpdate( AudioRecordingStepState.AudioRecording.Playback( - filePath = filePath, + audioPath = AudioPath.FilePath(filePath), isPlaying = false, isPrepared = false, hasError = true, @@ -100,7 +100,7 @@ internal class AndroidAudioRecordingManager( setOnPreparedListener { onStateUpdate( AudioRecordingStepState.AudioRecording.Playback( - filePath = filePath, + audioPath = AudioPath.FilePath(filePath), isPlaying = false, isPrepared = true, hasError = false, diff --git a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/navigation/ClaimChatEntries.kt b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/navigation/ClaimChatEntries.kt index 2c163cf3cc..efa88f89da 100644 --- a/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/navigation/ClaimChatEntries.kt +++ b/app/feature/feature-claim-chat/src/androidMain/kotlin/com/hedvig/feature/claim/chat/navigation/ClaimChatEntries.kt @@ -21,6 +21,7 @@ import kotlinx.serialization.Serializable data class ClaimChatKey( val isDevelopmentFlow: Boolean = false, val messageId: String? = null, + val resumableClaimId: String? = null, ) : HedvigNavKey @Serializable @@ -58,6 +59,7 @@ fun EntryProviderScope.claimChatEntries( ) { entry { key -> ClaimChatDestination( + resumableClaimId = key.resumableClaimId, isDevelopmentFlow = key.isDevelopmentFlow, shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale, openAppSettings = openAppSettings, 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 14e8d0d94e..bc37b4b02e 100644 --- a/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql +++ b/app/feature/feature-claim-chat/src/commonMain/graphql/FragmentClaimIntent.graphql @@ -14,6 +14,16 @@ fragment ClaimIntentFragment on ClaimIntent { id submittedAt } + createdAt + previousSteps { + id + text + hint + content { + ...ClaimIntentStepContentFragment + } + isRegrettable + } } fragment ClaimIntentStepContentFragment on ClaimIntentStepContent { @@ -44,6 +54,7 @@ fragment FormFragment on ClaimIntentStepContentForm { value } defaultValues + currentValues minValue maxValue searchData { @@ -63,6 +74,7 @@ fragment ContentSelectFragment on ClaimIntentStepContentSelect { isSkippable style defaultSelectedId + currentSelectedId } fragment TaskFragment on ClaimIntentStepContentTask { @@ -75,11 +87,18 @@ fragment AudioRecordingFragment on ClaimIntentStepContentAudioRecording { isSkippable freeTextMinLength freeTextMaxLength + currentAudioUrl + currentFreeText } fragment FileUploadFragment on ClaimIntentStepContentFileUpload { uploadUri isSkippable + currentFiles { + url + contentType + fileName + } } fragment SummaryFragment on ClaimIntentStepContentSummary { diff --git a/app/feature/feature-claim-chat/src/commonMain/graphql/ResumeClaimQuery.graphql b/app/feature/feature-claim-chat/src/commonMain/graphql/ResumeClaimQuery.graphql new file mode 100644 index 0000000000..21231fdcd1 --- /dev/null +++ b/app/feature/feature-claim-chat/src/commonMain/graphql/ResumeClaimQuery.graphql @@ -0,0 +1,7 @@ +query ResumeClaim { + currentMember { + resumableClaimIntent { + ...ClaimIntentFragment + } + } +} diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt index 97e4b68f97..9740ad56ea 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ClaimChatViewModel.kt @@ -34,6 +34,7 @@ import com.hedvig.feature.claim.chat.data.FormSubmissionData.FieldToSubmit import com.hedvig.feature.claim.chat.data.FreeTextErrorType.TooShort import com.hedvig.feature.claim.chat.data.GetClaimIntentUseCase import com.hedvig.feature.claim.chat.data.RegretStepUseCase +import com.hedvig.feature.claim.chat.data.ResumeClaimUseCase import com.hedvig.feature.claim.chat.data.SkipStepUseCase import com.hedvig.feature.claim.chat.data.StartClaimIntentUseCase import com.hedvig.feature.claim.chat.data.StepContent @@ -147,7 +148,6 @@ internal sealed interface ClaimChatUiState { val claimIntentId: ClaimIntentId, val steps: List, val currentStep: ClaimIntentStep?, - val freeText: String?, val outcome: ClaimIntentOutcome?, val errorSubmittingStep: ClaimChatErrorMessage?, val currentContinueButtonLoading: Boolean = false, @@ -164,6 +164,7 @@ internal sealed interface ClaimChatUiState { @HedvigViewModel(ActivityRetainedScope::class) internal class ClaimChatViewModel( @Assisted developmentFlow: Boolean, + @Assisted resumableClaimId: String?, startClaimIntentUseCase: StartClaimIntentUseCase, getClaimIntentUseCase: GetClaimIntentUseCase, submitTaskUseCase: SubmitTaskUseCase, @@ -177,6 +178,7 @@ internal class ClaimChatViewModel( regretStepUseCase: RegretStepUseCase, formFieldSearchUseCase: FormFieldSearchUseCase, fileService: FileService, + resumeClaimUseCase: ResumeClaimUseCase, ) : MoleculeViewModel( ClaimChatUiState.Initializing, ClaimChatPresenter( @@ -194,6 +196,8 @@ internal class ClaimChatViewModel( fileService, regretStepUseCase, formFieldSearchUseCase, + resumableClaimId, + resumeClaimUseCase, ), ) { override fun onCleared() { @@ -217,6 +221,8 @@ internal class ClaimChatPresenter( private val fileService: FileService, private val regretStepUseCase: RegretStepUseCase, private val formFieldSearchUseCase: FormFieldSearchUseCase, + private val resumableClaimId: String?, + private val resumeClaimUseCase: ResumeClaimUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present(lastState: ClaimChatUiState): ClaimChatUiState { @@ -240,7 +246,6 @@ internal class ClaimChatPresenter( var currentContinueButtonLoading by remember { mutableStateOf(false) } var currentSkipButtonLoading by remember { mutableStateOf(false) } var errorSubmittingStep by remember { mutableStateOf(null) } - var freeText by remember { mutableStateOf(null) } var showConfirmEditDialogForStep by remember { mutableStateOf(null) } var progress by remember { mutableStateOf( @@ -256,32 +261,72 @@ internal class ClaimChatPresenter( if (initializing) { LaunchedEffect(Unit) { - startClaimIntentUseCase - .invoke(developmentFlow) - .fold( - ifLeft = { - initializing = false - failedToStart = true - }, - ifRight = { claimIntent -> - Snapshot.withMutableSnapshot { + val isResumingClaim = resumableClaimId != null + if (isResumingClaim) { + resumeClaimUseCase + .invoke() + .fold( + ifLeft = { initializing = false - failedToStart = false - claimIntentId = claimIntent.id - steps.clear() - progress = claimIntent.progress - when (val next = claimIntent.next) { - is ClaimIntent.Next.Outcome -> { - outcome = next.claimIntentOutcome + failedToStart = true + }, + ifRight = { claimIntent -> + if (claimIntent == null) { + initializing = false + failedToStart = true + } else { + Snapshot.withMutableSnapshot { + initializing = false + failedToStart = false + claimIntentId = claimIntent.id + steps.clear() + steps.addAll( + claimIntent.previousSteps.filter { + it.stepContent !is StepContent.Task + }, + ) + progress = claimIntent.progress + when (val next = claimIntent.next) { + is ClaimIntent.Next.Outcome -> { + outcome = next.claimIntentOutcome + } + + is ClaimIntent.Next.Step -> { + steps.add(next.claimIntentStep) + } + } } + } + }, + ) + } else { + startClaimIntentUseCase + .invoke(developmentFlow) + .fold( + ifLeft = { + initializing = false + failedToStart = true + }, + ifRight = { claimIntent -> + Snapshot.withMutableSnapshot { + initializing = false + failedToStart = false + claimIntentId = claimIntent.id + steps.clear() + progress = claimIntent.progress + when (val next = claimIntent.next) { + is ClaimIntent.Next.Outcome -> { + outcome = next.claimIntentOutcome + } - is ClaimIntent.Next.Step -> { - steps.add(next.claimIntentStep) + is ClaimIntent.Next.Step -> { + steps.add(next.claimIntentStep) + } } } - } - }, - ) + }, + ) + } } } @@ -431,7 +476,10 @@ internal class ClaimChatPresenter( } is ClaimChatEvent.AudioRecording.SubmitTextInput -> { - val freeTextInput = freeText ?: return@CollectEvents + val recordingState = steps.find { it.id == event.id } + ?.stepContent.let { it as? StepContent.AudioRecording } + ?.recordingState as? FreeTextDescription + val freeTextInput = recordingState?.freeText ?: return@CollectEvents currentContinueButtonLoading = true launch { submitAudioRecordingUseCase @@ -479,13 +527,7 @@ internal class ClaimChatPresenter( } is ClaimChatEvent.AudioRecording.SwitchToFreeText -> { - val currentContent = currentStep?.stepContent as? StepContent.AudioRecording - ?: return@CollectEvents - val textTooShort = freeText?.length?.let { - currentContent.freeTextMinLength > it - } ?: true steps.updateStepWithSuccess(event.id) { step, content -> - val canSubmit = !currentContinueButtonLoading && !freeText.isNullOrEmpty() && !textTooShort showFreeTextOverlay = FreeTextRestrictions( content.freeTextMinLength, content.freeTextMaxLength, @@ -494,7 +536,8 @@ internal class ClaimChatPresenter( stepContent = content.copy( recordingState = FreeTextDescription( errorType = null, - canSubmit = canSubmit, + canSubmit = false, + freeText = null, ), ), ) @@ -690,11 +733,11 @@ internal class ClaimChatPresenter( null }, canSubmit = canSubmit, + freeText = event.text, ), ), ) } - freeText = event.text } } @@ -743,7 +786,6 @@ internal class ClaimChatPresenter( val index = steps.indexOf(stepToUpdate) if (index >= 0) { steps.subList(index, steps.size).clear() - if (steps.none { it.stepContent is StepContent.AudioRecording }) freeText = null } currentContinueButtonLoading = false currentSkipButtonLoading = false @@ -950,7 +992,6 @@ internal class ClaimChatPresenter( currentStep = currentStep, outcome = outcome, showFreeTextOverlay = showFreeTextOverlay, - freeText = freeText, errorSubmittingStep = errorSubmittingStep, currentContinueButtonLoading = currentContinueButtonLoading, currentSkipButtonLoading = currentSkipButtonLoading, 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 6df1fd1eed..b36c5c229c 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 @@ -15,6 +15,7 @@ internal data class ClaimIntent( val id: ClaimIntentId, val next: Next, val progress: Float?, + val previousSteps: List, ) { sealed interface Next { val step: Step? @@ -62,7 +63,9 @@ internal sealed interface StepContent { val uploadUri: String, override val isSkippable: Boolean, val localFiles: List, - ) : StepContent + ) : StepContent { + data class RemoteFile(val url: String, val contentType: String, val fileName: String) + } data class Task( val descriptions: List, @@ -186,6 +189,7 @@ sealed interface AudioRecordingStepState { val errorType: FreeTextErrorType?, val canSubmit: Boolean, val hasError: Boolean = false, + val freeText: String? = null, ) : AudioRecordingStepState sealed interface AudioRecording : AudioRecordingStepState { @@ -198,7 +202,7 @@ sealed interface AudioRecordingStepState { ) : AudioRecording data class Playback( - val filePath: String, + val audioPath: AudioPath, val isPlaying: Boolean, val isPrepared: Boolean, val hasError: Boolean, @@ -206,6 +210,12 @@ sealed interface AudioRecordingStepState { } } +sealed interface AudioPath { + data class FilePath(val filePath: String) : AudioPath + + data class RemoteUrl(val remoteUrl: String) : AudioPath +} + sealed interface FreeTextErrorType { data class TooShort(val minLength: Int) : FreeTextErrorType } 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 9848835497..b9e7a14aa4 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 @@ -4,6 +4,7 @@ import arrow.core.raise.Raise import arrow.core.raise.context.raise 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.logcat import com.hedvig.android.shared.partners.deflect.DeflectData @@ -56,6 +57,9 @@ internal fun ClaimIntentFragment.toClaimIntent(locale: CommonLocale): ClaimInten else -> error("ClaimIntentFragment contained null currentStep and null outcome") }, progress = progress?.toFloat(), + previousSteps = previousSteps.map { + it.toClaimIntentStep(locale) + }, ) } @@ -70,6 +74,17 @@ private fun ClaimIntentFragment.CurrentStep.toClaimIntentStep(locale: CommonLoca ) } +context(raise: Raise) +private fun ClaimIntentFragment.PreviousStep.toClaimIntentStep(locale: CommonLocale): ClaimIntentStep { + return ClaimIntentStep( + id = StepId(id), + text = text, + stepContent = this.content.toStepContent(locale), + isRegrettable = this.isRegrettable, + hint = hint, + ) +} + context(raise: Raise) private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): StepContent { return when (this) { @@ -83,7 +98,7 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): is ContentSelectFragment -> { StepContent.ContentSelect( options = options.toOptions(), - selectedOptionId = defaultSelectedId, + selectedOptionId = currentSelectedId ?: defaultSelectedId, isSkippable = isSkippable, style = when (style) { ClaimIntentStepContentSelectStyle.PILL -> StepContent.ContentSelectStyle.PILL @@ -102,10 +117,28 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): } is AudioRecordingFragment -> { + val audioUrl = this.currentAudioUrl + val freeText = this.currentFreeText + val recordingState = if (audioUrl != null) { + AudioRecordingStepState.AudioRecording.Playback( + audioPath = AudioPath.RemoteUrl(audioUrl), + isPlaying = false, + isPrepared = true, // TODO: check + hasError = false, + ) + } else if (freeText != null) { + AudioRecordingStepState.FreeTextDescription( + errorType = null, + canSubmit = true, + freeText = freeText, + ) + } else { + AudioRecordingStepState.AudioRecording.NotRecording + } StepContent.AudioRecording( uploadUri = uploadUri, isSkippable = isSkippable, - recordingState = AudioRecordingStepState.AudioRecording.NotRecording, + recordingState = recordingState, freeTextMinLength = freeTextMinLength, freeTextMaxLength = freeTextMaxLength, ) @@ -115,7 +148,15 @@ private fun ClaimIntentStepContentFragment.toStepContent(locale: CommonLocale): StepContent.FileUpload( uploadUri = uploadUri, isSkippable = isSkippable, - localFiles = emptyList(), + localFiles = this.currentFiles?.map { + UiFile( + name = it.fileName, + localPath = null, + url = it.url, + mimeType = it.contentType, + id = it.url, + ) + } ?: emptyList(), ) } @@ -207,12 +248,17 @@ private fun List.toOptions(): List) private fun List.toFields(locale: CommonLocale): List { return this.map { field -> + val defaultValues = if (field.currentValues.isNotEmpty()) { + field.currentValues.toFieldOptions(field.options) + } else { + field.defaultValues.toFieldOptions(field.options) + } StepContent.Form.Field( id = FieldId(field.id), isRequired = field.isRequired, suffix = field.suffix, title = field.title, - defaultValues = field.defaultValues.toFieldOptions(field.options), + defaultValues = defaultValues, maxValue = field.maxValue, minValue = field.minValue, type = when (field.type) { @@ -260,12 +306,12 @@ private fun List.toFields(locale: CommonLocale): List { DatePickerUiState( locale = locale, - initiallySelectedDate = field.defaultValues.getOrNull(0)?.let { LocalDate.parse(it) }, + initiallySelectedDate = defaultValues.getOrNull(0)?.let { LocalDate.parse(it.text) }, minDate = field.minValue?.let { LocalDate.parse(it) } ?: LocalDate(1900, 1, 1), maxDate = field.maxValue?.let { LocalDate.parse(it) } ?: LocalDate(2100, 1, 1), ) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ResumeClaimUseCase.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ResumeClaimUseCase.kt new file mode 100644 index 0000000000..6d6c190571 --- /dev/null +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/data/ResumeClaimUseCase.kt @@ -0,0 +1,39 @@ +package com.hedvig.feature.claim.chat.data + +import arrow.core.Either +import arrow.core.raise.either +import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.cache.normalized.FetchPolicy +import com.apollographql.apollo.cache.normalized.fetchPolicy +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.core.common.di.AppScope +import com.hedvig.android.language.LanguageService +import com.hedvig.android.logger.logcat +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import octopus.ResumeClaimQuery + +@SingleIn(AppScope::class) +@Inject +internal class ResumeClaimUseCase( + private val apolloClient: ApolloClient, + private val languageService: LanguageService, +) { + suspend fun invoke(): Either { + return either { + apolloClient + .query( + ResumeClaimQuery(), + ) + .fetchPolicy(FetchPolicy.NetworkOnly) + .safeExecute() + .mapLeft { + logcat { "StartClaimIntentUseCase error: $it" } + ClaimChatErrorMessage.GeneralError + } + .bind() + .currentMember.resumableClaimIntent + ?.toClaimIntent(languageService.getLocale()) + } + } +} 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 b998d9d84f..6c3d602e1a 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 @@ -88,6 +88,7 @@ import com.hedvig.feature.claim.chat.ClaimChatEvent.AudioRecording.* import com.hedvig.feature.claim.chat.ClaimChatUiState import com.hedvig.feature.claim.chat.ClaimChatViewModel import com.hedvig.feature.claim.chat.ClaimChatViewModelFactory +import com.hedvig.feature.claim.chat.data.AudioRecordingStepState import com.hedvig.feature.claim.chat.data.ClaimChatErrorMessage import com.hedvig.feature.claim.chat.data.ClaimIntentOutcome import com.hedvig.feature.claim.chat.data.ClaimIntentStep @@ -136,10 +137,11 @@ internal fun ClaimChatDestination( isDevelopmentFlow: Boolean, navigateUp: () -> Unit, openPlayStore: () -> Unit, + resumableClaimId: String?, ) { val claimChatViewModel = assistedMetroViewModel { - create(isDevelopmentFlow) + create(isDevelopmentFlow, resumableClaimId) } Box(Modifier.fillMaxSize(), propagateMinConstraints = true) { BlurredGradientBackground() @@ -227,9 +229,12 @@ private fun ClaimChatScreen( openAppSettings: () -> Unit, openPlayStore: () -> Unit, ) { + val currentFreeText = (uiState.currentStep?.stepContent as? StepContent.AudioRecording) + ?.recordingState.let { it as? AudioRecordingStepState.FreeTextDescription } + ?.freeText FreeTextOverlay( freeTextMaxLength = uiState.showFreeTextOverlay?.maxLength ?: 2000, - freeTextValue = uiState.freeText, + freeTextValue = currentFreeText, freeTextHint = stringResource(Res.string.CLAIMS_TEXT_INPUT_POPOVER_PLACEHOLDER), freeTextTitle = stringResource(Res.string.CLAIMS_TEXT_INPUT_PLACEHOLDER), freeTextOnCancelClick = { @@ -318,7 +323,8 @@ private fun ClaimChatScreenContent( if (showCloseFlowDialog) { HedvigAlertDialog( title = stringResource(Res.string.GENERAL_ARE_YOU_SURE), - text = stringResource(Res.string.claims_alert_body), + // text = stringResource(Res.string.claims_alert_body), + text = "Your answers will be saved in a draft claim", //TODO onDismissRequest = { showCloseFlowDialog = false }, @@ -485,7 +491,6 @@ private fun ClaimChatScrollableContent( StepContentSection( stepItem = item, - freeText = uiState.freeText, isCurrentStep = isCurrentStep, showAnimationSequence = showAnimationSequence, currentContinueButtonLoading = uiState.currentContinueButtonLoading, @@ -543,7 +548,6 @@ private fun ScrollToBottomButton(onClick: () -> Unit, modifier: Modifier = Modif @Composable private fun StepContentSection( stepItem: ClaimIntentStep, - freeText: String?, isCurrentStep: Boolean, showAnimationSequence: Boolean, currentContinueButtonLoading: Boolean, @@ -619,7 +623,6 @@ private fun StepContentSection( ) { StepBottomContent( stepItem = stepItem, - freeText = freeText, isCurrentStep = isCurrentStep, currentContinueButtonLoading = currentContinueButtonLoading, currentSkipButtonLoading = currentSkipButtonLoading, @@ -756,7 +759,6 @@ private fun CommonPaddingWrapper(content: @Composable () -> Unit) { @Composable private fun StepBottomContent( stepItem: ClaimIntentStep, - freeText: String?, isCurrentStep: Boolean, currentContinueButtonLoading: Boolean, currentSkipButtonLoading: Boolean, @@ -807,7 +809,6 @@ private fun StepBottomContent( clock = Clock.System, onShouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale, openAppSettings = openAppSettings, - freeText = freeText, onEvent = onEvent, continueButtonLoading = currentContinueButtonLoading, skipButtonLoading = currentSkipButtonLoading, diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt index 5f6836c811..446d357903 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/ClaimChatUiComponents.kt @@ -43,6 +43,7 @@ private fun PreviewClaimChatComponents() { recordingState = AudioRecordingStepState.FreeTextDescription( errorType = null, canSubmit = true, + freeText = "some not really long free text", ), clock = Clock.System, onShouldShowRequestPermissionRationale = { @@ -60,7 +61,6 @@ private fun PreviewClaimChatComponents() { onLaunchFullScreenEditText = {}, canSkip = true, onSkip = {}, - freeText = "some not really long free text", continueButtonLoading = false, skipButtonLoading = false, ) diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/FormStep.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/FormStep.kt index 512c1689f7..b0da4e687f 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/FormStep.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/FormStep.kt @@ -190,7 +190,8 @@ private fun FormContent( FieldType.TEXT -> { TextInputBubble( questionLabel = field.title, - text = field.selectedOptions.getOrNull(0)?.text, + text = field.selectedOptions.getOrNull(0)?.text + ?: field.defaultValues.getOrNull(0)?.text, suffix = field.suffix, onInput = { answer -> onSelectFieldAnswer( diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecorder.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecorder.kt index 179e16d2d4..8209440bb3 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecorder.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecorder.kt @@ -63,6 +63,8 @@ import com.hedvig.android.design.system.hedvig.LocalContentColor import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.tokens.MotionTokens import com.hedvig.audio.player.data.PlayableAudioSource +import com.hedvig.audio.player.data.SignedAudioUrl +import com.hedvig.feature.claim.chat.data.AudioPath import com.hedvig.feature.claim.chat.data.AudioRecordingStepState import com.hedvig.feature.claim.chat.ui.common.SkippedLabel import hedvig.resources.A11Y_AUDIO_RECORDING @@ -252,7 +254,15 @@ private fun Playback( if (!uiState.isPrepared) { HedvigCircularProgressIndicator() } else { - val audioPlayer = rememberAudioPlayer(PlayableAudioSource.LocalFilePath(uiState.filePath)) + val audioPlayer = when (uiState.audioPath) { + is AudioPath.FilePath -> rememberAudioPlayer(PlayableAudioSource.LocalFilePath(uiState.audioPath.filePath)) + + is AudioPath.RemoteUrl -> rememberAudioPlayer( + PlayableAudioSource.RemoteUrl( + SignedAudioUrl.fromSignedAudioUrlString(uiState.audioPath.remoteUrl), + ), + ) + } if (!isCurrentStep) { HedvigAudioPlayer( audioPlayer = audioPlayer, diff --git a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecordingStepSections.kt b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecordingStepSections.kt index 4a063a4a89..34f584d9ea 100644 --- a/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecordingStepSections.kt +++ b/app/feature/feature-claim-chat/src/commonMain/kotlin/com/hedvig/feature/claim/chat/ui/step/audiorecording/AudioRecordingStepSections.kt @@ -98,8 +98,10 @@ import com.hedvig.audio.player.data.AudioPlayer import com.hedvig.audio.player.data.AudioPlayerState import com.hedvig.audio.player.data.PlayableAudioSource import com.hedvig.audio.player.data.ProgressPercentage +import com.hedvig.audio.player.data.SignedAudioUrl import com.hedvig.feature.claim.chat.ClaimChatEvent import com.hedvig.feature.claim.chat.FreeTextRestrictions +import com.hedvig.feature.claim.chat.data.AudioPath import com.hedvig.feature.claim.chat.data.AudioRecordingStepState import com.hedvig.feature.claim.chat.data.ClaimIntentStep import com.hedvig.feature.claim.chat.data.FreeTextErrorType @@ -140,7 +142,6 @@ import org.jetbrains.compose.resources.stringResource @Composable internal fun AudioRecordingStep( item: ClaimIntentStep, - freeText: String?, stepContent: StepContent.AudioRecording, onShowFreeText: () -> Unit, onSwitchToAudioRecording: () -> Unit, @@ -185,7 +186,6 @@ internal fun AudioRecordingStep( canSkip = stepContent.isSkippable, onSkip = onSkip, isCurrentStep = isCurrentStep, - freeText = freeText, continueButtonLoading = continueButtonLoading, skipButtonLoading = skipButtonLoading, ) @@ -201,7 +201,6 @@ internal fun AudioRecordingStep( @Composable internal fun AudioRecorderBubble( recordingState: AudioRecordingStepState, - freeText: String?, clock: Clock, onShouldShowRequestPermissionRationale: (String) -> Boolean, startRecording: () -> Unit, @@ -238,7 +237,7 @@ internal fun AudioRecorderBubble( submitFreeText = submitFreeText, showAudioRecording = onSwitchToAudioRecording, onLaunchFullScreenEditText = onLaunchFullScreenEditText, - freeText = freeText, + freeText = recordingState.freeText, hasError = recordingState.hasError, errorType = recordingState.errorType, isCurrentStep = isCurrentStep, @@ -284,9 +283,17 @@ internal fun AudioRecorderBubble( } } else { if (recordingState is AudioRecordingStepState.AudioRecording.Playback) { - val audioPlayer = rememberAudioPlayer( - PlayableAudioSource.LocalFilePath(recordingState.filePath), - ) + val audioPlayer = when (recordingState.audioPath) { + is AudioPath.FilePath -> rememberAudioPlayer( + PlayableAudioSource.LocalFilePath(recordingState.audioPath.filePath), + ) + + is AudioPath.RemoteUrl -> rememberAudioPlayer( + PlayableAudioSource.RemoteUrl( + SignedAudioUrl.fromSignedAudioUrlString(recordingState.audioPath.remoteUrl), + ), + ) + } HedvigAudioPlayer( audioPlayer = audioPlayer, Modifier.padding(start = sentAnswersStartPadding), @@ -355,9 +362,13 @@ private fun AudioRecordingBottomSheet( } val audioPlayer = (audioRecordingState as? AudioRecordingStepState.AudioRecording.Playback)?.let { - rememberAudioPlayer( - PlayableAudioSource.LocalFilePath(it.filePath), - ) + if (it.audioPath is AudioPath.FilePath) { + rememberAudioPlayer( + PlayableAudioSource.LocalFilePath(it.audioPath.filePath), + ) + } else { + null + } } LaunchedEffect(bottomSheetState.isVisible) { @@ -1156,19 +1167,19 @@ private class AudioRecordingSheetContentStateProvider : filePath = "/path/to/recording.mp4", ), AudioRecordingStepState.AudioRecording.Playback( - filePath = "/path/to/recording.mp4", + audioPath = AudioPath.FilePath("/path/to/recording.mp4"), isPlaying = false, isPrepared = true, hasError = false, ), AudioRecordingStepState.AudioRecording.Playback( - filePath = "/path/to/recording.mp4", + audioPath = AudioPath.FilePath("/path/to/recording.mp4"), isPlaying = false, isPrepared = false, hasError = false, ), AudioRecordingStepState.AudioRecording.Playback( - filePath = "/path/to/recording.mp4", + audioPath = AudioPath.FilePath("/path/to/recording.mp4"), isPlaying = false, isPrepared = false, hasError = true, diff --git a/app/feature/feature-home/src/main/graphql/QueryHome.graphql b/app/feature/feature-home/src/main/graphql/QueryHome.graphql index c333001488..c971555513 100644 --- a/app/feature/feature-home/src/main/graphql/QueryHome.graphql +++ b/app/feature/feature-home/src/main/graphql/QueryHome.graphql @@ -1,5 +1,8 @@ query Home($claimsHistoryFlag: Boolean!) { currentMember { + resumableClaimIntent { + id + } claims@skip(if: $claimsHistoryFlag) { ...ClaimFragment } diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt index c4624d0ea3..86a890c4c1 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt @@ -172,6 +172,7 @@ internal class GetHomeDataUseCaseImpl( crossSells = crossSells, travelBannerInfo = travelBannerInfo?.firstOrNull(), showChatIcon = showChatIcon, + resumableClaimId = homeQueryData.currentMember.resumableClaimIntent?.id ) }.onLeft { error: ApolloOperationError -> logcat(operationError = error) { "GetHomeDataUseCase failed with $error" } @@ -280,6 +281,7 @@ data class HomeData( val firstVetSections: List, val crossSells: CrossSellSheetData, val travelBannerInfo: AddonBannerInfo?, + val resumableClaimId: String? ) { @Immutable data class ClaimStatusCardsData( diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCaseDemo.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCaseDemo.kt index 4ee160b208..fc50dfc2ea 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCaseDemo.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCaseDemo.kt @@ -56,6 +56,7 @@ internal class GetHomeDataUseCaseDemo : GetHomeDataUseCase { ), travelBannerInfo = null, showChatIcon = false, + resumableClaimId = null ).right(), ) } diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeEntries.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeEntries.kt index fbac21457e..885cc4c4f9 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeEntries.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/navigation/HomeEntries.kt @@ -25,7 +25,7 @@ fun EntryProviderScope.homeEntries( navigateToContactInfo: () -> Unit, navigateToMissingInfo: (String, CoInsuredFlowType) -> Unit, navigateToHelpCenter: () -> Unit, - navigateToClaimChat: () -> Unit, + navigateToClaimChat: (String?) -> Unit, navigateToChipIdScreen: () -> Unit, openAppSettings: () -> Unit, openUrl: (String) -> Unit, @@ -38,7 +38,7 @@ fun EntryProviderScope.homeEntries( viewModel = viewModel, onNavigateToInbox = dropUnlessResumed { onNavigateToInbox() }, onNavigateToNewConversation = dropUnlessResumed { onNavigateToNewConversation() }, - navigateToClaimChat = dropUnlessResumed { navigateToClaimChat() }, + navigateToClaimChat = navigateToClaimChat, onClaimDetailCardClicked = dropUnlessResumed { claimId: String -> navigateToClaimDetails(claimId) }, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index d0c15818c5..9ef4a72fa3 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -86,6 +86,7 @@ import com.hedvig.android.design.system.hedvig.LocalContentColor import com.hedvig.android.design.system.hedvig.NotificationDefaults import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority import com.hedvig.android.design.system.hedvig.StartClaimBottomSheet +import com.hedvig.android.design.system.hedvig.StartClaimSheetData import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.TooltipDefaults import com.hedvig.android.design.system.hedvig.TooltipDefaults.BeakDirection.TopEnd @@ -155,7 +156,7 @@ internal fun HomeDestination( viewModel: HomeViewModel, onNavigateToInbox: () -> Unit, onNavigateToNewConversation: () -> Unit, - navigateToClaimChat: () -> Unit, + navigateToClaimChat: (String?) -> Unit, onClaimDetailCardClicked: (claimId: String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToConnectPayout: () -> Unit, @@ -205,7 +206,7 @@ private fun HomeScreen( reload: () -> Unit, onNavigateToInbox: () -> Unit, onNavigateToNewConversation: () -> Unit, - navigateToClaimChat: () -> Unit, + navigateToClaimChat: (String?) -> Unit, onClaimDetailCardClicked: (claimId: String) -> Unit, navigateToConnectPayment: () -> Unit, navigateToConnectPayout: () -> Unit, @@ -237,10 +238,17 @@ private fun HomeScreen( onCrossSellClick = openCrossSellUrl, imageLoader = imageLoader, ) - val startClaimBottomSheetState = rememberHedvigBottomSheetState() + + val resumableClaimId = (uiState as? Success)?.resumableClaimId + val startClaimBottomSheetState = rememberHedvigBottomSheetState() StartClaimBottomSheet( state = startClaimBottomSheetState, - navigateToClaimChat = navigateToClaimChat, + navigateToOldClaim = { + navigateToClaimChat(resumableClaimId) + }, + navigateToClaimChat = { + navigateToClaimChat(null) + }, ) Box(Modifier.fillMaxSize()) { val toolbarHeight = 64.dp @@ -277,7 +285,9 @@ private fun HomeScreen( navigateToConnectPayment = navigateToConnectPayment, navigateToConnectPayout = navigateToConnectPayout, navigateToHelpCenter = navigateToHelpCenter, - openClaimFlowSheet = startClaimBottomSheetState::show, + openClaimFlowSheet = { + startClaimBottomSheetState.show(StartClaimSheetData(resumableClaimId)) + }, openAppSettings = openAppSettings, openUrl = openUrl, navigateToMissingInfo = navigateToMissingInfo, @@ -796,6 +806,7 @@ private fun PreviewHomeScreen( flowType = FlowType.APP_TRAVEL_PLUS_SELL_OR_UPGRADE, ), isProduction = true, + resumableClaimId = null ), notificationPermissionState = rememberPreviewNotificationPermissionState(), reload = {}, @@ -881,6 +892,7 @@ private fun PreviewHomeScreenAllHomeTextTypes( chatAction = ChatAction, addonBannerInfo = null, isProduction = true, + resumableClaimId = null ), notificationPermissionState = rememberPreviewNotificationPermissionState(), reload = {}, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenter.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenter.kt index fafa5cb3bc..3569e4b513 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenter.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenter.kt @@ -138,6 +138,7 @@ internal class HomePresenter( crossSellsAction = successData.crossSellsAction, addonBannerInfo = successData.addonBannerInfo, isProduction = isProduction, + resumableClaimId = successData.resumableClaimId ) } } @@ -177,6 +178,7 @@ internal sealed interface HomeUiState { val isProduction: Boolean, override val isHelpCenterEnabled: Boolean, override val hasUnseenChatMessages: Boolean, + val resumableClaimId: String? ) : HomeUiState data class Error(val message: String?) : HomeUiState @@ -195,6 +197,7 @@ private data class SuccessData( val crossSellsAction: HomeTopBarAction.CrossSellsAction?, val hasUnseenChatMessages: Boolean, val addonBannerInfo: AddonBannerInfo?, + val resumableClaimId: String? ) { companion object { fun fromLastState(lastState: HomeUiState): SuccessData? { @@ -210,6 +213,7 @@ private data class SuccessData( hasUnseenChatMessages = lastState.hasUnseenChatMessages, addonBannerInfo = lastState.addonBannerInfo, chatAction = lastState.chatAction, + resumableClaimId = lastState.resumableClaimId ) } @@ -257,6 +261,7 @@ private data class SuccessData( hasUnseenChatMessages = homeData.hasUnseenChatMessages, addonBannerInfo = homeData.travelBannerInfo, chatAction = if (homeData.showChatIcon) HomeTopBarAction.ChatAction else null, + resumableClaimId = homeData.resumableClaimId ) } } diff --git a/app/feature/feature-home/src/test/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenterTest.kt b/app/feature/feature-home/src/test/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenterTest.kt index 4f7fde4823..c122a4ee87 100644 --- a/app/feature/feature-home/src/test/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenterTest.kt +++ b/app/feature/feature-home/src/test/kotlin/com/hedvig/android/feature/home/home/ui/HomePresenterTest.kt @@ -143,6 +143,7 @@ internal class HomePresenterTest { crossSells = CrossSellSheetData(testCrossSell, listOf()), firstVetSections = listOf(), travelBannerInfo = null, + resumableClaimId = null ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -174,6 +175,8 @@ internal class HomePresenterTest { hasUnseenChatMessages = false, addonBannerInfo = null, isProduction = false, + resumableClaimId = null + ), ) } @@ -207,6 +210,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), showHelpCenter = false, travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -225,6 +230,8 @@ internal class HomePresenterTest { crossSellsAction = null, addonBannerInfo = null, isProduction = false, + resumableClaimId = null + ), ) } @@ -282,6 +289,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), crossSells = CrossSellSheetData(null, listOf()), travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()) @@ -317,6 +326,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), showHelpCenter = false, travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -333,6 +344,8 @@ internal class HomePresenterTest { crossSellsAction = null, addonBannerInfo = null, isProduction = false, + resumableClaimId = null + ), ) } @@ -371,6 +384,8 @@ internal class HomePresenterTest { ), showHelpCenter = false, travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -387,6 +402,8 @@ internal class HomePresenterTest { crossSellsAction = null, addonBannerInfo = null, isProduction = false, + resumableClaimId = null + ), ) } @@ -424,6 +441,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), showHelpCenter = false, travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -444,6 +463,7 @@ internal class HomePresenterTest { ), addonBannerInfo = null, isProduction = false, + resumableClaimId = null ), ) } @@ -474,6 +494,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), showHelpCenter = false, travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -490,6 +512,8 @@ internal class HomePresenterTest { crossSellsAction = null, addonBannerInfo = null, isProduction = false, + resumableClaimId = null + ), ) } @@ -520,6 +544,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), showHelpCenter = false, travelBannerInfo = null, + resumableClaimId = null + ).right(), ) assertThat(awaitItem()).isEqualTo( @@ -536,6 +562,8 @@ internal class HomePresenterTest { crossSellsAction = null, addonBannerInfo = null, isProduction = false, + resumableClaimId = null + ), ) } @@ -562,6 +590,8 @@ internal class HomePresenterTest { firstVetSections = listOf(), crossSells = CrossSellSheetData(null, emptyList()), travelBannerInfo = null, + resumableClaimId = null + ) }