mirror of
https://github.com/bitwarden/android.git
synced 2026-08-29 10:17:56 -05:00
BITAU-189 BITAU-188 Only show default save option row when sync is en… (#261)
This commit is contained in:
+2
-2
@@ -373,7 +373,7 @@ private fun DefaultSaveOptionSelectionRow(
|
||||
var shouldShowDefaultSaveOptionDialog by remember { mutableStateOf(false) }
|
||||
|
||||
BitwardenTextRow(
|
||||
text = stringResource(id = R.string.default_save_options),
|
||||
text = stringResource(id = R.string.default_save_option),
|
||||
onClick = { shouldShowDefaultSaveOptionDialog = true },
|
||||
modifier = modifier,
|
||||
withDivider = true,
|
||||
@@ -388,7 +388,7 @@ private fun DefaultSaveOptionSelectionRow(
|
||||
var dialogSelection by remember { mutableStateOf(currentSelection) }
|
||||
if (shouldShowDefaultSaveOptionDialog) {
|
||||
BitwardenSelectionDialog(
|
||||
title = stringResource(id = R.string.default_save_options),
|
||||
title = stringResource(id = R.string.default_save_option),
|
||||
subtitle = stringResource(id = R.string.default_save_options_subtitle),
|
||||
dismissLabel = stringResource(id = R.string.confirm),
|
||||
onDismissRequest = { shouldShowDefaultSaveOptionDialog = false },
|
||||
|
||||
+44
-2
@@ -7,6 +7,9 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.authenticator.BuildConfig
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.util.isSyncWithBitwardenEnabled
|
||||
import com.bitwarden.authenticator.data.platform.manager.FeatureFlagManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.model.LocalFeatureFlag
|
||||
@@ -22,6 +25,9 @@ import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.Defau
|
||||
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
|
||||
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||
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 kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@@ -34,11 +40,12 @@ private const val KEY_STATE = "state"
|
||||
/**
|
||||
* View model for the settings screen.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@Suppress("TooManyFunctions", "LongParameterList")
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
clock: Clock,
|
||||
private val authenticatorRepository: AuthenticatorRepository,
|
||||
private val authenticatorBridgeManager: AuthenticatorBridgeManager,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
@@ -55,8 +62,18 @@ class SettingsViewModel @Inject constructor(
|
||||
featureFlagManager.getFeatureFlag(LocalFeatureFlag.PasswordManagerSync),
|
||||
accountSyncState = authenticatorBridgeManager.accountSyncStateFlow.value,
|
||||
defaultSaveOption = settingsRepository.defaultSaveOption,
|
||||
sharedAccountsState = authenticatorRepository.sharedCodesStateFlow.value,
|
||||
),
|
||||
) {
|
||||
|
||||
init {
|
||||
authenticatorRepository
|
||||
.sharedCodesStateFlow
|
||||
.map { SettingsAction.Internal.SharedAccountsStateUpdated(it) }
|
||||
.onEach(::handleAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: SettingsAction) {
|
||||
when (action) {
|
||||
is SettingsAction.SecurityClick -> {
|
||||
@@ -82,6 +99,20 @@ class SettingsViewModel @Inject constructor(
|
||||
is SettingsAction.Internal.BiometricsKeyResultReceive -> {
|
||||
handleBiometricsKeyResultReceive(action)
|
||||
}
|
||||
|
||||
is SettingsAction.Internal.SharedAccountsStateUpdated -> {
|
||||
handleSharedAccountsStateUpdated(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSharedAccountsStateUpdated(
|
||||
action: SettingsAction.Internal.SharedAccountsStateUpdated,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
showDefaultSaveOptionRow = action.state.isSyncWithBitwardenEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,12 +304,16 @@ class SettingsViewModel @Inject constructor(
|
||||
isSubmitCrashLogsEnabled: Boolean,
|
||||
accountSyncState: AccountSyncState,
|
||||
isSyncWithBitwardenFeatureEnabled: Boolean,
|
||||
sharedAccountsState: SharedVerificationCodesState,
|
||||
): SettingsState {
|
||||
val currentYear = Year.now(clock)
|
||||
val copyrightInfo = "© Bitwarden Inc. 2015-$currentYear".asText()
|
||||
// Show sync with Bitwarden row if feature is enabled and the OS is supported:
|
||||
val shouldShowSyncWithBitwarden = isSyncWithBitwardenFeatureEnabled &&
|
||||
accountSyncState != AccountSyncState.OsVersionNotSupported
|
||||
// Show default save options only if the user had enabled sync with Bitwarden:
|
||||
// (They can enable it via the "Sync with Bitwarden" row.
|
||||
val shouldShowDefaultSaveOption = sharedAccountsState.isSyncWithBitwardenEnabled
|
||||
return SettingsState(
|
||||
appearance = SettingsState.Appearance(
|
||||
language = appLanguage,
|
||||
@@ -293,7 +328,7 @@ class SettingsViewModel @Inject constructor(
|
||||
copyrightInfo = copyrightInfo,
|
||||
defaultSaveOption = defaultSaveOption,
|
||||
showSyncWithBitwarden = shouldShowSyncWithBitwarden,
|
||||
showDefaultSaveOptionRow = shouldShowSyncWithBitwarden,
|
||||
showDefaultSaveOptionRow = shouldShowDefaultSaveOption,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -511,5 +546,12 @@ sealed class SettingsAction(
|
||||
* Indicates the biometrics key validation results has been received.
|
||||
*/
|
||||
data class BiometricsKeyResultReceive(val result: BiometricsKeyResult) : SettingsAction()
|
||||
|
||||
/**
|
||||
* Indicates that shared account state was updated.
|
||||
*/
|
||||
data class SharedAccountsStateUpdated(
|
||||
val state: SharedVerificationCodesState,
|
||||
) : SettingsAction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<string name="something_went_wrong">Something went wrong</string>
|
||||
<string name="please_try_again">Please try again</string>
|
||||
<string name="move_to_bitwarden">Move to Bitwarden</string>
|
||||
<string name="default_save_options">Default save options</string>
|
||||
<string name="default_save_option">Default save option</string>
|
||||
<string name="save_to_bitwarden">Save to Bitwarden</string>
|
||||
<string name="save_locally">Save locally</string>
|
||||
<string name="none">None</string>
|
||||
|
||||
+6
-6
@@ -119,32 +119,32 @@ class SettingsScreenTest : BaseComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Default Save Options row should be hidden when showDefaultSaveOptionRow is false`() {
|
||||
fun `Default Save Option row should be hidden when showDefaultSaveOptionRow is false`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE
|
||||
composeTestRule.onNodeWithText("Default save options").assertExists()
|
||||
composeTestRule.onNodeWithText("Default save option").assertExists()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
showDefaultSaveOptionRow = false,
|
||||
)
|
||||
}
|
||||
composeTestRule.onNodeWithText("Default save options").assertDoesNotExist()
|
||||
composeTestRule.onNodeWithText("Default save option").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `Default Save Options dialog should send DefaultSaveOptionUpdated when confirm is clicked`() =
|
||||
fun `Default Save Option dialog should send DefaultSaveOptionUpdated when confirm is clicked`() =
|
||||
runTest {
|
||||
val expectedSaveOption = DefaultSaveOption.BITWARDEN_APP
|
||||
mutableStateFlow.value = DEFAULT_STATE
|
||||
composeTestRule
|
||||
.onNodeWithText("Default save options")
|
||||
.onNodeWithText("Default save option")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
// Make sure the dialog is showing:
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Default save options")
|
||||
.onAllNodesWithText("Default save option")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
|
||||
+48
-1
@@ -4,6 +4,9 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.BuildConfig
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.util.isSyncWithBitwardenEnabled
|
||||
import com.bitwarden.authenticator.data.platform.manager.FeatureFlagManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.model.LocalFeatureFlag
|
||||
@@ -19,11 +22,15 @@ import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
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.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
@@ -34,6 +41,11 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
private val authenticatorBridgeManager: AuthenticatorBridgeManager = mockk {
|
||||
every { accountSyncStateFlow } returns MutableStateFlow(AccountSyncState.Loading)
|
||||
}
|
||||
|
||||
private val mutableSharedCodesFlow = MutableStateFlow(MOCK_SHARED_CODES_STATE)
|
||||
private val authenticatorRepository: AuthenticatorRepository = mockk {
|
||||
every { sharedCodesStateFlow } returns mutableSharedCodesFlow
|
||||
}
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { appLanguage } returns APP_LANGUAGE
|
||||
every { appTheme } returns APP_THEME
|
||||
@@ -46,6 +58,16 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
every { getFeatureFlag(LocalFeatureFlag.PasswordManagerSync) } returns true
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(SharedVerificationCodesState::isSyncWithBitwardenEnabled)
|
||||
every { MOCK_SHARED_CODES_STATE.isSyncWithBitwardenEnabled } returns false
|
||||
}
|
||||
|
||||
fun teardown() {
|
||||
unmockkStatic(SharedVerificationCodesState::isSyncWithBitwardenEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `initialState should be correct when saved state is null and password manager feature flag is off`() {
|
||||
@@ -130,6 +152,29 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `Default save option row should only show when shared codes state shows syncing as enabled`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
val enabledState: SharedVerificationCodesState = mockk {
|
||||
every { isSyncWithBitwardenEnabled } returns true
|
||||
}
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
mutableSharedCodesFlow.update { enabledState }
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
showDefaultSaveOptionRow = true,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on DefaultSaveOptionUpdated should update SettingsRepository and state`() {
|
||||
@@ -154,12 +199,14 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
savedStateHandle = SavedStateHandle().apply { this["state"] = savedState },
|
||||
clock = CLOCK,
|
||||
authenticatorBridgeManager = authenticatorBridgeManager,
|
||||
authenticatorRepository = authenticatorRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
clipboardManager = clipboardManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
||||
private val MOCK_SHARED_CODES_STATE: SharedVerificationCodesState = mockk()
|
||||
private val APP_LANGUAGE = AppLanguage.ENGLISH
|
||||
private val APP_THEME = AppTheme.DEFAULT
|
||||
private val CLOCK = Clock.fixed(
|
||||
@@ -175,7 +222,7 @@ private val DEFAULT_STATE = SettingsState(
|
||||
isSubmitCrashLogsEnabled = true,
|
||||
isUnlockWithBiometricsEnabled = true,
|
||||
showSyncWithBitwarden = true,
|
||||
showDefaultSaveOptionRow = true,
|
||||
showDefaultSaveOptionRow = false,
|
||||
defaultSaveOption = DEFAULT_SAVE_OPTION,
|
||||
dialog = null,
|
||||
version = R.string.version.asText()
|
||||
|
||||
Reference in New Issue
Block a user