mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
Fill out trusted device UI (#1142)
This commit is contained in:
parent
483a10a3a7
commit
226b62a1cd
5 changed files with 382 additions and 3 deletions
|
@ -1,7 +1,19 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -9,8 +21,13 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
|
@ -18,7 +35,11 @@ import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.handlers.TrustedDeviceH
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
|
||||
/**
|
||||
* The top level composable for the Reset Password screen.
|
||||
|
@ -31,9 +52,15 @@ fun TrustedDeviceScreen(
|
|||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val handlers = remember(viewModel) { TrustedDeviceHandlers.create(viewModel = viewModel) }
|
||||
|
||||
val context = LocalContext.current
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
TrustedDeviceEvent.NavigateBack -> onNavigateBack()
|
||||
is TrustedDeviceEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +70,7 @@ fun TrustedDeviceScreen(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TrustedDeviceScaffold(
|
||||
|
@ -66,5 +94,94 @@ private fun TrustedDeviceScaffold(
|
|||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.remember_this_device),
|
||||
description = stringResource(id = R.string.turn_off_using_public_device),
|
||||
isChecked = state.isRemembered,
|
||||
onCheckedChange = handlers.onRememberToggle,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = R.string.approve_with_my_other_device),
|
||||
onClick = handlers.onApproveWithDeviceClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(id = R.string.request_admin_approval),
|
||||
onClick = handlers.onApproveWithAdminClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(id = R.string.approve_with_master_password),
|
||||
onClick = handlers.onApproveWithPasswordClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.logging_in_as_x_on_y,
|
||||
state.emailAddress,
|
||||
state.environmentLabel,
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "LoggingInAsLabel" }
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
BitwardenClickableText(
|
||||
label = stringResource(id = R.string.not_you),
|
||||
onClick = handlers.onNotYouButtonClick,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
modifier = Modifier.semantics { testTag = "NotYouLabel" },
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TrustedDeviceScaffold_preview() {
|
||||
TrustedDeviceScaffold(
|
||||
state = TrustedDeviceState(
|
||||
isRemembered = false,
|
||||
emailAddress = "email@bitwarden.com",
|
||||
environmentLabel = "vault.bitwarden.pw",
|
||||
),
|
||||
handlers = TrustedDeviceHandlers(
|
||||
onBackClick = {},
|
||||
onRememberToggle = {},
|
||||
onApproveWithAdminClick = {},
|
||||
onApproveWithDeviceClick = {},
|
||||
onApproveWithPasswordClick = {},
|
||||
onNotYouButtonClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
@ -14,23 +19,58 @@ private const val KEY_STATE = "state"
|
|||
class TrustedDeviceViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<TrustedDeviceState, TrustedDeviceEvent, TrustedDeviceAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: TrustedDeviceState,
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: TrustedDeviceState(
|
||||
emailAddress = "",
|
||||
environmentLabel = "",
|
||||
isRemembered = false,
|
||||
),
|
||||
) {
|
||||
override fun handleAction(action: TrustedDeviceAction) {
|
||||
when (action) {
|
||||
TrustedDeviceAction.BackClick -> handleBackClick()
|
||||
is TrustedDeviceAction.RememberToggle -> handleRememberToggle(action)
|
||||
TrustedDeviceAction.ApproveWithAdminClick -> handleApproveWithAdminClick()
|
||||
TrustedDeviceAction.ApproveWithDeviceClick -> handleApproveWithDeviceClick()
|
||||
TrustedDeviceAction.ApproveWithPasswordClick -> handleApproveWithPasswordClick()
|
||||
TrustedDeviceAction.NotYouClick -> handleNotYouClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackClick() {
|
||||
sendEvent(TrustedDeviceEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleRememberToggle(action: TrustedDeviceAction.RememberToggle) {
|
||||
mutableStateFlow.update { it.copy(isRemembered = action.isRemembered) }
|
||||
}
|
||||
|
||||
private fun handleApproveWithAdminClick() {
|
||||
sendEvent(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
|
||||
private fun handleApproveWithDeviceClick() {
|
||||
sendEvent(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
|
||||
private fun handleApproveWithPasswordClick() {
|
||||
sendEvent(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
|
||||
private fun handleNotYouClick() {
|
||||
sendEvent(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models the state for the Trusted Device screen.
|
||||
*/
|
||||
data object TrustedDeviceState
|
||||
@Parcelize
|
||||
data class TrustedDeviceState(
|
||||
val emailAddress: String,
|
||||
val environmentLabel: String,
|
||||
val isRemembered: Boolean,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Models events for the Trusted Device screen.
|
||||
|
@ -40,6 +80,11 @@ sealed class TrustedDeviceEvent {
|
|||
* Navigates back.
|
||||
*/
|
||||
data object NavigateBack : TrustedDeviceEvent()
|
||||
|
||||
/**
|
||||
* Displays the [message] as a toast.
|
||||
*/
|
||||
data class ShowToast(val message: Text) : TrustedDeviceEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,4 +95,31 @@ sealed class TrustedDeviceAction {
|
|||
* User clicked back button.
|
||||
*/
|
||||
data object BackClick : TrustedDeviceAction()
|
||||
|
||||
/**
|
||||
* User toggled the remember device switch.
|
||||
*/
|
||||
data class RememberToggle(
|
||||
val isRemembered: Boolean,
|
||||
) : TrustedDeviceAction()
|
||||
|
||||
/**
|
||||
* User clicked the "Approve with my other device" button.
|
||||
*/
|
||||
data object ApproveWithDeviceClick : TrustedDeviceAction()
|
||||
|
||||
/**
|
||||
* User clicked the "Request admin approval" button.
|
||||
*/
|
||||
data object ApproveWithAdminClick : TrustedDeviceAction()
|
||||
|
||||
/**
|
||||
* User clicked the "Approve with master password" button.
|
||||
*/
|
||||
data object ApproveWithPasswordClick : TrustedDeviceAction()
|
||||
|
||||
/**
|
||||
* Indicates that the "Not you?" text was clicked.
|
||||
*/
|
||||
data object NotYouClick : TrustedDeviceAction()
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.TrustedDeviceViewModel
|
|||
*/
|
||||
data class TrustedDeviceHandlers(
|
||||
val onBackClick: () -> Unit,
|
||||
val onRememberToggle: (Boolean) -> Unit,
|
||||
val onApproveWithDeviceClick: () -> Unit,
|
||||
val onApproveWithAdminClick: () -> Unit,
|
||||
val onApproveWithPasswordClick: () -> Unit,
|
||||
val onNotYouButtonClick: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
|
@ -18,6 +23,21 @@ data class TrustedDeviceHandlers(
|
|||
fun create(viewModel: TrustedDeviceViewModel): TrustedDeviceHandlers =
|
||||
TrustedDeviceHandlers(
|
||||
onBackClick = { viewModel.trySendAction(TrustedDeviceAction.BackClick) },
|
||||
onRememberToggle = {
|
||||
viewModel.trySendAction(TrustedDeviceAction.RememberToggle(it))
|
||||
},
|
||||
onApproveWithDeviceClick = {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithDeviceClick)
|
||||
},
|
||||
onApproveWithAdminClick = {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithAdminClick)
|
||||
},
|
||||
onApproveWithPasswordClick = {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithPasswordClick)
|
||||
},
|
||||
onNotYouButtonClick = {
|
||||
viewModel.trySendAction(TrustedDeviceAction.NotYouClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsOff
|
||||
import androidx.compose.ui.test.assertIsOn
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
@ -46,6 +52,110 @@ class TrustedDeviceScreenTest : BaseComposeTest() {
|
|||
viewModel.trySendAction(TrustedDeviceAction.BackClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on remember toggle changed should send RememberToggle`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Remember this device")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(TrustedDeviceAction.RememberToggle(true))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on approve with device clicked should send ApproveWithDeviceClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Approve with my other device")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithDeviceClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on approve with admin clicked should send ApproveWithAdminClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Request admin approval")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithAdminClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on approve with master password clicked should send ApproveWithPasswordClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Approve with master password")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithPasswordClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on not you clicked should send NotYouClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Not you?")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(TrustedDeviceAction.NotYouClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remember this device toggle should update according to state`() {
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Remember this device")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
|
||||
mutableStateFlow.update { DEFAULT_STATE.copy(isRemembered = true) }
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Remember this device")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email and environment label should update according to state`() {
|
||||
mutableStateFlow.update { DEFAULT_STATE }
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Logging in as bitwarden@email.com on vault.test.pw")
|
||||
.assertDoesNotExist()
|
||||
composeTestRule
|
||||
.onNodeWithText("Logging in as email@bitwarden.com on vault.bitwarden.pw")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(
|
||||
emailAddress = "bitwarden@email.com",
|
||||
environmentLabel = "vault.test.pw",
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Logging in as email@bitwarden.com on vault.bitwarden.pw")
|
||||
.assertDoesNotExist()
|
||||
composeTestRule
|
||||
.onNodeWithText("Logging in as bitwarden@email.com on vault.test.pw")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: TrustedDeviceState = TrustedDeviceState
|
||||
private val DEFAULT_STATE: TrustedDeviceState = TrustedDeviceState(
|
||||
emailAddress = "email@bitwarden.com",
|
||||
environmentLabel = "vault.bitwarden.pw",
|
||||
isRemembered = false,
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -19,6 +20,59 @@ class TrustedDeviceViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on RememberToggle updates the isRemembered state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
viewModel.trySendAction(TrustedDeviceAction.RememberToggle(isRemembered = true))
|
||||
assertEquals(DEFAULT_STATE.copy(isRemembered = true), awaitItem())
|
||||
viewModel.trySendAction(TrustedDeviceAction.RememberToggle(isRemembered = false))
|
||||
assertEquals(DEFAULT_STATE.copy(isRemembered = false), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ApproveWithAdminClick emits ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithAdminClick)
|
||||
assertEquals(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ApproveWithDeviceClick emits ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithDeviceClick)
|
||||
assertEquals(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ApproveWithPasswordClick emits ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(TrustedDeviceAction.ApproveWithPasswordClick)
|
||||
assertEquals(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NotYouClick emits ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(TrustedDeviceAction.NotYouClick)
|
||||
assertEquals(TrustedDeviceEvent.ShowToast("Not yet implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: TrustedDeviceState? = null,
|
||||
): TrustedDeviceViewModel =
|
||||
|
@ -26,3 +80,9 @@ class TrustedDeviceViewModelTest : BaseViewModelTest() {
|
|||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: TrustedDeviceState = TrustedDeviceState(
|
||||
emailAddress = "",
|
||||
environmentLabel = "",
|
||||
isRemembered = false,
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue