Add SettingsRepository and VaultTimeout (#519)

This commit is contained in:
Brian Yencho 2024-01-07 12:12:01 -06:00 committed by Álison Fernandes
parent 54c288cb25
commit 5469874c95
4 changed files with 263 additions and 0 deletions

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.platform.repository
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import kotlinx.coroutines.flow.StateFlow
/**
* Provides an API for observing and modifying settings state.
*/
interface SettingsRepository {
/**
* Gets updates for the [VaultTimeout] associated with the given [userId].
*/
fun getVaultTimeoutStateFlow(userId: String): StateFlow<VaultTimeout>
/**
* Stores the given [vaultTimeout] for the given [userId].
*/
fun storeVaultTimeout(userId: String, vaultTimeout: VaultTimeout)
}

View file

@ -0,0 +1,55 @@
package com.x8bit.bitwarden.data.platform.repository
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/**
* Primary implementation of [SettingsRepository].
*/
class SettingsRepositoryImpl(
private val settingsDiskSource: SettingsDiskSource,
private val dispatcherManager: DispatcherManager,
) : SettingsRepository {
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
override fun getVaultTimeoutStateFlow(userId: String): StateFlow<VaultTimeout> =
settingsDiskSource
.getVaultTimeoutInMinutesFlow(userId = userId)
.map { it.toVaultTimeout() }
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Eagerly,
initialValue = settingsDiskSource
.getVaultTimeoutInMinutes(userId = userId)
.toVaultTimeout(),
)
override fun storeVaultTimeout(userId: String, vaultTimeout: VaultTimeout) {
settingsDiskSource.storeVaultTimeoutInMinutes(
userId = userId,
vaultTimeoutInMinutes = vaultTimeout.vaultTimeoutInMinutes,
)
}
}
/**
* Converts a stored [Int] representing a vault timeout in minutes to a [VaultTimeout].
*/
private fun Int?.toVaultTimeout(): VaultTimeout =
when (this) {
VaultTimeout.Immediately.vaultTimeoutInMinutes -> VaultTimeout.Immediately
VaultTimeout.OneMinute.vaultTimeoutInMinutes -> VaultTimeout.OneMinute
VaultTimeout.FiveMinutes.vaultTimeoutInMinutes -> VaultTimeout.FiveMinutes
VaultTimeout.ThirtyMinutes.vaultTimeoutInMinutes -> VaultTimeout.ThirtyMinutes
VaultTimeout.OneHour.vaultTimeoutInMinutes -> VaultTimeout.OneHour
VaultTimeout.FourHours.vaultTimeoutInMinutes -> VaultTimeout.FourHours
VaultTimeout.OnAppRestart.vaultTimeoutInMinutes -> VaultTimeout.OnAppRestart
null -> VaultTimeout.Never
else -> VaultTimeout.Custom(vaultTimeoutInMinutes = this)
}

View file

@ -0,0 +1,118 @@
package com.x8bit.bitwarden.data.platform.repository.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
/**
* Represents the timeout period for a user's vault.
*/
@Suppress("MagicNumber")
sealed class VaultTimeout : Parcelable {
/**
* The type of timeout.
*/
abstract val type: Type
/**
* The time (in minutes) that the vault can stay unlocked before it will automatically lock
* itself (or `null` to allow for vault that never locks).
*/
abstract val vaultTimeoutInMinutes: Int?
/**
* The vault should be considered timed-out immediately.
*/
@Parcelize
data object Immediately : VaultTimeout() {
override val type: Type get() = Type.IMMEDIATELY
override val vaultTimeoutInMinutes: Int get() = 0
}
/**
* The vault should time out after one minute.
*/
@Parcelize
data object OneMinute : VaultTimeout() {
override val type: Type get() = Type.ONE_MINUTE
override val vaultTimeoutInMinutes: Int get() = 1
}
/**
* The vault should time out after five minutes.
*/
@Parcelize
data object FiveMinutes : VaultTimeout() {
override val type: Type get() = Type.FIVE_MINUTES
override val vaultTimeoutInMinutes: Int get() = 5
}
/**
* The vault should time out after thirty minutes.
*/
@Parcelize
data object ThirtyMinutes : VaultTimeout() {
override val type: Type get() = Type.THIRTY_MINUTES
override val vaultTimeoutInMinutes: Int get() = 30
}
/**
* The vault should time out after one hour.
*/
@Parcelize
data object OneHour : VaultTimeout() {
override val type: Type get() = Type.ONE_HOUR
override val vaultTimeoutInMinutes: Int get() = 60
}
/**
* The vault should time out after four hours.
*/
@Parcelize
data object FourHours : VaultTimeout() {
override val type: Type get() = Type.FOUR_HOURS
override val vaultTimeoutInMinutes: Int get() = 240
}
/**
* The vault should time out after an app restart.
*/
@Parcelize
data object OnAppRestart : VaultTimeout() {
override val type: Type get() = Type.ON_APP_RESTART
override val vaultTimeoutInMinutes: Int get() = -1
}
/**
* The vault should never automatically timeout.
*/
@Parcelize
data object Never : VaultTimeout() {
override val type: Type get() = Type.NEVER
override val vaultTimeoutInMinutes: Int? get() = null
}
/**
* The timeout period is a custom value given by the dynamic [vaultTimeoutInMinutes].
*/
@Parcelize
data class Custom(
override val vaultTimeoutInMinutes: Int,
) : VaultTimeout() {
override val type: Type get() = Type.CUSTOM
}
/**
* The specific type of timeout.
*/
enum class Type {
IMMEDIATELY,
ONE_MINUTE,
FIVE_MINUTES,
THIRTY_MINUTES,
ONE_HOUR,
FOUR_HOURS,
ON_APP_RESTART,
NEVER,
CUSTOM,
}
}

View file

@ -0,0 +1,71 @@
package com.x8bit.bitwarden.data.platform.repository
import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class SettingsRepositoryTest {
private val fakeSettingsDiskSource = FakeSettingsDiskSource()
private val settingsRepository = SettingsRepositoryImpl(
settingsDiskSource = fakeSettingsDiskSource,
dispatcherManager = FakeDispatcherManager(),
)
@Test
fun `getVaultTimeoutStateFlow should react to changes in SettingsDiskSource`() = runTest {
val userId = "userId"
settingsRepository
.getVaultTimeoutStateFlow(userId = userId)
.test {
assertEquals(
VaultTimeout.Never,
awaitItem(),
)
VAULT_TIMEOUT_MAP.forEach { (vaultTimeout, vaultTimeoutInMinutes) ->
fakeSettingsDiskSource.storeVaultTimeoutInMinutes(
userId = userId,
vaultTimeoutInMinutes = vaultTimeoutInMinutes,
)
assertEquals(
vaultTimeout,
awaitItem(),
)
}
}
}
@Test
fun `storeVaultTimeout should properly update SettingsDiskSource`() {
val userId = "userId"
VAULT_TIMEOUT_MAP.forEach { (vaultTimeout, vaultTimeoutInMinutes) ->
settingsRepository.storeVaultTimeout(
userId = userId,
vaultTimeout = vaultTimeout,
)
assertEquals(
vaultTimeoutInMinutes,
fakeSettingsDiskSource.getVaultTimeoutInMinutes(userId = userId),
)
}
}
}
/**
* Maps a VaultTimeout to its expected vaultTimeoutInMinutes value.
*/
private val VAULT_TIMEOUT_MAP =
mapOf(
VaultTimeout.OneMinute to 1,
VaultTimeout.FiveMinutes to 5,
VaultTimeout.ThirtyMinutes to 30,
VaultTimeout.OneHour to 60,
VaultTimeout.FourHours to 240,
VaultTimeout.OnAppRestart to -1,
VaultTimeout.Never to null,
VaultTimeout.Custom(vaultTimeoutInMinutes = 123) to 123,
)