BIT-1184: Add pending deletion state to display deletion dialog (#793)

This commit is contained in:
David Perez
2024-01-25 17:22:07 -06:00
committed by Álison Fernandes
parent 317cc7396e
commit 26335bf217
7 changed files with 166 additions and 14 deletions

View File

@@ -69,6 +69,11 @@ interface AuthRepository : AuthenticatorProvider {
*/
var hasPendingAccountAddition: Boolean
/**
* Clears the pending deletion state that occurs when the an account is successfully deleted.
*/
fun clearPendingAccountDeletion()
/**
* Attempt to delete the current account and logout them out upon success.
*/

View File

@@ -6,8 +6,8 @@ import com.bitwarden.crypto.Kdf
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.CaptchaRequired
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.TwoFactorRequired
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.Success
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.TwoFactorRequired
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
@@ -62,6 +62,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Singleton
@@ -86,7 +87,8 @@ class AuthRepositoryImpl(
dispatcherManager: DispatcherManager,
private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() },
) : AuthRepository {
private val mutableHasPendingAccountAdditionStateFlow = MutableStateFlow<Boolean>(false)
private val mutableHasPendingAccountAdditionStateFlow = MutableStateFlow(false)
private val mutableHasPendingAccountDeletionStateFlow = MutableStateFlow(false)
/**
* The auth information to make the identity token request will need to be
@@ -128,11 +130,13 @@ class AuthRepositoryImpl(
authDiskSource.userOrganizationsListFlow,
vaultRepository.vaultStateFlow,
mutableHasPendingAccountAdditionStateFlow,
mutableHasPendingAccountDeletionStateFlow,
) {
userStateJson,
userOrganizationsList,
vaultState,
hasPendingAccountAddition,
_,
->
userStateJson
?.toUserState(
@@ -142,6 +146,11 @@ class AuthRepositoryImpl(
vaultUnlockTypeProvider = ::getVaultUnlockType,
)
}
.filter {
// If there is a pending account deletion, continue showing
// the original UserState until it is confirmed.
!mutableHasPendingAccountDeletionStateFlow.value
}
.stateIn(
scope = collectionScope,
started = SharingStarted.Eagerly,
@@ -170,9 +179,14 @@ class AuthRepositoryImpl(
override var hasPendingAccountAddition: Boolean
by mutableHasPendingAccountAdditionStateFlow::value
override fun clearPendingAccountDeletion() {
mutableHasPendingAccountDeletionStateFlow.value = false
}
override suspend fun deleteAccount(password: String): DeleteAccountResult {
val profile = authDiskSource.userState?.activeAccount?.profile
?: return DeleteAccountResult.Error
mutableHasPendingAccountDeletionStateFlow.value = true
return authSdkSource
.hashPassword(
email = profile.email,
@@ -182,6 +196,7 @@ class AuthRepositoryImpl(
)
.flatMap { hashedPassword -> accountsService.deleteAccount(hashedPassword) }
.onSuccess { logout() }
.onFailure { clearPendingAccountDeletion() }
.fold(
onFailure = { DeleteAccountResult.Error },
onSuccess = { DeleteAccountResult.Success },

View File

@@ -67,6 +67,16 @@ fun DeleteAccountScreen(
}
when (val dialog = state.dialog) {
DeleteAccountState.DeleteAccountDialog.DeleteSuccess -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = null,
message = R.string.your_account_has_been_permanently_deleted.asText(),
),
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(DeleteAccountAction.AccountDeletionConfirm) }
},
)
is DeleteAccountState.DeleteAccountDialog.Error -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),

View File

@@ -43,6 +43,7 @@ class DeleteAccountViewModel @Inject constructor(
DeleteAccountAction.CancelClick -> handleCancelClick()
DeleteAccountAction.CloseClick -> handleCloseClick()
is DeleteAccountAction.DeleteAccountClick -> handleDeleteAccountClick(action)
DeleteAccountAction.AccountDeletionConfirm -> handleAccountDeletionConfirm()
DeleteAccountAction.DismissDialog -> handleDismissDialog()
is DeleteAccountAction.Internal.DeleteAccountComplete -> {
handleDeleteAccountComplete(action)
@@ -68,6 +69,11 @@ class DeleteAccountViewModel @Inject constructor(
}
}
private fun handleAccountDeletionConfirm() {
authRepository.clearPendingAccountDeletion()
mutableStateFlow.update { it.copy(dialog = null) }
}
private fun handleDismissDialog() {
mutableStateFlow.update { it.copy(dialog = null) }
}
@@ -77,8 +83,9 @@ class DeleteAccountViewModel @Inject constructor(
) {
when (action.result) {
DeleteAccountResult.Success -> {
mutableStateFlow.update { it.copy(dialog = null) }
// TODO: Display a dialog confirming account deletion (BIT-1184)
mutableStateFlow.update {
it.copy(dialog = DeleteAccountState.DeleteAccountDialog.DeleteSuccess)
}
}
DeleteAccountResult.Error -> {
@@ -106,6 +113,12 @@ data class DeleteAccountState(
* Displays a dialog.
*/
sealed class DeleteAccountDialog : Parcelable {
/**
* Dialog to confirm to the user that the account has been deleted.
*/
@Parcelize
data object DeleteSuccess : DeleteAccountDialog()
/**
* Displays the error dialog when deleting an account fails.
*/
@@ -160,6 +173,11 @@ sealed class DeleteAccountAction {
val masterPassword: String,
) : DeleteAccountAction()
/**
* The user has confirmed that their account has been deleted.
*/
data object AccountDeletionConfirm : DeleteAccountAction()
/**
* The user has clicked to dismiss the dialog.
*/