diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt index 8ad37f4a9..4140b206b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt @@ -94,6 +94,14 @@ fun AccountSecurityScreen( AccountSecurityEvent.NavigateToPendingRequests -> onNavigateToPendingRequests() + is AccountSecurityEvent.NavigateToTwoStepLogin -> { + intentManager.launchUri(event.url.toUri()) + } + + is AccountSecurityEvent.NavigateToChangeMasterPassword -> { + intentManager.launchUri(event.url.toUri()) + } + is AccountSecurityEvent.ShowToast -> { Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt index b3c63b8c9..bed9c6728 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt @@ -5,9 +5,11 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction +import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.util.Text @@ -30,6 +32,7 @@ class AccountSecurityViewModel @Inject constructor( private val authRepository: AuthRepository, private val vaultRepository: VaultRepository, private val settingsRepository: SettingsRepository, + private val environmentRepository: EnvironmentRepository, savedStateHandle: SavedStateHandle, ) : BaseViewModel( initialState = savedStateHandle[KEY_STATE] @@ -43,6 +46,14 @@ class AccountSecurityViewModel @Inject constructor( vaultTimeoutAction = settingsRepository.vaultTimeoutAction, ), ) { + private val webSettingsUrl: String + get() { + val baseUrl = environmentRepository + .environment + .environmentUrlData + .baseWebVaultUrlOrDefault + return "$baseUrl/#/settings" + } init { stateFlow @@ -90,8 +101,7 @@ class AccountSecurityViewModel @Inject constructor( private fun handleBackClick() = sendEvent(AccountSecurityEvent.NavigateBack) private fun handleChangeMasterPasswordClick() { - // TODO BIT-971: Add Leaving app Dialog - sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText())) + sendEvent(AccountSecurityEvent.NavigateToChangeMasterPassword(webSettingsUrl)) } private fun handleConfirmLogoutClick() { @@ -198,8 +208,7 @@ class AccountSecurityViewModel @Inject constructor( } private fun handleTwoStepLoginClick() { - // TODO BIT-468: Implement two-step login - sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText())) + sendEvent(AccountSecurityEvent.NavigateToTwoStepLogin(webSettingsUrl)) } private fun handleUnlockWithBiometricToggled( @@ -300,6 +309,16 @@ sealed class AccountSecurityEvent { */ data object NavigateToPendingRequests : AccountSecurityEvent() + /** + * Navigate to the two step login screen. + */ + data class NavigateToTwoStepLogin(val url: String) : AccountSecurityEvent() + + /** + * Navigate to the change master password screen. + */ + data class NavigateToChangeMasterPassword(val url: String) : AccountSecurityEvent() + /** * Displays a toast with the given [Text]. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt index 30afd0442..4b86f309c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt @@ -1060,6 +1060,13 @@ class AccountSecurityScreenTest : BaseComposeTest() { verify { viewModel.trySendAction(AccountSecurityAction.TwoStepLoginClick) } } + @Test + fun `on NavigateToTwoStepLogin should call launchUri on intentManager`() { + val uri = "testUri" + mutableEventFlow.tryEmit(AccountSecurityEvent.NavigateToTwoStepLogin(uri)) + verify { intentManager.launchUri(uri.toUri()) } + } + @Suppress("MaxLineLength") @Test fun `on change master password click should display confirmation dialog and confirm should send ChangeMasterPasswordClick`() { @@ -1077,6 +1084,13 @@ class AccountSecurityScreenTest : BaseComposeTest() { verify { viewModel.trySendAction(AccountSecurityAction.ChangeMasterPasswordClick) } } + @Test + fun `on NavigateToChangeMasterPassword should call launchUri on intentManager`() { + val uri = "testUri" + mutableEventFlow.tryEmit(AccountSecurityEvent.NavigateToChangeMasterPassword(uri)) + verify { intentManager.launchUri(uri.toUri()) } + } + @Test fun `on Lock now click should send LockNowClick`() { composeTestRule.onNodeWithText("Lock now").performScrollTo().performClick() diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt index f567b345b..1e5a4a8bf 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModelTest.kt @@ -3,9 +3,12 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.x8bit.bitwarden.data.auth.repository.AuthRepository +import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository +import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction +import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.util.asText @@ -22,6 +25,8 @@ import org.junit.jupiter.api.Test class AccountSecurityViewModelTest : BaseViewModelTest() { + private val fakeEnvironmentRepository = FakeEnvironmentRepository() + @Test fun `initial state should be correct when saved state is set`() { val viewModel = createViewModel(initialState = DEFAULT_STATE) @@ -74,17 +79,33 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { } } + @Suppress("MaxLineLength") @Test - fun `on ChangeMasterPasswordClick should emit ShowToast`() = runTest { - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(AccountSecurityAction.ChangeMasterPasswordClick) - assertEquals( - AccountSecurityEvent.ShowToast("Not yet implemented.".asText()), - awaitItem(), - ) + fun `on ChangeMasterPasswordClick should emit NavigateToChangeMasterPassword with correct URL based on US and EU environments`() = + runTest { + fakeEnvironmentRepository.environment = Environment.Us + val viewModel = createViewModel() + viewModel.eventFlow.test { + + viewModel.trySendAction(AccountSecurityAction.ChangeMasterPasswordClick) + assertEquals( + AccountSecurityEvent.NavigateToChangeMasterPassword( + "https://vault.bitwarden.com/#/settings", + ), + awaitItem(), + ) + + fakeEnvironmentRepository.environment = Environment.Eu + + viewModel.trySendAction(AccountSecurityAction.ChangeMasterPasswordClick) + assertEquals( + AccountSecurityEvent.NavigateToChangeMasterPassword( + "https://vault.bitwarden.eu/#/settings", + ), + awaitItem(), + ) + } } - } @Test fun `on DeleteAccountClick should emit NavigateToDeleteAccount`() = runTest { @@ -184,17 +205,33 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { verify { settingsRepository.vaultTimeoutAction = VaultTimeoutAction.LOGOUT } } + @Suppress("MaxLineLength") @Test - fun `on TwoStepLoginClick should emit NavigateToTwoStepLogin`() = runTest { - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(AccountSecurityAction.TwoStepLoginClick) - assertEquals( - AccountSecurityEvent.ShowToast("Not yet implemented.".asText()), - awaitItem(), - ) + fun `on TwoStepLoginClick should emit NavigateToTwoStepLogin with correct URL based on US and EU environments`() = + runTest { + fakeEnvironmentRepository.environment = Environment.Us + val viewModel = createViewModel() + viewModel.eventFlow.test { + + viewModel.trySendAction(AccountSecurityAction.TwoStepLoginClick) + assertEquals( + AccountSecurityEvent.NavigateToTwoStepLogin( + "https://vault.bitwarden.com/#/settings", + ), + awaitItem(), + ) + + fakeEnvironmentRepository.environment = Environment.Eu + + viewModel.trySendAction(AccountSecurityAction.TwoStepLoginClick) + assertEquals( + AccountSecurityEvent.NavigateToTwoStepLogin( + "https://vault.bitwarden.eu/#/settings", + ), + awaitItem(), + ) + } } - } @Test fun `on UnlockWithBiometricToggle should emit ShowToast`() = runTest { @@ -379,11 +416,13 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { } } + @Suppress("LongParameterList") private fun createViewModel( initialState: AccountSecurityState? = DEFAULT_STATE, authRepository: AuthRepository = mockk(relaxed = true), vaultRepository: VaultRepository = mockk(relaxed = true), settingsRepository: SettingsRepository = mockk(relaxed = true), + environmentRepository: EnvironmentRepository = fakeEnvironmentRepository, savedStateHandle: SavedStateHandle = SavedStateHandle().apply { set("state", initialState) }, @@ -391,6 +430,7 @@ class AccountSecurityViewModelTest : BaseViewModelTest() { authRepository = authRepository, vaultRepository = vaultRepository, settingsRepository = settingsRepository, + environmentRepository = environmentRepository, savedStateHandle = savedStateHandle, )