diff --git a/app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryInsuranceContracts.graphql b/app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryInsuranceContracts.graphql index 4f4e126780..5cbcf56193 100644 --- a/app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryInsuranceContracts.graphql +++ b/app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryInsuranceContracts.graphql @@ -36,7 +36,7 @@ fragment AgreementFragment on Agreement { lastName ssn birthdate - needsMissingInfo + hasMissingInfo } displayItems { ...AgreementDisplayItemFragment diff --git a/app/feature/feature-edit-coinsured/src/main/graphql/QueryCoInsured.graphql b/app/feature/feature-edit-coinsured/src/main/graphql/QueryCoInsured.graphql index 12eb1632b4..6712036a32 100644 --- a/app/feature/feature-edit-coinsured/src/main/graphql/QueryCoInsured.graphql +++ b/app/feature/feature-edit-coinsured/src/main/graphql/QueryCoInsured.graphql @@ -25,6 +25,6 @@ fragment CoInsuredAgreementFragment on Agreement { lastName birthdate ssn - needsMissingInfo + hasMissingInfo } } diff --git a/app/feature/feature-edit-coinsured/src/main/graphql/QueryPersonalInformation.graphql b/app/feature/feature-edit-coinsured/src/main/graphql/QueryPersonalInformation.graphql new file mode 100644 index 0000000000..e52684879b --- /dev/null +++ b/app/feature/feature-edit-coinsured/src/main/graphql/QueryPersonalInformation.graphql @@ -0,0 +1,6 @@ +query PersonalInformation($ssn: String!) { + personalInformation(input: { personalNumber: $ssn }) { + firstName + lastName + } +} diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/CoInsured.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/CoInsured.kt index 376e47caef..67dc27b461 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/CoInsured.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/CoInsured.kt @@ -25,4 +25,14 @@ internal data class CoInsured( fun identifier(dateTimeFormatter: DateTimeFormatter): String? = ssn ?: birthDate?.toJavaLocalDate()?.format(dateTimeFormatter) + + companion object { + fun fromPersonalInformation(personalInformation: CoInsuredPersonalInformation, ssn: String): CoInsured = CoInsured( + firstName = personalInformation.firstName, + lastName = personalInformation.lastName, + birthDate = null, + ssn = ssn, + hasMissingInfo = false, + ) + } } diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/FetchCoInsuredPersonalInformationUseCase.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/FetchCoInsuredPersonalInformationUseCase.kt new file mode 100644 index 0000000000..81767cee44 --- /dev/null +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/FetchCoInsuredPersonalInformationUseCase.kt @@ -0,0 +1,39 @@ +package com.hedvig.android.feature.editcoinsured.data + +import arrow.core.Either +import arrow.core.raise.either +import arrow.core.raise.ensureNotNull +import com.apollographql.apollo3.ApolloClient +import com.hedvig.android.apollo.safeExecute +import com.hedvig.android.apollo.toEither +import com.hedvig.android.core.common.ErrorMessage +import octopus.PersonalInformationQuery + +internal interface FetchCoInsuredPersonalInformationUseCase { + suspend fun invoke(ssn: String): Either +} + +internal class FetchCoInsuredPersonalInformationUseCaseImpl( + private val apolloClient: ApolloClient, +) : FetchCoInsuredPersonalInformationUseCase { + override suspend fun invoke(ssn: String): Either = either { + val result = apolloClient.query(PersonalInformationQuery(ssn)) + .safeExecute() + .toEither(::ErrorMessage) + .bind() + + ensureNotNull(result.personalInformation) { + ErrorMessage("No personal information found") + } + + CoInsuredPersonalInformation( + firstName = result.personalInformation.firstName, + lastName = result.personalInformation.lastName, + ) + } +} + +internal data class CoInsuredPersonalInformation( + val firstName: String, + val lastName: String, +) diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/GetCoInsuredUseCase.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/GetCoInsuredUseCase.kt index 01dea26db8..1e9b3c20c0 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/GetCoInsuredUseCase.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/GetCoInsuredUseCase.kt @@ -32,14 +32,16 @@ internal class GetCoInsuredUseCaseImpl( CoInsuredError.ContractNotFound } - val currentCoInsured = contract.currentAgreement.coInsured?.map { - CoInsured( - it.firstName, - it.lastName, - it.birthdate, - it.ssn, - it.needsMissingInfo, - ) + val currentCoInsured = contract.let { + it.currentAgreement.coInsured?.map { + CoInsured( + it.firstName, + it.lastName, + it.birthdate, + it.ssn, + it.hasMissingInfo, + ) + } } CoInsuredResult( diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/Member.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/Member.kt index 5aa35c99b9..1e9b1449a1 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/Member.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/data/Member.kt @@ -3,7 +3,7 @@ package com.hedvig.android.feature.editcoinsured.data internal data class Member( val firstName: String, val lastName: String, - val ssn: String? + val ssn: String?, ) { val displayName: String = "$firstName $lastName" } diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/EditCoInsuredModule.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/EditCoInsuredModule.kt index 8aa70045a0..9509cf931d 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/EditCoInsuredModule.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/EditCoInsuredModule.kt @@ -2,31 +2,32 @@ package com.hedvig.android.feature.editcoinsured.di import com.apollographql.apollo3.ApolloClient import com.hedvig.android.apollo.octopus.di.octopusClient -import com.hedvig.android.core.demomode.DemoManager +import com.hedvig.android.feature.editcoinsured.data.FetchCoInsuredPersonalInformationUseCase +import com.hedvig.android.feature.editcoinsured.data.FetchCoInsuredPersonalInformationUseCaseImpl +import com.hedvig.android.feature.editcoinsured.data.GetCoInsuredUseCase import com.hedvig.android.feature.editcoinsured.data.GetCoInsuredUseCaseImpl import com.hedvig.android.feature.editcoinsured.ui.EditCoInsuredViewModel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val editCoInsuredModule = module { - single { + single { GetCoInsuredUseCaseImpl( get(octopusClient), ) } - single { - GetCoInsuredUseCaseProvider( - demoManager = get(), - prodImpl = get(), - demoImpl = get(), + single { + FetchCoInsuredPersonalInformationUseCaseImpl( + get(octopusClient), ) } viewModel { (contractId: String) -> EditCoInsuredViewModel( contractId, - get(), + get(), + get(), ) } } diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/GetCoInsuredUseCaseProvider.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/GetCoInsuredUseCaseProvider.kt deleted file mode 100644 index 5307358dbb..0000000000 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/di/GetCoInsuredUseCaseProvider.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.hedvig.android.feature.editcoinsured.di - -import com.hedvig.android.core.demomode.DemoManager -import com.hedvig.android.core.demomode.ProdOrDemoProvider -import com.hedvig.android.feature.editcoinsured.data.GetCoInsuredUseCase - -internal class GetCoInsuredUseCaseProvider( - override val demoManager: DemoManager, - override val demoImpl: GetCoInsuredUseCase, - override val prodImpl: GetCoInsuredUseCase, -) : ProdOrDemoProvider diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/navigation/EditCoInsuredGraph.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/navigation/EditCoInsuredGraph.kt index 5c94182a0b..bea568da11 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/navigation/EditCoInsuredGraph.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/navigation/EditCoInsuredGraph.kt @@ -10,7 +10,6 @@ fun NavGraphBuilder.editCoInsuredGraph(navigateUp: () -> Unit) { composable { backStackEntry -> EditCoInsuredDestination( koinViewModel { parametersOf(contractId) }, - contractId, allowEdit, navigateUp, ) diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/AddCoInsuredBottomSheetContent.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/AddCoInsuredBottomSheetContent.kt new file mode 100644 index 0000000000..a6436b20c7 --- /dev/null +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/AddCoInsuredBottomSheetContent.kt @@ -0,0 +1,152 @@ +package com.hedvig.android.feature.editcoinsured.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import com.hedvig.android.core.designsystem.component.button.HedvigContainedButton +import com.hedvig.android.core.designsystem.component.button.HedvigTextButton +import com.hedvig.android.core.designsystem.component.textfield.HedvigTextField +import com.hedvig.android.core.designsystem.preview.HedvigPreview +import com.hedvig.android.core.designsystem.theme.HedvigTheme +import com.hedvig.android.feature.editcoinsured.data.CoInsured +import hedvig.resources.R + +@Composable +internal fun AddCoInsuredBottomSheetContent( + onSave: (CoInsured) -> Unit, + onFetchInfo: (ssn: String) -> Unit, + onDismiss: () -> Unit, + isLoading: Boolean, + errorMessage: String?, + coInsured: CoInsured?, +) { + var ssn by remember { mutableStateOf("") } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 16.dp), + ) { + Spacer(Modifier.height(16.dp)) + Text(stringResource(id = R.string.CONTRACT_ADD_COINSURED)) + Spacer(Modifier.height(24.dp)) + HedvigTextField( + value = ssn, + label = { + Text(stringResource(id = R.string.CONTRACT_PERSONAL_IDENTITY)) + }, + onValueChange = { text: String -> + ssn = text + }, + errorText = errorMessage, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { + onFetchInfo(ssn) + }, + ), + withNewDesign = true, + modifier = Modifier.fillMaxWidth(), + ) + AnimatedVisibility( + visible = coInsured != null, + modifier = Modifier.padding(top = 4.dp), + ) { + HedvigTextField( + value = coInsured?.displayName ?: "", + onValueChange = {}, + label = { + Text(stringResource(id = R.string.FULL_NAME_TEXT)) + }, + enabled = false, + withNewDesign = true, + modifier = Modifier.fillMaxWidth(), + ) + } + Spacer(Modifier.height(16.dp)) + HedvigContainedButton( + text = if (coInsured != null) { + stringResource(id = R.string.CONTRACT_ADD_COINSURED) + } else { + stringResource(id = R.string.CONTRACT_SSN_FETCH_INFO) + }, + enabled = ssn.isNotBlank(), + onClick = { + if (coInsured != null) { + onSave(coInsured) + } else { + onFetchInfo(ssn) + } + }, + isLoading = isLoading, + ) + Spacer(Modifier.height(8.dp)) + HedvigTextButton( + onClick = onDismiss, + text = stringResource(id = R.string.general_cancel_button), + modifier = Modifier.fillMaxWidth(), + ) + Spacer(Modifier.height(32.dp)) + } +} + +@Composable +@HedvigPreview +private fun AddCoInsuredBottomSheetContentPreview() { + HedvigTheme { + Surface(color = MaterialTheme.colorScheme.background) { + AddCoInsuredBottomSheetContent( + onFetchInfo = {}, + onSave = {}, + onDismiss = {}, + isLoading = false, + coInsured = null, + errorMessage = null, + ) + } + } +} + +@Composable +@HedvigPreview +private fun AddCoInsuredBottomSheetContentWithCoInsuredPreview() { + HedvigTheme { + Surface(color = MaterialTheme.colorScheme.background) { + AddCoInsuredBottomSheetContent( + onFetchInfo = {}, + onSave = {}, + onDismiss = {}, + isLoading = false, + coInsured = CoInsured( + "Tester", + "Testersson", + birthDate = null, + ssn = "144412022193", + hasMissingInfo = false, + ), + errorMessage = null, + ) + } + } +} diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredDestination.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredDestination.kt index 843632c39c..2a32ffe9f1 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredDestination.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredDestination.kt @@ -1,41 +1,62 @@ package com.hedvig.android.feature.editcoinsured.ui import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Surface +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.hedvig.android.core.designsystem.component.button.HedvigContainedButton +import com.hedvig.android.core.designsystem.component.progress.HedvigFullScreenCenterAlignedProgressDebounced +import com.hedvig.android.core.designsystem.material3.squircleLargeTop import com.hedvig.android.core.designsystem.preview.HedvigPreview import com.hedvig.android.core.designsystem.theme.HedvigTheme import com.hedvig.android.core.ui.appbar.m3.TopAppBarWithBack import com.hedvig.android.core.ui.rememberHedvigDateTimeFormatter import com.hedvig.android.feature.editcoinsured.data.CoInsured import com.hedvig.android.feature.editcoinsured.data.Member +import hedvig.resources.R import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.launch import kotlinx.datetime.LocalDate -import org.koin.androidx.compose.koinViewModel -import org.koin.core.parameter.parametersOf @Composable internal fun EditCoInsuredDestination( viewModel: EditCoInsuredViewModel, - contractId: String, allowEdit: Boolean, navigateUp: () -> Unit, ) { - val viewModel: EditCoInsuredViewModel = koinViewModel { parametersOf(contractId) } - val uiState by viewModel.uiState.collectAsStateWithLifecycle() EditCoInsuredScreen( - navigateUp, - allowEdit, - uiState, + navigateUp = navigateUp, + allowEdit = allowEdit, + uiState = uiState, + onSave = { + viewModel.emit(EditCoInsuredEvent.AddCoInsured(it)) + }, + onFetchInfo = { + viewModel.emit(EditCoInsuredEvent.FetchCoInsuredPersonalInformation(it)) + }, + onResetBottomSheetState = { + viewModel.emit(EditCoInsuredEvent.ResetBottomSheetState) + }, ) } @@ -44,24 +65,80 @@ private fun EditCoInsuredScreen( navigateUp: () -> Unit, allowEdit: Boolean, uiState: EditCoInsuredState, + onSave: (CoInsured) -> Unit, + onFetchInfo: (ssn: String) -> Unit, + onResetBottomSheetState: () -> Unit, ) { Column(Modifier.fillMaxSize()) { TopAppBarWithBack( - title = stringResource(id = hedvig.resources.R.string.COINSURED_EDIT_TITLE), + title = stringResource(id = R.string.COINSURED_EDIT_TITLE), onClick = navigateUp, ) - CoInsuredList(uiState, allowEdit) + + when (uiState) { + is EditCoInsuredState.Error -> {} + is EditCoInsuredState.Loaded -> { + val coroutineScope = rememberCoroutineScope() + var showAddCoInsuredBottomSheet by rememberSaveable { mutableStateOf(false) } + if (showAddCoInsuredBottomSheet) { + val sheetState = rememberModalBottomSheetState(true) + ModalBottomSheet( + containerColor = MaterialTheme.colorScheme.background, + onDismissRequest = { + showAddCoInsuredBottomSheet = false + onResetBottomSheetState() + }, + shape = MaterialTheme.shapes.squircleLargeTop, + sheetState = sheetState, + tonalElevation = 0.dp, + windowInsets = BottomSheetDefaults.windowInsets.only(WindowInsetsSides.Top), + ) { + AddCoInsuredBottomSheetContent( + onSave = onSave, + onFetchInfo = onFetchInfo, + onDismiss = { + coroutineScope.launch { + sheetState.hide() + }.invokeOnCompletion { + showAddCoInsuredBottomSheet = false + onResetBottomSheetState() + } + }, + isLoading = uiState.bottomSheetState.isLoadingPersonalInfo, + coInsured = uiState.bottomSheetState.coInsuredFromSsn, + errorMessage = uiState.bottomSheetState.coInsuredFromSsnError, + ) + } + } + + Column { + CoInsuredList(uiState.listState, allowEdit) + if (!allowEdit) { + Spacer(Modifier.height(8.dp)) + HedvigContainedButton( + text = stringResource(id = R.string.CONTRACT_ADD_COINSURED), + onClick = { + showAddCoInsuredBottomSheet = true + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + } + } + + EditCoInsuredState.Loading -> HedvigFullScreenCenterAlignedProgressDebounced() + } } } @Composable -private fun CoInsuredList(uiState: EditCoInsuredState, allowEdit: Boolean) { +private fun CoInsuredList(uiState: EditCoInsuredState.Loaded.CoInsuredListState, allowEdit: Boolean) { val dateTimeFormatter = rememberHedvigDateTimeFormatter() Column { uiState.member?.let { InsuredRow( displayName = it.displayName, - details = it.ssn, + identifier = it.ssn ?: "", hasMissingInfo = false, allowEdit = false, isMember = true, @@ -76,8 +153,8 @@ private fun CoInsuredList(uiState: EditCoInsuredState, allowEdit: Boolean) { } InsuredRow( - displayName = coInsured.displayName, - details = coInsured.identifier(dateTimeFormatter), + displayName = coInsured.displayName.ifBlank { stringResource(id = R.string.CONTRACT_COINSURED) }, + identifier = coInsured.identifier(dateTimeFormatter) ?: stringResource(id = R.string.CONTRACT_NO_INFORMATION), hasMissingInfo = coInsured.hasMissingInfo, isMember = false, allowEdit = allowEdit, @@ -96,31 +173,38 @@ private fun EditCoInsuredScreenEditablePreview() { EditCoInsuredScreen( navigateUp = { }, allowEdit = true, - uiState = EditCoInsuredState( - isLoading = false, - errorMessage = null, - member = Member( - firstName = "Member", - lastName = "Membersson", - ssn = "197312331093", - ), - coInsured = persistentListOf( - CoInsured( - "Test", - "Testersson", - LocalDate.fromEpochDays(300), - "19910113-1093", - hasMissingInfo = false, + uiState = EditCoInsuredState.Loaded( + listState = EditCoInsuredState.Loaded.CoInsuredListState( + coInsured = persistentListOf( + CoInsured( + "Test", + "Testersson", + LocalDate.fromEpochDays(300), + "19910113-1093", + hasMissingInfo = false, + ), + CoInsured( + null, + null, + null, + null, + hasMissingInfo = true, + ), ), - CoInsured( - null, - null, - null, - null, - hasMissingInfo = true, + member = Member( + firstName = "Member", + lastName = "Membersson", + ssn = "197312331093", ), ), + bottomSheetState = EditCoInsuredState.Loaded.BottomSheetState( + coInsuredFromSsn = null, + isLoadingPersonalInfo = false, + ), ), + onSave = {}, + onFetchInfo = {}, + onResetBottomSheetState = {}, ) } } @@ -134,31 +218,38 @@ private fun EditCoInsuredScreenNonEditablePreview() { EditCoInsuredScreen( navigateUp = { }, allowEdit = false, - uiState = EditCoInsuredState( - isLoading = false, - errorMessage = null, - member = Member( - firstName = "Member", - lastName = "Membersson", - ssn = "197312331093", - ), - coInsured = persistentListOf( - CoInsured( - "Test", - "Testersson", - LocalDate.fromEpochDays(300), - "19910113-1093", - hasMissingInfo = false, + uiState = EditCoInsuredState.Loaded( + listState = EditCoInsuredState.Loaded.CoInsuredListState( + coInsured = persistentListOf( + CoInsured( + "Test", + "Testersson", + LocalDate.fromEpochDays(300), + "19910113-1093", + hasMissingInfo = false, + ), + CoInsured( + null, + null, + null, + null, + hasMissingInfo = true, + ), ), - CoInsured( - null, - null, - null, - null, - hasMissingInfo = true, + member = Member( + firstName = "Member", + lastName = "Membersson", + ssn = "197312331093", ), ), + bottomSheetState = EditCoInsuredState.Loaded.BottomSheetState( + coInsuredFromSsn = null, + isLoadingPersonalInfo = false, + ), ), + onSave = {}, + onFetchInfo = {}, + onResetBottomSheetState = {}, ) } } diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredPresenter.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredPresenter.kt index 65d1e29ecd..400b2933ea 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredPresenter.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredPresenter.kt @@ -7,9 +7,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.Snapshot -import com.hedvig.android.core.demomode.Provider +import com.hedvig.android.core.common.safeCast import com.hedvig.android.feature.editcoinsured.data.CoInsured import com.hedvig.android.feature.editcoinsured.data.CoInsuredError +import com.hedvig.android.feature.editcoinsured.data.FetchCoInsuredPersonalInformationUseCase import com.hedvig.android.feature.editcoinsured.data.GetCoInsuredUseCase import com.hedvig.android.feature.editcoinsured.data.Member import com.hedvig.android.molecule.public.MoleculePresenter @@ -19,51 +20,128 @@ import kotlinx.collections.immutable.persistentListOf internal class EditCoInsuredPresenter( private val contractId: String, - private val getCoInsuredUseCaseProvider: Provider, + private val getCoInsuredUseCaseProvider: GetCoInsuredUseCase, + private val fetchCoInsuredPersonalInformationUseCaseProvider: FetchCoInsuredPersonalInformationUseCase, ) : MoleculePresenter { @Composable override fun MoleculePresenterScope.present(lastState: EditCoInsuredState): EditCoInsuredState { - var errorMessage by remember { mutableStateOf(lastState.errorMessage) } - var isLoading by remember { mutableStateOf(lastState.isLoading) } - var coInsured by remember { mutableStateOf(lastState.coInsured) } - var member by remember { mutableStateOf(lastState.member) } + var errorMessage by remember { mutableStateOf(null) } + var isLoading by remember { mutableStateOf(false) } + var coInsured by remember { + val lastCoInsured = lastState.safeCast()?.listState?.coInsured + val coInsured = lastCoInsured ?: persistentListOf() + mutableStateOf(coInsured) + } + var coInsuredFromSsn by remember { mutableStateOf(null) } + var coInsuredFromSsnError by remember { mutableStateOf(null) } + var member by remember { mutableStateOf(lastState.safeCast()?.listState?.member) } + var ssnQuery by remember { mutableStateOf(null) } LaunchedEffect(Unit) { + if (member != null) { + return@LaunchedEffect + } + isLoading = true - getCoInsuredUseCaseProvider.provide().invoke(contractId).fold( - ifLeft = { - Snapshot.withMutableSnapshot { - isLoading = false - errorMessage = when (it) { - CoInsuredError.ContractNotFound -> "Could not find contract" - is CoInsuredError.GenericError -> it.message + getCoInsuredUseCaseProvider.invoke(contractId) + .fold( + ifLeft = { + Snapshot.withMutableSnapshot { + isLoading = false + errorMessage = when (it) { + CoInsuredError.ContractNotFound -> "Could not find contract" + is CoInsuredError.GenericError -> it.message + } + } + }, + ifRight = { + Snapshot.withMutableSnapshot { + errorMessage = null + isLoading = false + coInsured = it.coInsured + member = it.member } - } - }, - ifRight = { - Snapshot.withMutableSnapshot { - errorMessage = null - isLoading = false - coInsured = it.coInsured - member = it.member - } - }, + }, + ) + } + + CollectEvents { event -> + when (event) { + is EditCoInsuredEvent.FetchCoInsuredPersonalInformation -> ssnQuery = event.ssn + is EditCoInsuredEvent.AddCoInsured -> {} + is EditCoInsuredEvent.RemoveCoInsured -> {} + EditCoInsuredEvent.ResetBottomSheetState -> Snapshot.withMutableSnapshot { + coInsuredFromSsn = null + coInsuredFromSsnError = null + ssnQuery = null + } + } + } + + LaunchedEffect(ssnQuery) { + ssnQuery?.let { ssnQuery -> + fetchCoInsuredPersonalInformationUseCaseProvider.invoke(ssnQuery) + .fold( + ifLeft = { coInsuredFromSsnError = it.message }, + ifRight = { + Snapshot.withMutableSnapshot { + coInsuredFromSsn = CoInsured.fromPersonalInformation(it, ssnQuery) + coInsuredFromSsnError = null + } + }, + ) + } + } + + return if (isLoading) { + EditCoInsuredState.Loading + } else if (errorMessage != null) { + EditCoInsuredState.Error(errorMessage) + } else if (coInsured.isNotEmpty() || member != null) { + EditCoInsuredState.Loaded( + listState = EditCoInsuredState.Loaded.CoInsuredListState( + coInsured = coInsured, + member = member, + ), + bottomSheetState = EditCoInsuredState.Loaded.BottomSheetState( + coInsuredFromSsn = coInsuredFromSsn, + coInsuredFromSsnError = coInsuredFromSsnError, + ), ) + } else { + EditCoInsuredState.Error("Could not fetch co-insured or member") } - return EditCoInsuredState( - isLoading = isLoading, - errorMessage = errorMessage, - coInsured = coInsured, - member = member - ) } } -internal sealed interface EditCoInsuredEvent +internal sealed interface EditCoInsuredEvent { + data class FetchCoInsuredPersonalInformation(val ssn: String) : EditCoInsuredEvent + + data class AddCoInsured(val coInsured: CoInsured) : EditCoInsuredEvent -internal data class EditCoInsuredState( - val isLoading: Boolean = false, - val errorMessage: String? = null, - val coInsured: ImmutableList = persistentListOf(), - val member: Member? = null, -) + data class RemoveCoInsured(val coInsured: CoInsured) : EditCoInsuredEvent + + data object ResetBottomSheetState : EditCoInsuredEvent +} + +internal sealed interface EditCoInsuredState { + data object Loading : EditCoInsuredState + + data class Error(val message: String?) : EditCoInsuredState + + data class Loaded( + val listState: CoInsuredListState, + val bottomSheetState: BottomSheetState, + ) : EditCoInsuredState { + data class CoInsuredListState( + val coInsured: ImmutableList = persistentListOf(), + val member: Member? = null, + ) + + data class BottomSheetState( + val coInsuredFromSsn: CoInsured? = null, + val coInsuredFromSsnError: String? = null, + val isLoadingPersonalInfo: Boolean = false, + ) + } +} diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredViewModel.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredViewModel.kt index 72b2d29d35..02ce5ed87e 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredViewModel.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/EditCoInsuredViewModel.kt @@ -1,13 +1,18 @@ package com.hedvig.android.feature.editcoinsured.ui -import com.hedvig.android.core.demomode.Provider +import com.hedvig.android.feature.editcoinsured.data.FetchCoInsuredPersonalInformationUseCase import com.hedvig.android.feature.editcoinsured.data.GetCoInsuredUseCase import com.hedvig.android.molecule.android.MoleculeViewModel internal class EditCoInsuredViewModel( contractId: String, - getCoInsuredUseCaseProvider: Provider, + getCoInsuredUseCase: GetCoInsuredUseCase, + fetchCoInsuredPersonalInformationUseCase: FetchCoInsuredPersonalInformationUseCase, ) : MoleculeViewModel( - EditCoInsuredState(), - EditCoInsuredPresenter(contractId, getCoInsuredUseCaseProvider), + EditCoInsuredState.Loading, + EditCoInsuredPresenter( + contractId = contractId, + getCoInsuredUseCaseProvider = getCoInsuredUseCase, + fetchCoInsuredPersonalInformationUseCaseProvider = fetchCoInsuredPersonalInformationUseCase, + ), ) diff --git a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/InsuredRow.kt b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/InsuredRow.kt index 93e54ff427..4c779c28ac 100644 --- a/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/InsuredRow.kt +++ b/app/feature/feature-edit-coinsured/src/main/kotlin/com/hedvig/android/feature/editcoinsured/ui/InsuredRow.kt @@ -29,8 +29,8 @@ import hedvig.resources.R @Composable internal fun InsuredRow( - displayName: String?, - details: String?, + displayName: String, + identifier: String, hasMissingInfo: Boolean, allowEdit: Boolean, isMember: Boolean, @@ -44,34 +44,19 @@ internal fun InsuredRow( modifier = Modifier.padding(vertical = 16.dp), ) { Column { - if (displayName != null) { - Text( - text = displayName, - color = if (isMember) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - Color.Unspecified - }, - ) - } else { - Text(stringResource(id = R.string.CONTRACT_COINSURED)) - } + Text( + text = displayName, + color = if (isMember) { + MaterialTheme.colorScheme.onSurfaceVariant + } else { + Color.Unspecified + }, + ) - if (details != null) { - Text( - text = details, - color = if (isMember) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - MaterialTheme.colorScheme.onSurfaceVariant - }, - ) - } else { - Text( - text = stringResource(id = R.string.CONTRACT_NO_INFORMATION), - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + Text( + text = identifier, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) } } }, @@ -135,7 +120,7 @@ private fun InsuredRowPreviewEditable() { Surface { InsuredRow( displayName = "Test testersson", - details = "182312041933", + identifier = "182312041933", hasMissingInfo = false, isMember = false, onRemove = {}, @@ -153,7 +138,7 @@ private fun InsuredRowPreviewMissingInfo() { Surface { InsuredRow( displayName = "Test testersson", - details = "182312041933", + identifier = "182312041933", hasMissingInfo = true, isMember = false, onRemove = {}, @@ -171,7 +156,7 @@ private fun InsuredRowPreviewMember() { Surface { InsuredRow( displayName = "Test testersson", - details = "182312041933", + identifier = "182312041933", hasMissingInfo = false, isMember = true, onRemove = {}, @@ -181,5 +166,3 @@ private fun InsuredRowPreviewMember() { } } } - - diff --git a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt index f7da79b90b..264a881d85 100644 --- a/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt +++ b/app/feature/feature-insurances/src/main/kotlin/com/hedvig/android/feature/insurances/data/GetInsuranceContractsUseCase.kt @@ -128,5 +128,5 @@ private fun AgreementFragment.CoInsured.toCoInsured(): InsuranceAgreement.CoInsu ssn = ssn, birthDate = birthdate, activeFrom = null, - hasMissingInfo = needsMissingInfo, + hasMissingInfo = hasMissingInfo, ) diff --git a/app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryNeedsCoInsuredInfoReminder.graphql b/app/member-reminders/member-reminders-public/src/main/graphql/QueryNeedsCoInsuredInfoReminder.graphql similarity index 80% rename from app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryNeedsCoInsuredInfoReminder.graphql rename to app/member-reminders/member-reminders-public/src/main/graphql/QueryNeedsCoInsuredInfoReminder.graphql index 174c3464e5..e6a4ae3022 100644 --- a/app/apollo/apollo-octopus-public/src/main/graphql/com/hedvig/android/apollo/octopus/graphql/insurance/QueryNeedsCoInsuredInfoReminder.graphql +++ b/app/member-reminders/member-reminders-public/src/main/graphql/QueryNeedsCoInsuredInfoReminder.graphql @@ -4,12 +4,12 @@ query NeedsCoInsuredInfoReminder { id currentAgreement { coInsured { - needsMissingInfo + hasMissingInfo } } upcomingChangedAgreement { coInsured { - needsMissingInfo + hasMissingInfo } } } diff --git a/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetNeedsCoInsuredInfoRemindersUseCase.kt b/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetNeedsCoInsuredInfoRemindersUseCase.kt index 7ef1ac7a3e..7319c1c858 100644 --- a/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetNeedsCoInsuredInfoRemindersUseCase.kt +++ b/app/member-reminders/member-reminders-public/src/main/kotlin/com/hedvig/android/memberreminders/GetNeedsCoInsuredInfoRemindersUseCase.kt @@ -40,8 +40,8 @@ internal class GetNeedsCoInsuredInfoRemindersUseCaseImpl( } private fun NeedsCoInsuredInfoReminderQuery.Data.CurrentMember.ActiveContract.hasMissingInfo() = - (currentAgreement.coInsured?.filter { it.needsMissingInfo }?.size ?: 0) > 0 || - (upcomingChangedAgreement?.coInsured?.filter { it.needsMissingInfo }?.size ?: 0) > 0 + (currentAgreement.coInsured?.filter { it.hasMissingInfo }?.size ?: 0) > 0 || + (upcomingChangedAgreement?.coInsured?.filter { it.hasMissingInfo }?.size ?: 0) > 0 } sealed interface CoInsuredInfoReminderError {