mirror of
https://github.com/bitwarden/android.git
synced 2026-10-23 11:33:22 -05:00
BITAU-89 Have SettingsViewModel obsererve DefaultSaveOption (#265)
Co-authored-by: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com>
This commit is contained in:
1 parent
78d7865207
commit
b42ec0ae13
8 files changed
+147
-46
No files matched your search
+5
@@ -30,6 +30,11 @@ interface SettingsDiskSource {
|
||||
*/
|
||||
var defaultSaveOption: DefaultSaveOption
|
||||
|
||||
/**
|
||||
* Flow that emits changes to [defaultSaveOption]
|
||||
*/
|
||||
val defaultSaveOptionFlow: Flow<DefaultSaveOption>
|
||||
|
||||
/**
|
||||
* The currently persisted biometric integrity source for the system.
|
||||
*/
|
||||
|
||||
+7
@@ -45,6 +45,9 @@ class SettingsDiskSourceImpl(
|
||||
private val mutableIsCrashLoggingEnabledFlow =
|
||||
bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableDefaultSaveOptionFlow =
|
||||
bufferedMutableSharedFlow<DefaultSaveOption>()
|
||||
|
||||
override var appLanguage: AppLanguage?
|
||||
get() = getString(key = APP_LANGUAGE_KEY)
|
||||
?.let { storedValue ->
|
||||
@@ -89,7 +92,11 @@ class SettingsDiskSourceImpl(
|
||||
key = DEFAULT_SAVE_OPTION_KEY,
|
||||
value = newValue.value,
|
||||
)
|
||||
mutableDefaultSaveOptionFlow.tryEmit(newValue)
|
||||
}
|
||||
override val defaultSaveOptionFlow: Flow<DefaultSaveOption>
|
||||
get() = mutableDefaultSaveOptionFlow
|
||||
.onSubscription { emit(defaultSaveOption) }
|
||||
|
||||
override var systemBiometricIntegritySource: String?
|
||||
get() = getString(key = SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY)
|
||||
|
||||
+5
@@ -37,6 +37,11 @@ interface SettingsRepository {
|
||||
*/
|
||||
var defaultSaveOption: DefaultSaveOption
|
||||
|
||||
/**
|
||||
* Flow that emits changes to [defaultSaveOption]
|
||||
*/
|
||||
val defaultSaveOptionFlow: Flow<DefaultSaveOption>
|
||||
|
||||
/**
|
||||
* Whether or not biometric unlocking is enabled for the current user.
|
||||
*/
|
||||
|
||||
+3
@@ -44,6 +44,9 @@ class SettingsRepositoryImpl(
|
||||
|
||||
override var defaultSaveOption: DefaultSaveOption by settingsDiskSource::defaultSaveOption
|
||||
|
||||
override val defaultSaveOptionFlow: Flow<DefaultSaveOption>
|
||||
by settingsDiskSource::defaultSaveOptionFlow
|
||||
|
||||
override val isUnlockWithBiometricsEnabled: Boolean
|
||||
get() = authDiskSource.getUserBiometricUnlockKey() != null
|
||||
|
||||
|
||||
+24
-2
@@ -72,6 +72,12 @@ class SettingsViewModel @Inject constructor(
|
||||
.map { SettingsAction.Internal.SharedAccountsStateUpdated(it) }
|
||||
.onEach(::handleAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.defaultSaveOptionFlow
|
||||
.map { SettingsAction.Internal.DefaultSaveOptionUpdated(it) }
|
||||
.onEach(::handleAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: SettingsAction) {
|
||||
@@ -103,6 +109,10 @@ class SettingsViewModel @Inject constructor(
|
||||
is SettingsAction.Internal.SharedAccountsStateUpdated -> {
|
||||
handleSharedAccountsStateUpdated(action)
|
||||
}
|
||||
|
||||
is SettingsAction.Internal.DefaultSaveOptionUpdated -> {
|
||||
handleDefaultSaveOptionUpdated(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,14 +185,19 @@ class SettingsViewModel @Inject constructor(
|
||||
SettingsAction.DataClick.BackupClick -> handleBackupClick()
|
||||
SettingsAction.DataClick.SyncWithBitwardenClick -> handleSyncWithBitwardenClick()
|
||||
is SettingsAction.DataClick.DefaultSaveOptionUpdated ->
|
||||
handleDefaultSaveOptionUpdated(action)
|
||||
handleDefaultSaveOptionChosen(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDefaultSaveOptionUpdated(
|
||||
private fun handleDefaultSaveOptionChosen(
|
||||
action: SettingsAction.DataClick.DefaultSaveOptionUpdated,
|
||||
) {
|
||||
settingsRepository.defaultSaveOption = action.option
|
||||
}
|
||||
|
||||
private fun handleDefaultSaveOptionUpdated(
|
||||
action: SettingsAction.Internal.DefaultSaveOptionUpdated,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
defaultSaveOption = action.option,
|
||||
@@ -553,5 +568,12 @@ sealed class SettingsAction(
|
||||
data class SharedAccountsStateUpdated(
|
||||
val state: SharedVerificationCodesState,
|
||||
) : SettingsAction()
|
||||
|
||||
/**
|
||||
* Indicates that the default save option on disk was updated.
|
||||
*/
|
||||
data class DefaultSaveOptionUpdated(
|
||||
val option: DefaultSaveOption,
|
||||
) : SettingsAction()
|
||||
}
|
||||
}
|
||||
+47
-35
@@ -1,8 +1,10 @@
|
||||
package com.bitwarden.authenticator.data.platform.datasource.disk
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.data.platform.base.FakeSharedPreferences
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
|
||||
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.assertNull
|
||||
@@ -54,46 +56,56 @@ class SettingDiskSourceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `defaultSaveOption should read and write from shared preferences`() {
|
||||
fun `defaultSaveOption should read and write from shared preferences`() = runTest {
|
||||
val sharedPrefsKey = "bwPreferencesStorage:defaultSaveOption"
|
||||
|
||||
// Verify initial value is null and disk source should default to NONE
|
||||
assertNull(sharedPreferences.getString(sharedPrefsKey, null))
|
||||
assertEquals(
|
||||
DefaultSaveOption.NONE,
|
||||
settingDiskSource.defaultSaveOption,
|
||||
)
|
||||
settingDiskSource.defaultSaveOptionFlow.test {
|
||||
// Verify initial value is null and disk source should default to NONE
|
||||
assertNull(sharedPreferences.getString(sharedPrefsKey, null))
|
||||
assertEquals(
|
||||
DefaultSaveOption.NONE,
|
||||
settingDiskSource.defaultSaveOption,
|
||||
)
|
||||
assertEquals(
|
||||
DefaultSaveOption.NONE,
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
// Updating the shared preferences should update disk source
|
||||
sharedPreferences.edit {
|
||||
putString(
|
||||
sharedPrefsKey,
|
||||
DefaultSaveOption.BITWARDEN_APP.value,
|
||||
// Updating the shared preferences should update disk source
|
||||
sharedPreferences.edit {
|
||||
putString(
|
||||
sharedPrefsKey,
|
||||
DefaultSaveOption.BITWARDEN_APP.value,
|
||||
)
|
||||
}
|
||||
assertEquals(
|
||||
DefaultSaveOption.BITWARDEN_APP,
|
||||
settingDiskSource.defaultSaveOption,
|
||||
)
|
||||
|
||||
// Updating the disk source should update shared preferences
|
||||
settingDiskSource.defaultSaveOption = DefaultSaveOption.LOCAL
|
||||
assertEquals(
|
||||
DefaultSaveOption.LOCAL.value,
|
||||
sharedPreferences.getString(sharedPrefsKey, null),
|
||||
)
|
||||
assertEquals(
|
||||
DefaultSaveOption.LOCAL,
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
// Incorrect value should default to DefaultSaveOption.NONE
|
||||
sharedPreferences.edit {
|
||||
putString(
|
||||
sharedPrefsKey,
|
||||
"invalid",
|
||||
)
|
||||
}
|
||||
assertEquals(
|
||||
DefaultSaveOption.NONE,
|
||||
settingDiskSource.defaultSaveOption,
|
||||
)
|
||||
}
|
||||
assertEquals(
|
||||
DefaultSaveOption.BITWARDEN_APP,
|
||||
settingDiskSource.defaultSaveOption,
|
||||
)
|
||||
|
||||
// Updating the disk source should update shared preferences
|
||||
settingDiskSource.defaultSaveOption = DefaultSaveOption.LOCAL
|
||||
assertEquals(
|
||||
DefaultSaveOption.LOCAL.value,
|
||||
sharedPreferences.getString(sharedPrefsKey, null),
|
||||
)
|
||||
|
||||
// Incorrect value should default to DefaultSaveOption.NONE
|
||||
sharedPreferences.edit {
|
||||
putString(
|
||||
sharedPrefsKey,
|
||||
"invalid",
|
||||
)
|
||||
}
|
||||
assertEquals(
|
||||
DefaultSaveOption.NONE,
|
||||
settingDiskSource.defaultSaveOption,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+24
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.authenticator.data.platform.repository
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.bitwarden.authenticator.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
import com.bitwarden.authenticator.data.authenticator.datasource.sdk.AuthenticatorSdkSource
|
||||
@@ -12,6 +13,8 @@ import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flow
|
||||
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
|
||||
@@ -93,6 +96,27 @@ class SettingsRepositoryTest {
|
||||
verify { settingsDiskSource.defaultSaveOption = DefaultSaveOption.BITWARDEN_APP }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `defaultSaveOptionFlow should match SettingsDiskSource`() = runTest {
|
||||
// Reading from repository should read from disk source:
|
||||
val expectedOptions = listOf(
|
||||
DefaultSaveOption.NONE,
|
||||
DefaultSaveOption.LOCAL,
|
||||
DefaultSaveOption.BITWARDEN_APP,
|
||||
DefaultSaveOption.NONE,
|
||||
)
|
||||
every { settingsDiskSource.defaultSaveOptionFlow } returns flow {
|
||||
expectedOptions.forEach { emit(it) }
|
||||
}
|
||||
|
||||
settingsRepository.defaultSaveOptionFlow.test {
|
||||
expectedOptions.forEach {
|
||||
assertEquals(it, awaitItem())
|
||||
}
|
||||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `previouslySyncedBitwardenAccountIds should pull from and update SettingsDiskSource`() {
|
||||
// Reading from repository should read from disk source:
|
||||
|
||||
+32
-9
@@ -1,6 +1,7 @@
|
||||
package com.bitwarden.authenticator.ui.platform.feature.settings
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.BuildConfig
|
||||
import com.bitwarden.authenticator.R
|
||||
@@ -11,6 +12,7 @@ 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
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.bitwarden.authenticator.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.authenticator.ui.platform.base.BaseViewModelTest
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import com.bitwarden.authenticator.ui.platform.base.util.concat
|
||||
@@ -29,6 +31,7 @@ import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -46,10 +49,12 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
private val authenticatorRepository: AuthenticatorRepository = mockk {
|
||||
every { sharedCodesStateFlow } returns mutableSharedCodesFlow
|
||||
}
|
||||
private val mutableDefaultSaveOptionFlow = bufferedMutableSharedFlow<DefaultSaveOption>()
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { appLanguage } returns APP_LANGUAGE
|
||||
every { appTheme } returns APP_THEME
|
||||
every { defaultSaveOption } returns DEFAULT_SAVE_OPTION
|
||||
every { defaultSaveOptionFlow } returns mutableDefaultSaveOptionFlow
|
||||
every { isUnlockWithBiometricsEnabled } returns true
|
||||
every { isCrashLoggingEnabled } returns true
|
||||
}
|
||||
@@ -64,6 +69,7 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
every { MOCK_SHARED_CODES_STATE.isSyncWithBitwardenEnabled } returns false
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun teardown() {
|
||||
unmockkStatic(SharedVerificationCodesState::isSyncWithBitwardenEnabled)
|
||||
}
|
||||
@@ -177,22 +183,39 @@ class SettingsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `on DefaultSaveOptionUpdated should update SettingsRepository and state`() {
|
||||
fun `on DefaultSaveOptionUpdated should update SettingsRepository`() {
|
||||
val expectedOption = DefaultSaveOption.BITWARDEN_APP
|
||||
every { settingsRepository.defaultSaveOption = expectedOption } just runs
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(SettingsAction.DataClick.DefaultSaveOptionUpdated(expectedOption))
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
defaultSaveOption = expectedOption,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify { settingsRepository.defaultSaveOption = expectedOption }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `Default save option should update when repository emits`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
|
||||
mutableDefaultSaveOptionFlow.emit(DefaultSaveOption.LOCAL)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
defaultSaveOption = DefaultSaveOption.LOCAL,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
mutableDefaultSaveOptionFlow.emit(DefaultSaveOption.BITWARDEN_APP)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
defaultSaveOption = DefaultSaveOption.BITWARDEN_APP,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
savedState: SettingsState? = DEFAULT_STATE,
|
||||
) = SettingsViewModel(
|
||||
|
||||
Reference in new issue
Block a user