BIT-363, BIT-1323: Add time interval options to session timeout menu (#529)

This commit is contained in:
Brian Yencho
2024-01-08 09:19:55 -06:00
committed by Álison Fernandes
parent 7cfdddaa81
commit e69c4eb29e
8 changed files with 428 additions and 52 deletions

View File

@@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.di
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSourceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -27,4 +29,13 @@ object PlatformDiskModule {
sharedPreferences = sharedPreferences,
json = json,
)
@Provides
@Singleton
fun provideSettingsDiskSource(
sharedPreferences: SharedPreferences,
): SettingsDiskSource =
SettingsDiskSourceImpl(
sharedPreferences = sharedPreferences,
)
}

View File

@@ -2,9 +2,12 @@ package com.x8bit.bitwarden.data.platform.repository.di
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
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.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepositoryImpl
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepositoryImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -30,4 +33,17 @@ object PlatformRepositoryModule {
authDiskSource = authDiskSource,
dispatcherManager = dispatcherManager,
)
@Provides
@Singleton
fun provideSettingsRepository(
authDiskSource: AuthDiskSource,
settingsDiskSource: SettingsDiskSource,
dispatcherManager: DispatcherManager,
): SettingsRepository =
SettingsRepositoryImpl(
authDiskSource = authDiskSource,
settingsDiskSource = settingsDiskSource,
dispatcherManager = dispatcherManager,
)
}

View File

@@ -18,7 +18,9 @@ import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@@ -28,6 +30,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.Text
@@ -40,9 +43,11 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
import com.x8bit.bitwarden.ui.platform.util.displayLabel
/**
* Displays the account security screen.
@@ -198,19 +203,13 @@ fun AccountSecurityScreen(
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
BitwardenTextRow(
text = stringResource(id = R.string.session_timeout),
onClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.SessionTimeoutClick) }
SessionTimeoutRow(
selectedVaultTimeoutType = state.vaultTimeoutType,
onVaultTimeoutTypeSelect = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.VaultTimeoutTypeSelect(it)) }
},
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = state.sessionTimeout(),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
)
BitwardenTextRow(
text = stringResource(id = R.string.session_timeout_action),
onClick = remember(viewModel) {
@@ -286,6 +285,70 @@ fun AccountSecurityScreen(
}
}
@Suppress("LongMethod")
@Composable
private fun SessionTimeoutRow(
selectedVaultTimeoutType: VaultTimeout.Type,
onVaultTimeoutTypeSelect: (VaultTimeout.Type) -> Unit,
modifier: Modifier = Modifier,
) {
var shouldShowSelectionDialog by remember { mutableStateOf(false) }
var shouldShowNeverTimeoutConfirmationDialog by remember { mutableStateOf(false) }
BitwardenTextRow(
text = stringResource(id = R.string.session_timeout),
onClick = { shouldShowSelectionDialog = true },
modifier = modifier,
) {
Text(
text = selectedVaultTimeoutType.displayLabel(),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
when {
shouldShowSelectionDialog -> {
val vaultTimeoutOptions = VaultTimeout.Type.entries
BitwardenSelectionDialog(
title = stringResource(id = R.string.session_timeout),
onDismissRequest = { shouldShowSelectionDialog = false },
) {
vaultTimeoutOptions.forEach { vaultTimeoutOption ->
BitwardenSelectionRow(
text = vaultTimeoutOption.displayLabel,
onClick = {
shouldShowSelectionDialog = false
val selectedType =
vaultTimeoutOptions.first { it == vaultTimeoutOption }
if (selectedType == VaultTimeout.Type.NEVER) {
shouldShowNeverTimeoutConfirmationDialog = true
} else {
onVaultTimeoutTypeSelect(selectedType)
}
},
isSelected = selectedVaultTimeoutType == vaultTimeoutOption,
)
}
}
}
shouldShowNeverTimeoutConfirmationDialog -> {
BitwardenTwoButtonDialog(
title = stringResource(id = R.string.warning),
message = stringResource(id = R.string.never_lock_warning),
confirmButtonText = stringResource(id = R.string.ok),
dismissButtonText = stringResource(id = R.string.cancel),
onConfirmClick = {
shouldShowNeverTimeoutConfirmationDialog = false
onVaultTimeoutTypeSelect(VaultTimeout.Type.NEVER)
},
onDismissClick = { shouldShowNeverTimeoutConfirmationDialog = false },
onDismissRequest = { shouldShowNeverTimeoutConfirmationDialog = false },
)
}
}
}
@Composable
private fun FingerPrintPhraseDialog(
fingerprintPhrase: Text,
@@ -349,7 +412,10 @@ private fun SessionTimeoutActionDialog(
BitwardenSelectionRow(
text = option.text,
isSelected = option == selectedSessionTimeoutAction,
onClick = { onActionSelect(SessionTimeoutAction.values().first { it == option }) },
onClick = {
onActionSelect(
SessionTimeoutAction.values().first { it == option })
},
)
}
}

View File

@@ -5,6 +5,8 @@ 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.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
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
@@ -26,6 +28,7 @@ private const val KEY_STATE = "state"
class AccountSecurityViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val vaultRepository: VaultRepository,
private val settingsRepository: SettingsRepository,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<AccountSecurityState, AccountSecurityEvent, AccountSecurityAction>(
initialState = savedStateHandle[KEY_STATE]
@@ -35,7 +38,7 @@ class AccountSecurityViewModel @Inject constructor(
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
sessionTimeout = "15 Minutes".asText(),
vaultTimeoutType = settingsRepository.vaultTimeout.type,
sessionTimeoutAction = SessionTimeoutAction.LOCK,
),
) {
@@ -58,12 +61,12 @@ class AccountSecurityViewModel @Inject constructor(
is AccountSecurityAction.LoginRequestToggle -> handleLoginRequestToggle(action)
AccountSecurityAction.LogoutClick -> handleLogoutClick()
AccountSecurityAction.PendingLoginRequestsClick -> handlePendingLoginRequestsClick()
is AccountSecurityAction.VaultTimeoutTypeSelect -> handleVaultTimeoutTypeSelect(action)
is AccountSecurityAction.SessionTimeoutActionSelect -> {
handleSessionTimeoutActionSelect(action)
}
AccountSecurityAction.SessionTimeoutActionClick -> handleSessionTimeoutActionClick()
AccountSecurityAction.SessionTimeoutClick -> handleSessionTimeoutClick()
AccountSecurityAction.TwoStepLoginClick -> handleTwoStepLoginClick()
is AccountSecurityAction.UnlockWithBiometricToggle -> {
handleUnlockWithBiometricToggled(action)
@@ -119,6 +122,30 @@ class AccountSecurityViewModel @Inject constructor(
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
}
private fun handleVaultTimeoutTypeSelect(action: AccountSecurityAction.VaultTimeoutTypeSelect) {
val vaultTimeoutType = action.vaultTimeoutType
mutableStateFlow.update {
it.copy(
vaultTimeoutType = action.vaultTimeoutType,
)
}
val vaultTimeout = when (vaultTimeoutType) {
VaultTimeout.Type.IMMEDIATELY -> VaultTimeout.Immediately
VaultTimeout.Type.ONE_MINUTE -> VaultTimeout.OneMinute
VaultTimeout.Type.FIVE_MINUTES -> VaultTimeout.FiveMinutes
VaultTimeout.Type.THIRTY_MINUTES -> VaultTimeout.ThirtyMinutes
VaultTimeout.Type.ONE_HOUR -> VaultTimeout.OneHour
VaultTimeout.Type.FOUR_HOURS -> VaultTimeout.FourHours
VaultTimeout.Type.ON_APP_RESTART -> VaultTimeout.OnAppRestart
VaultTimeout.Type.NEVER -> VaultTimeout.Never
VaultTimeout.Type.CUSTOM -> VaultTimeout.Custom(vaultTimeoutInMinutes = 0)
}
settingsRepository.vaultTimeout = vaultTimeout
// TODO: Finish implementing vault timeouts (BIT-1120)
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
}
private fun handleSessionTimeoutActionSelect(
action: AccountSecurityAction.SessionTimeoutActionSelect,
) {
@@ -136,11 +163,6 @@ class AccountSecurityViewModel @Inject constructor(
mutableStateFlow.update { it.copy(dialog = AccountSecurityDialog.SessionTimeoutAction) }
}
private fun handleSessionTimeoutClick() {
// TODO BIT-462: Implement session timeout
sendEvent(AccountSecurityEvent.ShowToast("Display session timeout dialog.".asText()))
}
private fun handleTwoStepLoginClick() {
// TODO BIT-468: Implement two-step login
sendEvent(AccountSecurityEvent.ShowToast("Not yet implemented.".asText()))
@@ -171,7 +193,7 @@ data class AccountSecurityState(
val isApproveLoginRequestsEnabled: Boolean,
val isUnlockWithBiometricsEnabled: Boolean,
val isUnlockWithPinEnabled: Boolean,
val sessionTimeout: Text,
val vaultTimeoutType: VaultTimeout.Type,
val sessionTimeoutAction: SessionTimeoutAction,
) : Parcelable
@@ -295,6 +317,13 @@ sealed class AccountSecurityAction {
*/
data object PendingLoginRequestsClick : AccountSecurityAction()
/**
* User selected a [vaultTimeoutType].
*/
data class VaultTimeoutTypeSelect(
val vaultTimeoutType: VaultTimeout.Type,
) : AccountSecurityAction()
/**
* User selected a [SessionTimeoutAction].
*/
@@ -307,11 +336,6 @@ sealed class AccountSecurityAction {
*/
data object SessionTimeoutActionClick : AccountSecurityAction()
/**
* User clicked session timeout.
*/
data object SessionTimeoutClick : AccountSecurityAction()
/**
* User clicked two-step login.
*/

View File

@@ -0,0 +1,23 @@
package com.x8bit.bitwarden.ui.platform.util
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
/**
* Provides a human-readable display label for the given [VaultTimeout.Type].
*/
val VaultTimeout.Type.displayLabel: Text
get() = when (this) {
VaultTimeout.Type.IMMEDIATELY -> R.string.immediately
VaultTimeout.Type.ONE_MINUTE -> R.string.one_minute
VaultTimeout.Type.FIVE_MINUTES -> R.string.five_minutes
VaultTimeout.Type.THIRTY_MINUTES -> R.string.thirty_minutes
VaultTimeout.Type.ONE_HOUR -> R.string.one_hour
VaultTimeout.Type.FOUR_HOURS -> R.string.four_hours
VaultTimeout.Type.ON_APP_RESTART -> R.string.on_restart
VaultTimeout.Type.NEVER -> R.string.never
VaultTimeout.Type.CUSTOM -> R.string.custom
}
.asText()