mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
PM-13609 Navigate to new import flow from Vault settings when feature is enabled. (#4090)
This commit is contained in:
parent
736912bd6c
commit
970a1e14cd
7 changed files with 136 additions and 20 deletions
|
@ -35,6 +35,7 @@ fun NavGraphBuilder.settingsGraph(
|
|||
onNavigateToPendingRequests: () -> Unit,
|
||||
onNavigateToSetupUnlockScreen: () -> Unit,
|
||||
onNavigateToSetupAutoFillScreen: () -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = SETTINGS_ROUTE,
|
||||
|
@ -70,6 +71,7 @@ fun NavGraphBuilder.settingsGraph(
|
|||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToExportVault = onNavigateToExportVault,
|
||||
onNavigateToFolders = onNavigateToFolders,
|
||||
onNavigateToImportLogins = onNavigateToImportLogins,
|
||||
)
|
||||
blockAutoFillDestination(onNavigateBack = { navController.popBackStack() })
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ fun NavGraphBuilder.vaultSettingsDestination(
|
|||
onNavigateBack: () -> Unit,
|
||||
onNavigateToExportVault: () -> Unit,
|
||||
onNavigateToFolders: () -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
) {
|
||||
composableWithPushTransitions(
|
||||
route = VAULT_SETTINGS_ROUTE,
|
||||
|
@ -22,6 +23,7 @@ fun NavGraphBuilder.vaultSettingsDestination(
|
|||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToExportVault = onNavigateToExportVault,
|
||||
onNavigateToFolders = onNavigateToFolders,
|
||||
onNavigateToImportLogins = onNavigateToImportLogins,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
|
@ -40,10 +41,11 @@ fun VaultSettingsScreen(
|
|||
onNavigateBack: () -> Unit,
|
||||
onNavigateToExportVault: () -> Unit,
|
||||
onNavigateToFolders: () -> Unit,
|
||||
onNavigateToImportLogins: () -> Unit,
|
||||
viewModel: VaultSettingsViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
val state = viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
val context = LocalContext.current
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
|
@ -56,10 +58,14 @@ fun VaultSettingsScreen(
|
|||
}
|
||||
|
||||
is VaultSettingsEvent.NavigateToImportVault -> {
|
||||
if (state.isNewImportLoginsFlowEnabled) {
|
||||
onNavigateToImportLogins()
|
||||
} else {
|
||||
intentManager.launchUri(event.url.toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
|
@ -106,6 +112,18 @@ fun VaultSettingsScreen(
|
|||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
if (state.isNewImportLoginsFlowEnabled) {
|
||||
BitwardenTextRow(
|
||||
text = stringResource(R.string.import_items),
|
||||
onClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(VaultSettingsAction.ImportItemsClick) }
|
||||
},
|
||||
withDivider = true,
|
||||
modifier = Modifier
|
||||
.testTag("ImportItemsLinkItemView")
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
} else {
|
||||
BitwardenExternalLinkRow(
|
||||
text = stringResource(R.string.import_items),
|
||||
onConfirmClick = remember(viewModel) {
|
||||
|
@ -116,7 +134,7 @@ fun VaultSettingsScreen(
|
|||
dialogMessage =
|
||||
stringResource(
|
||||
id = R.string.you_can_import_data_to_your_vault_on_x,
|
||||
state.value.importUrl,
|
||||
state.importUrl,
|
||||
),
|
||||
modifier = Modifier
|
||||
.testTag("ImportItemsLinkItemView")
|
||||
|
@ -125,3 +143,4 @@ fun VaultSettingsScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.toBaseWebVaultImportUrl
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -11,7 +18,8 @@ import javax.inject.Inject
|
|||
*/
|
||||
@HiltViewModel
|
||||
class VaultSettingsViewModel @Inject constructor(
|
||||
val environmentRepository: EnvironmentRepository,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
val featureFlagManager: FeatureFlagManager,
|
||||
) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
|
||||
initialState = run {
|
||||
VaultSettingsState(
|
||||
|
@ -19,15 +27,35 @@ class VaultSettingsViewModel @Inject constructor(
|
|||
.environment
|
||||
.environmentUrlData
|
||||
.toBaseWebVaultImportUrl,
|
||||
isNewImportLoginsFlowEnabled = featureFlagManager
|
||||
.getFeatureFlag(FlagKey.ImportLoginsFlow),
|
||||
)
|
||||
},
|
||||
) {
|
||||
init {
|
||||
featureFlagManager
|
||||
.getFeatureFlagFlow(FlagKey.ImportLoginsFlow)
|
||||
.map { VaultSettingsAction.Internal.ImportLoginsFeatureFlagChanged(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: VaultSettingsAction): Unit = when (action) {
|
||||
VaultSettingsAction.BackClick -> handleBackClicked()
|
||||
VaultSettingsAction.ExportVaultClick -> handleExportVaultClicked()
|
||||
VaultSettingsAction.FoldersButtonClick -> handleFoldersButtonClicked()
|
||||
VaultSettingsAction.ImportItemsClick -> handleImportItemsClicked()
|
||||
is VaultSettingsAction.Internal.ImportLoginsFeatureFlagChanged -> {
|
||||
handleImportLoginsFeatureFlagChanged(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleImportLoginsFeatureFlagChanged(
|
||||
action: VaultSettingsAction.Internal.ImportLoginsFeatureFlagChanged,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isNewImportLoginsFlowEnabled = action.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackClicked() {
|
||||
|
@ -54,6 +82,7 @@ class VaultSettingsViewModel @Inject constructor(
|
|||
*/
|
||||
data class VaultSettingsState(
|
||||
val importUrl: String,
|
||||
val isNewImportLoginsFlowEnabled: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -111,4 +140,17 @@ sealed class VaultSettingsAction {
|
|||
* Indicates that the user clicked the Import Items button.
|
||||
*/
|
||||
data object ImportItemsClick : VaultSettingsAction()
|
||||
|
||||
/**
|
||||
* Internal actions not performed by user interation
|
||||
*/
|
||||
sealed class Internal : VaultSettingsAction() {
|
||||
|
||||
/**
|
||||
* Indicates that the import logins feature flag has changed.
|
||||
*/
|
||||
data class ImportLoginsFeatureFlagChanged(
|
||||
val isEnabled: Boolean,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,6 +246,7 @@ private fun VaultUnlockedNavBarScaffold(
|
|||
onNavigateToPendingRequests = navigateToPendingRequests,
|
||||
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
|
||||
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
|
||||
onNavigateToImportLogins = onNavigateToImportLogins,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,15 @@ import io.mockk.mockk
|
|||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class VaultSettingsScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateToImportLoginsCalled = false
|
||||
private var onNavigateBackCalled = false
|
||||
private var onNavigateToExportVaultCalled = false
|
||||
private var onNavigateToFoldersCalled = false
|
||||
|
@ -30,6 +33,7 @@ class VaultSettingsScreenTest : BaseComposeTest() {
|
|||
private val mutableStateFlow = MutableStateFlow(
|
||||
VaultSettingsState(
|
||||
importUrl = "testUrl/#/tools/import",
|
||||
isNewImportLoginsFlowEnabled = false,
|
||||
),
|
||||
)
|
||||
private val intentManager: IntentManager = mockk(relaxed = true) {
|
||||
|
@ -50,6 +54,7 @@ class VaultSettingsScreenTest : BaseComposeTest() {
|
|||
onNavigateToExportVault = { onNavigateToExportVaultCalled = true },
|
||||
onNavigateToFolders = { onNavigateToFoldersCalled = true },
|
||||
intentManager = intentManager,
|
||||
onNavigateToImportLogins = { onNavigateToImportLoginsCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -124,11 +129,32 @@ class VaultSettingsScreenTest : BaseComposeTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToImportVault should invoke IntentManager`() {
|
||||
fun `on NavigateToImportVault should invoke IntentManager not lambda`() {
|
||||
val testUrl = "testUrl"
|
||||
mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateToImportVault(testUrl))
|
||||
verify {
|
||||
intentManager.launchUri(testUrl.toUri())
|
||||
}
|
||||
assertFalse(onNavigateToImportLoginsCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when new logins feature flag is enabled send action right when import items is clicked`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isNewImportLoginsFlowEnabled = true)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Import items").performClick()
|
||||
verify { viewModel.trySendAction(VaultSettingsAction.ImportItemsClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when new logins feature flag is enabled NavigateToImportVault should invoke lambda`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isNewImportLoginsFlowEnabled = true)
|
||||
}
|
||||
val testUrl = "testUrl"
|
||||
mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateToImportVault(testUrl))
|
||||
assertTrue(onNavigateToImportLoginsCalled)
|
||||
verify(exactly = 0) { intentManager.launchUri(testUrl.toUri()) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,27 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultSettingsViewModelTest : BaseViewModelTest() {
|
||||
private val environmentRepository = FakeEnvironmentRepository()
|
||||
private val mutableImportLoginsFlagFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager = mockk<FeatureFlagManager> {
|
||||
every { getFeatureFlagFlow(FlagKey.ImportLoginsFlow) } returns mutableImportLoginsFlagFlow
|
||||
every { getFeatureFlag(FlagKey.ImportLoginsFlow) } returns false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackClick should emit NavigateBack`() = runTest {
|
||||
|
@ -44,7 +57,18 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ImportLoginsFeatureFlagChanged should update state`() {
|
||||
val viewModel = createViewModel()
|
||||
assertFalse(
|
||||
viewModel.stateFlow.value.isNewImportLoginsFlowEnabled,
|
||||
)
|
||||
mutableImportLoginsFlagFlow.update { true }
|
||||
assertTrue(viewModel.stateFlow.value.isNewImportLoginsFlowEnabled)
|
||||
}
|
||||
|
||||
private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel(
|
||||
environmentRepository = environmentRepository,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue