diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index c92b4f43c..3ba54d725 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -23,6 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R 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.BitwardenBasicDialog import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent @@ -136,6 +137,9 @@ fun VaultAddEditScreen( onDismissRequest = remember(viewModel) { { viewModel.trySendAction(VaultAddEditAction.Common.DismissDialog) } }, + onAutofillDismissRequest = remember(viewModel) { + { viewModel.trySendAction(VaultAddEditAction.Common.InitialAutofillDialogDismissed) } + }, ) if (pendingDeleteCipher) { @@ -267,6 +271,7 @@ fun VaultAddEditScreen( private fun VaultAddEditItemDialogs( dialogState: VaultAddEditState.DialogState?, onDismissRequest: () -> Unit, + onAutofillDismissRequest: () -> Unit, ) { when (dialogState) { 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 } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index bea7e544c..e0a103e57 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -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.util.toAutofillSaveItemOrNull 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.util.takeUntilLoaded import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository @@ -75,6 +76,7 @@ class VaultAddEditViewModel @Inject constructor( private val clipboardManager: BitwardenClipboardManager, private val vaultRepository: VaultRepository, private val generatorRepository: GeneratorRepository, + private val settingsRepository: SettingsRepository, private val specialCircumstanceManager: SpecialCircumstanceManager, private val resourceManager: ResourceManager, ) : BaseViewModel( @@ -90,6 +92,17 @@ class VaultAddEditViewModel @Inject constructor( val autofillSelectionData = specialCircumstanceManager .specialCircumstance ?.toAutofillSelectionDataOrNull() + + val dialogState = + if (!settingsRepository.initialAutofillDialogShown && + vaultAddEditType is VaultAddEditType.AddItem && + autofillSelectionData == null + ) { + VaultAddEditState.DialogState.InitialAutofillPrompt + } else { + null + } + val defaultAddTypeContent = autofillSelectionData ?.toDefaultAddTypeContent() ?: autofillSaveItem @@ -106,7 +119,7 @@ class VaultAddEditViewModel @Inject constructor( is VaultAddEditType.EditItem -> VaultAddEditState.ViewState.Loading is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading }, - dialog = null, + dialog = dialogState, // Set special conditions for autofill save shouldShowCloseButton = autofillSaveItem == null, shouldExitOnSave = autofillSaveItem != null, @@ -195,6 +208,9 @@ class VaultAddEditViewModel @Inject constructor( ) 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( action: VaultAddEditAction.Common.AddNewCustomFieldClick, ) { @@ -1716,6 +1739,12 @@ data class VaultAddEditState( */ @Parcelize 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() + /** + * The user has dismissed the initial autofill dialog. + */ + data object InitialAutofillDialogDismissed : Common() + /** * The user has clicked the move to organization overflow option. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index ed885af7b..03fa4faba 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -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 fun `clicking dismiss dialog button should send DismissDialog action`() { mutableStateFlow.value = DEFAULT_STATE_LOGIN_DIALOG diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index 9dfd10390..30e8f806f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -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.clipboard.BitwardenClipboardManager 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.Environment import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow @@ -70,6 +71,10 @@ import java.util.UUID @Suppress("LargeClass") class VaultAddEditViewModelTest : BaseViewModelTest() { + private val settingsRepository: SettingsRepository = mockk { + every { initialAutofillDialogShown = any() } just runs + every { initialAutofillDialogShown } returns true + } private val mutableUserStateFlow = MutableStateFlow(createUserState()) private val authRepository: AuthRepository = mockk { every { userStateFlow } returns mutableUserStateFlow @@ -1769,6 +1774,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { specialCircumstanceManager = specialCircumstanceManager, resourceManager = resourceManager, 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 @Suppress("MaxLineLength") @@ -2301,6 +2315,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { specialCircumstanceManager = specialCircumstanceManager, resourceManager = bitwardenResourceManager, authRepository = authRepository, + settingsRepository = settingsRepository, ) private fun createVaultData(