mirror of
https://github.com/bitwarden/android.git
synced 2024-11-26 11:26:09 +03:00
Allow users to navigate to Add Item screen for autofill save (#900)
This commit is contained in:
parent
d711360bad
commit
10471a7ea6
9 changed files with 161 additions and 8 deletions
|
@ -6,6 +6,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
|
@ -110,9 +111,17 @@ class MainViewModel @Inject constructor(
|
|||
intent: Intent,
|
||||
isFirstIntent: Boolean,
|
||||
) {
|
||||
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||
when {
|
||||
autofillSaveItem != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = autofillSaveItem,
|
||||
)
|
||||
}
|
||||
|
||||
autofillSelectionData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
@ -19,6 +20,15 @@ sealed class SpecialCircumstance : Parcelable {
|
|||
val shouldFinishWhenComplete: Boolean,
|
||||
) : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched via the autofill framework in order to allow the user to manually save
|
||||
* data that was entered in an external form.
|
||||
*/
|
||||
@Parcelize
|
||||
data class AutofillSave(
|
||||
val autofillSaveItem: AutofillSaveItem,
|
||||
) : SpecialCircumstance()
|
||||
|
||||
/**
|
||||
* The app was launched in order to allow the user to manually select data for autofill.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
|||
*/
|
||||
fun SpecialCircumstance.toAutofillSelectionDataOrNull(): AutofillSelectionData? =
|
||||
when (this) {
|
||||
is SpecialCircumstance.AutofillSave -> null
|
||||
is SpecialCircumstance.AutofillSelection -> this.autofillSelectionData
|
||||
is SpecialCircumstance.ShareNewSend -> null
|
||||
}
|
||||
|
|
|
@ -32,7 +32,9 @@ import com.x8bit.bitwarden.ui.platform.theme.NonNullExitTransitionProvider
|
|||
import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.navigateToAddSend
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.navigateToVaultAddEdit
|
||||
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.navigateToVaultItemListingAsRoot
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
@ -85,6 +87,7 @@ fun RootNavScreen(
|
|||
RootNavState.Splash -> SPLASH_ROUTE
|
||||
RootNavState.VaultLocked -> VAULT_UNLOCK_ROUTE
|
||||
is RootNavState.VaultUnlocked,
|
||||
is RootNavState.VaultUnlockedForAutofillSave,
|
||||
is RootNavState.VaultUnlockedForAutofillSelection,
|
||||
is RootNavState.VaultUnlockedForNewSend,
|
||||
-> VAULT_UNLOCKED_GRAPH_ROUTE
|
||||
|
@ -126,6 +129,14 @@ fun RootNavScreen(
|
|||
)
|
||||
}
|
||||
|
||||
is RootNavState.VaultUnlockedForAutofillSave -> {
|
||||
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||
navController.navigateToVaultAddEdit(
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
navOptions = rootNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
is RootNavState.VaultUnlockedForAutofillSelection -> {
|
||||
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||
navController.navigateToVaultItemListingAsRoot(
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
|
@ -67,6 +68,12 @@ class RootNavViewModel @Inject constructor(
|
|||
|
||||
userState.activeAccount.isVaultUnlocked -> {
|
||||
when (specialCircumstance) {
|
||||
is SpecialCircumstance.AutofillSave -> {
|
||||
RootNavState.VaultUnlockedForAutofillSave(
|
||||
autofillSaveItem = specialCircumstance.autofillSaveItem,
|
||||
)
|
||||
}
|
||||
|
||||
is SpecialCircumstance.AutofillSelection -> {
|
||||
RootNavState.VaultUnlockedForAutofillSelection(
|
||||
activeUserId = userState.activeAccount.userId,
|
||||
|
@ -126,6 +133,15 @@ sealed class RootNavState : Parcelable {
|
|||
val activeUserId: String,
|
||||
) : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show an add item screen for a user to complete the saving of data collected by
|
||||
* the autofill framework.
|
||||
*/
|
||||
@Parcelize
|
||||
data class VaultUnlockedForAutofillSave(
|
||||
val autofillSaveItem: AutofillSaveItem,
|
||||
) : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show a selection screen for autofill for an unlocked user.
|
||||
*/
|
||||
|
|
|
@ -8,7 +8,9 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
|
@ -129,6 +131,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val shareData = mockk<IntentManager.ShareData>()
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
|
||||
|
||||
|
@ -152,6 +155,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
|
@ -169,12 +173,36 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with an autofill save item should set the special circumstance to AutofillSave`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSaveItem = mockk<AutofillSaveItem>()
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = autofillSaveItem,
|
||||
),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val shareData = mockk<IntentManager.ShareData>()
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
|
||||
|
||||
|
@ -198,6 +226,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
|
@ -215,6 +244,29 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with an autofill save item should set the special circumstance to AutofillSave`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockIntent = mockk<Intent>()
|
||||
val autofillSaveItem = mockk<AutofillSaveItem>()
|
||||
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveNewIntent(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = autofillSaveItem,
|
||||
),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `changes in the allowed screen capture value should result in emissions of ScreenCaptureSettingChange `() =
|
||||
|
|
|
@ -27,14 +27,18 @@ class SpecialCircumstanceExtensionsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `toAutofillSelectionDataOrNull should a non-null value for other types`() {
|
||||
assertNull(
|
||||
SpecialCircumstance
|
||||
.ShareNewSend(
|
||||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
)
|
||||
.toAutofillSelectionDataOrNull(),
|
||||
fun `toAutofillSelectionDataOrNull should a null value for other types`() {
|
||||
listOf(
|
||||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = mockk(),
|
||||
),
|
||||
SpecialCircumstance.ShareNewSend(
|
||||
data = mockk(),
|
||||
shouldFinishWhenComplete = true,
|
||||
),
|
||||
)
|
||||
.forEach { specialCircumstance ->
|
||||
assertNull(specialCircumstance.toAutofillSelectionDataOrNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,18 @@ class RootNavScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault unlocked for autofill save works as expected:
|
||||
rootNavStateFlow.value =
|
||||
RootNavState.VaultUnlockedForAutofillSave(
|
||||
autofillSaveItem = mockk(),
|
||||
)
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "vault_add_edit_item/add",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault unlocked for autofill works as expected:
|
||||
rootNavStateFlow.value =
|
||||
RootNavState.VaultUnlockedForAutofillSelection(
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
|
@ -175,6 +176,43 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault but there is an AutofillSave special circumstance the nav state should be VaultUnlockedForAutofillSave`() {
|
||||
val autofillSaveItem: AutofillSaveItem = mockk()
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = autofillSaveItem,
|
||||
)
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
environment = Environment.Us,
|
||||
isPremium = true,
|
||||
isLoggedIn = true,
|
||||
isVaultUnlocked = true,
|
||||
needsPasswordReset = false,
|
||||
isBiometricsEnabled = false,
|
||||
organizations = emptyList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.VaultUnlockedForAutofillSave(
|
||||
autofillSaveItem = autofillSaveItem,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault but there is an AutofillSelection special circumstance the nav state should be VaultUnlockedForAutofillSelection`() {
|
||||
|
|
Loading…
Reference in a new issue