mirror of
https://github.com/bitwarden/android.git
synced 2025-02-17 04:19:54 +03:00
BIT-1670: Add the initial autofill dialog (#922)
This commit is contained in:
parent
77ac4b1956
commit
f380e21600
4 changed files with 105 additions and 2 deletions
|
@ -23,6 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||||
|
@ -136,6 +137,9 @@ fun VaultAddEditScreen(
|
||||||
onDismissRequest = remember(viewModel) {
|
onDismissRequest = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(VaultAddEditAction.Common.DismissDialog) }
|
{ viewModel.trySendAction(VaultAddEditAction.Common.DismissDialog) }
|
||||||
},
|
},
|
||||||
|
onAutofillDismissRequest = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(VaultAddEditAction.Common.InitialAutofillDialogDismissed) }
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (pendingDeleteCipher) {
|
if (pendingDeleteCipher) {
|
||||||
|
@ -267,6 +271,7 @@ fun VaultAddEditScreen(
|
||||||
private fun VaultAddEditItemDialogs(
|
private fun VaultAddEditItemDialogs(
|
||||||
dialogState: VaultAddEditState.DialogState?,
|
dialogState: VaultAddEditState.DialogState?,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
|
onAutofillDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
when (dialogState) {
|
when (dialogState) {
|
||||||
is VaultAddEditState.DialogState.Loading -> {
|
is VaultAddEditState.DialogState.Loading -> {
|
||||||
|
@ -285,6 +290,16 @@ private fun VaultAddEditItemDialogs(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is VaultAddEditState.DialogState.InitialAutofillPrompt -> {
|
||||||
|
BitwardenBasicDialog(
|
||||||
|
visibilityState = BasicDialogState.Shown(
|
||||||
|
title = R.string.bitwarden_autofill_service.asText(),
|
||||||
|
message = R.string.bitwarden_autofill_service_alert2.asText(),
|
||||||
|
),
|
||||||
|
onDismissRequest = onAutofillDismissRequest,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
null -> Unit
|
null -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
|
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||||
|
@ -75,6 +76,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
private val clipboardManager: BitwardenClipboardManager,
|
private val clipboardManager: BitwardenClipboardManager,
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
private val generatorRepository: GeneratorRepository,
|
private val generatorRepository: GeneratorRepository,
|
||||||
|
private val settingsRepository: SettingsRepository,
|
||||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||||
private val resourceManager: ResourceManager,
|
private val resourceManager: ResourceManager,
|
||||||
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
|
) : BaseViewModel<VaultAddEditState, VaultAddEditEvent, VaultAddEditAction>(
|
||||||
|
@ -90,6 +92,17 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
val autofillSelectionData = specialCircumstanceManager
|
val autofillSelectionData = specialCircumstanceManager
|
||||||
.specialCircumstance
|
.specialCircumstance
|
||||||
?.toAutofillSelectionDataOrNull()
|
?.toAutofillSelectionDataOrNull()
|
||||||
|
|
||||||
|
val dialogState =
|
||||||
|
if (!settingsRepository.initialAutofillDialogShown &&
|
||||||
|
vaultAddEditType is VaultAddEditType.AddItem &&
|
||||||
|
autofillSelectionData == null
|
||||||
|
) {
|
||||||
|
VaultAddEditState.DialogState.InitialAutofillPrompt
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
val defaultAddTypeContent = autofillSelectionData
|
val defaultAddTypeContent = autofillSelectionData
|
||||||
?.toDefaultAddTypeContent()
|
?.toDefaultAddTypeContent()
|
||||||
?: autofillSaveItem
|
?: autofillSaveItem
|
||||||
|
@ -106,7 +119,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
is VaultAddEditType.EditItem -> VaultAddEditState.ViewState.Loading
|
is VaultAddEditType.EditItem -> VaultAddEditState.ViewState.Loading
|
||||||
is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading
|
is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading
|
||||||
},
|
},
|
||||||
dialog = null,
|
dialog = dialogState,
|
||||||
// Set special conditions for autofill save
|
// Set special conditions for autofill save
|
||||||
shouldShowCloseButton = autofillSaveItem == null,
|
shouldShowCloseButton = autofillSaveItem == null,
|
||||||
shouldExitOnSave = autofillSaveItem != null,
|
shouldExitOnSave = autofillSaveItem != null,
|
||||||
|
@ -195,6 +208,9 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action)
|
is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action)
|
||||||
|
is VaultAddEditAction.Common.InitialAutofillDialogDismissed -> {
|
||||||
|
handleInitialAutofillDialogDismissed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +378,13 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleInitialAutofillDialogDismissed() {
|
||||||
|
settingsRepository.initialAutofillDialogShown = true
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialog = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAddNewCustomFieldClick(
|
private fun handleAddNewCustomFieldClick(
|
||||||
action: VaultAddEditAction.Common.AddNewCustomFieldClick,
|
action: VaultAddEditAction.Common.AddNewCustomFieldClick,
|
||||||
) {
|
) {
|
||||||
|
@ -1716,6 +1739,12 @@ data class VaultAddEditState(
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Loading(val label: Text) : DialogState()
|
data class Loading(val label: Text) : DialogState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the initial autofill dialog to the user.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data object InitialAutofillPrompt : DialogState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1815,6 +1844,11 @@ sealed class VaultAddEditAction {
|
||||||
*/
|
*/
|
||||||
data object AttachmentsClick : Common()
|
data object AttachmentsClick : Common()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user has dismissed the initial autofill dialog.
|
||||||
|
*/
|
||||||
|
data object InitialAutofillDialogDismissed : Common()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has clicked the move to organization overflow option.
|
* The user has clicked the move to organization overflow option.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -220,6 +220,45 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `clicking dismiss dialog button on InitialAutofillPrompt should send InitialAutofillDialogDismissed action`() {
|
||||||
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN_DIALOG.copy(
|
||||||
|
dialog = VaultAddEditState.DialogState.InitialAutofillPrompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onAllNodesWithText("Ok")
|
||||||
|
.filterToOne(hasAnyAncestor(isDialog()))
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultAddEditAction.Common.InitialAutofillDialogDismissed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `InitialAutofillPrompt is shown according to state`() {
|
||||||
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN
|
||||||
|
|
||||||
|
composeTestRule.assertNoDialogExists()
|
||||||
|
|
||||||
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN_DIALOG.copy(
|
||||||
|
dialog = VaultAddEditState.DialogState.InitialAutofillPrompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onAllNodesWithText("Bitwarden Auto-fill Service")
|
||||||
|
.filterToOne(hasAnyAncestor(isDialog()))
|
||||||
|
.assertIsDisplayed()
|
||||||
|
|
||||||
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN_DIALOG.copy(
|
||||||
|
dialog = VaultAddEditState.DialogState.Loading("Loading".asText()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking dismiss dialog button should send DismissDialog action`() {
|
fun `clicking dismiss dialog button should send DismissDialog action`() {
|
||||||
mutableStateFlow.value = DEFAULT_STATE_LOGIN_DIALOG
|
mutableStateFlow.value = DEFAULT_STATE_LOGIN_DIALOG
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
@ -70,6 +71,10 @@ import java.util.UUID
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class VaultAddEditViewModelTest : BaseViewModelTest() {
|
class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
private val settingsRepository: SettingsRepository = mockk {
|
||||||
|
every { initialAutofillDialogShown = any() } just runs
|
||||||
|
every { initialAutofillDialogShown } returns true
|
||||||
|
}
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(createUserState())
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(createUserState())
|
||||||
private val authRepository: AuthRepository = mockk {
|
private val authRepository: AuthRepository = mockk {
|
||||||
every { userStateFlow } returns mutableUserStateFlow
|
every { userStateFlow } returns mutableUserStateFlow
|
||||||
|
@ -1769,6 +1774,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
resourceManager = resourceManager,
|
resourceManager = resourceManager,
|
||||||
authRepository = authRepository,
|
authRepository = authRepository,
|
||||||
|
settingsRepository = settingsRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2195,8 +2201,16 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `InitialAutofillDialogDismissed should update the settings value to true`() {
|
||||||
|
viewModel.trySendAction(VaultAddEditAction.Common.InitialAutofillDialogDismissed)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
settingsRepository.initialAutofillDialogShown = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
//region Helper functions
|
//region Helper functions
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
|
@ -2301,6 +2315,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
resourceManager = bitwardenResourceManager,
|
resourceManager = bitwardenResourceManager,
|
||||||
authRepository = authRepository,
|
authRepository = authRepository,
|
||||||
|
settingsRepository = settingsRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createVaultData(
|
private fun createVaultData(
|
||||||
|
|
Loading…
Add table
Reference in a new issue