BIT-972 Add the import url (#903)

This commit is contained in:
Oleg Semenenko 2024-01-31 20:50:45 -06:00 committed by Álison Fernandes
parent 7b32e46d37
commit 77913805ab
6 changed files with 114 additions and 12 deletions

View file

@ -50,6 +50,15 @@ val EnvironmentUrlDataJson.baseWebSendUrl: String
?.let { "$it/#/send/" } ?.let { "$it/#/send/" }
?: DEFAULT_WEB_SEND_URL ?: DEFAULT_WEB_SEND_URL
/**
* Returns the base web vault import URL or the default value if one is not present.
*/
val EnvironmentUrlDataJson.toBaseWebVaultImportUrl: String
get() =
this
.baseWebVaultUrlOrDefault
.let { "$it/#/tools/import" }
/** /**
* Returns a base icon url based on the environment or the default value if values are missing. * Returns a base icon url based on the environment or the default value if values are missing.
*/ */

View file

@ -17,13 +17,17 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
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.components.BitwardenExternalLinkRow import com.x8bit.bitwarden.ui.platform.components.BitwardenExternalLinkRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
/** /**
* Displays the vault settings screen. * Displays the vault settings screen.
@ -36,7 +40,10 @@ fun VaultSettingsScreen(
onNavigateToExportVault: () -> Unit, onNavigateToExportVault: () -> Unit,
onNavigateToFolders: () -> Unit, onNavigateToFolders: () -> Unit,
viewModel: VaultSettingsViewModel = hiltViewModel(), viewModel: VaultSettingsViewModel = hiltViewModel(),
intentManager: IntentManager = LocalIntentManager.current,
) { ) {
val state = viewModel.stateFlow.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
EventsEffect(viewModel = viewModel) { event -> EventsEffect(viewModel = viewModel) { event ->
when (event) { when (event) {
@ -46,6 +53,10 @@ fun VaultSettingsScreen(
is VaultSettingsEvent.ShowToast -> { is VaultSettingsEvent.ShowToast -> {
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
} }
is VaultSettingsEvent.NavigateToImportVault -> {
intentManager.launchUri(event.url.toUri())
}
} }
} }
@ -96,8 +107,12 @@ fun VaultSettingsScreen(
{ viewModel.trySendAction(VaultSettingsAction.ImportItemsClick) } { viewModel.trySendAction(VaultSettingsAction.ImportItemsClick) }
}, },
withDivider = true, withDivider = true,
dialogTitle = stringResource(id = R.string.import_items_confirmation), dialogTitle = stringResource(id = R.string.continue_to_web_app),
dialogMessage = stringResource(id = R.string.import_items_description), dialogMessage =
stringResource(
id = R.string.you_can_import_data_to_your_vault_on_x,
state.value.baseUrl,
),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
} }

View file

@ -1,5 +1,8 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault
import com.x8bit.bitwarden.data.platform.repository.util.toBaseWebVaultImportUrl
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -8,10 +11,23 @@ import javax.inject.Inject
* View model for the vault screen. * View model for the vault screen.
*/ */
@HiltViewModel @HiltViewModel
class VaultSettingsViewModel @Inject constructor() : class VaultSettingsViewModel @Inject constructor(
BaseViewModel<Unit, VaultSettingsEvent, VaultSettingsAction>( val environmentRepository: EnvironmentRepository,
initialState = Unit, ) : BaseViewModel<VaultSettingsState, VaultSettingsEvent, VaultSettingsAction>(
) { initialState = run {
VaultSettingsState(
baseUrl = environmentRepository
.environment
.environmentUrlData
.baseWebVaultUrlOrDefault,
importUrl = environmentRepository
.environment
.environmentUrlData
.toBaseWebVaultImportUrl,
)
},
) {
override fun handleAction(action: VaultSettingsAction): Unit = when (action) { override fun handleAction(action: VaultSettingsAction): Unit = when (action) {
VaultSettingsAction.BackClick -> handleBackClicked() VaultSettingsAction.BackClick -> handleBackClicked()
VaultSettingsAction.ExportVaultClick -> handleExportVaultClicked() VaultSettingsAction.ExportVaultClick -> handleExportVaultClicked()
@ -32,11 +48,20 @@ class VaultSettingsViewModel @Inject constructor() :
} }
private fun handleImportItemsClicked() { private fun handleImportItemsClicked() {
// TODO BIT-972 implement import items functionality sendEvent(
sendEvent(VaultSettingsEvent.ShowToast("Not yet implemented.")) VaultSettingsEvent.NavigateToImportVault(state.importUrl),
)
} }
} }
/**
* Models the state for the VaultSettingScreen.
*/
data class VaultSettingsState(
val baseUrl: String,
val importUrl: String,
)
/** /**
* Models events for the vault screen. * Models events for the vault screen.
*/ */
@ -46,6 +71,11 @@ sealed class VaultSettingsEvent {
*/ */
data object NavigateBack : VaultSettingsEvent() data object NavigateBack : VaultSettingsEvent()
/**
* Navigate to the import vault URL.
*/
data class NavigateToImportVault(val url: String) : VaultSettingsEvent()
/** /**
* Navigate to the Export Vault screen. * Navigate to the Export Vault screen.
*/ */

View file

@ -236,6 +236,29 @@ class EnvironmentUrlsDataJsonExtensionsTest {
.baseIconUrl, .baseIconUrl,
) )
} }
@Test
fun `toBaseWebVaultImportUrl should return correct url if webVault is empty`() {
val expectedUrl = "base/#/tools/import"
assertEquals(
expectedUrl,
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.copy(
webVault = null,
)
.toBaseWebVaultImportUrl,
)
}
@Test
fun `toBaseWebVaultImportUrl should correctly convert to the import url`() {
val expectedUrl = "webVault/#/tools/import"
assertEquals(
expectedUrl,
DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA.toBaseWebVaultImportUrl,
)
}
} }
private val DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA = EnvironmentUrlDataJson( private val DEFAULT_CUSTOM_ENVIRONMENT_URL_DATA = EnvironmentUrlDataJson(

View file

@ -7,8 +7,10 @@ import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.core.net.toUri
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
@ -23,7 +25,15 @@ class VaultSettingsScreenTest : BaseComposeTest() {
private var onNavigateToExportVaultCalled = false private var onNavigateToExportVaultCalled = false
private var onNavigateToFoldersCalled = false private var onNavigateToFoldersCalled = false
private val mutableEventFlow = bufferedMutableSharedFlow<VaultSettingsEvent>() private val mutableEventFlow = bufferedMutableSharedFlow<VaultSettingsEvent>()
private val mutableStateFlow = MutableStateFlow(Unit) private val mutableStateFlow = MutableStateFlow(
VaultSettingsState(
baseUrl = "testUrl", importUrl = "testUrl/#/tools/import",
),
)
private val intentManager: IntentManager = mockk(relaxed = true) {
every { launchUri(any()) } returns Unit
}
val viewModel = mockk<VaultSettingsViewModel>(relaxed = true) { val viewModel = mockk<VaultSettingsViewModel>(relaxed = true) {
every { eventFlow } returns mutableEventFlow every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow every { stateFlow } returns mutableStateFlow
@ -37,6 +47,7 @@ class VaultSettingsScreenTest : BaseComposeTest() {
onNavigateBack = { onNavigateBackCalled = true }, onNavigateBack = { onNavigateBackCalled = true },
onNavigateToExportVault = { onNavigateToExportVaultCalled = true }, onNavigateToExportVault = { onNavigateToExportVaultCalled = true },
onNavigateToFolders = { onNavigateToFoldersCalled = true }, onNavigateToFolders = { onNavigateToFoldersCalled = true },
intentManager = intentManager,
) )
} }
} }
@ -101,4 +112,13 @@ class VaultSettingsScreenTest : BaseComposeTest() {
mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateToFolders) mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateToFolders)
assertTrue(onNavigateToFoldersCalled) assertTrue(onNavigateToFoldersCalled)
} }
@Test
fun `on NavigateToImportVault should invoke IntentManager`() {
val testUrl = "testUrl"
mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateToImportVault(testUrl))
verify {
intentManager.launchUri(testUrl.toUri())
}
}
} }

View file

@ -1,12 +1,14 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import app.cash.turbine.test import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class VaultSettingsViewModelTest : BaseViewModelTest() { class VaultSettingsViewModelTest : BaseViewModelTest() {
private val environmentRepository = FakeEnvironmentRepository()
@Test @Test
fun `BackClick should emit NavigateBack`() = runTest { fun `BackClick should emit NavigateBack`() = runTest {
@ -30,16 +32,19 @@ class VaultSettingsViewModelTest : BaseViewModelTest() {
} }
@Test @Test
fun `ImportItemsClick should emit ShowToast`() = runTest { fun `ImportItemsClick should emit send NavigateToImportVault with correct url`() = runTest {
val viewModel = createViewModel() val viewModel = createViewModel()
val expected = "https://vault.bitwarden.com/#/tools/import"
viewModel.eventFlow.test { viewModel.eventFlow.test {
viewModel.trySendAction(VaultSettingsAction.ImportItemsClick) viewModel.trySendAction(VaultSettingsAction.ImportItemsClick)
assertEquals( assertEquals(
VaultSettingsEvent.ShowToast("Not yet implemented."), VaultSettingsEvent.NavigateToImportVault(expected),
awaitItem(), awaitItem(),
) )
} }
} }
private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel() private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel(
environmentRepository = environmentRepository,
)
} }