mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
Add skeleton for trusted device UI (#1132)
This commit is contained in:
parent
05079f2a32
commit
509ef72546
7 changed files with 248 additions and 0 deletions
|
@ -20,6 +20,7 @@ import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.loginWithDeviceDestin
|
|||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.navigateToLoginWithDevice
|
||||
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.masterPasswordHintDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.masterpasswordhint.navigateToMasterPasswordHint
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.trustedDeviceDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLogin
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestination
|
||||
|
||||
|
@ -106,6 +107,9 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
|
|||
masterPasswordHintDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
trustedDeviceDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
twoFactorLoginDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
|
||||
|
||||
private const val TRUSTED_DEVICE_ROUTE: String = "trusted_device"
|
||||
|
||||
/**
|
||||
* Add the Trusted Device Screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.trustedDeviceDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composableWithSlideTransitions(
|
||||
route = TRUSTED_DEVICE_ROUTE,
|
||||
) {
|
||||
TrustedDeviceScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the Trusted Device Screen.
|
||||
*/
|
||||
fun NavController.navigateToTrustedDevice(navOptions: NavOptions? = null) {
|
||||
this.navigate(TRUSTED_DEVICE_ROUTE, navOptions)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.handlers.TrustedDeviceHandlers
|
||||
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.scaffold.BitwardenScaffold
|
||||
|
||||
/**
|
||||
* The top level composable for the Reset Password screen.
|
||||
*/
|
||||
@Composable
|
||||
fun TrustedDeviceScreen(
|
||||
viewModel: TrustedDeviceViewModel = hiltViewModel(),
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val handlers = remember(viewModel) { TrustedDeviceHandlers.create(viewModel = viewModel) }
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
TrustedDeviceEvent.NavigateBack -> onNavigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
TrustedDeviceScaffold(
|
||||
state = state,
|
||||
handlers = handlers,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TrustedDeviceScaffold(
|
||||
state: TrustedDeviceState,
|
||||
handlers: TrustedDeviceHandlers,
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.log_in_initiated),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = NavigationIcon(
|
||||
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||
onNavigationIconClick = handlers.onBackClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
/**
|
||||
* Manages application state for the Trusted Device screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class TrustedDeviceViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<TrustedDeviceState, TrustedDeviceEvent, TrustedDeviceAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: TrustedDeviceState,
|
||||
) {
|
||||
override fun handleAction(action: TrustedDeviceAction) {
|
||||
when (action) {
|
||||
TrustedDeviceAction.BackClick -> handleBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackClick() {
|
||||
sendEvent(TrustedDeviceEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models the state for the Trusted Device screen.
|
||||
*/
|
||||
data object TrustedDeviceState
|
||||
|
||||
/**
|
||||
* Models events for the Trusted Device screen.
|
||||
*/
|
||||
sealed class TrustedDeviceEvent {
|
||||
/**
|
||||
* Navigates back.
|
||||
*/
|
||||
data object NavigateBack : TrustedDeviceEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the Trusted Device screen.
|
||||
*/
|
||||
sealed class TrustedDeviceAction {
|
||||
/**
|
||||
* User clicked back button.
|
||||
*/
|
||||
data object BackClick : TrustedDeviceAction()
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice.handlers
|
||||
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.TrustedDeviceAction
|
||||
import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.TrustedDeviceViewModel
|
||||
|
||||
/**
|
||||
* A collection of handler functions for managing actions within the context of the Trusted Device
|
||||
* Screen.
|
||||
*/
|
||||
data class TrustedDeviceHandlers(
|
||||
val onBackClick: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Creates an instance of [TrustedDeviceHandlers] by binding actions to the provided
|
||||
* [TrustedDeviceViewModel].
|
||||
*/
|
||||
fun create(viewModel: TrustedDeviceViewModel): TrustedDeviceHandlers =
|
||||
TrustedDeviceHandlers(
|
||||
onBackClick = { viewModel.trySendAction(TrustedDeviceAction.BackClick) },
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.trusteddevice
|
||||
|
||||
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 kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
class TrustedDeviceScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled: Boolean = false
|
||||
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<TrustedDeviceEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
val viewModel = mockk<TrustedDeviceViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
TrustedDeviceScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(TrustedDeviceEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: TrustedDeviceState = TrustedDeviceState
|
|
@ -0,0 +1,28 @@
|
|||
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 kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class TrustedDeviceViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `on BackClick emits NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(TrustedDeviceAction.BackClick)
|
||||
assertEquals(TrustedDeviceEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: TrustedDeviceState? = null,
|
||||
): TrustedDeviceViewModel =
|
||||
TrustedDeviceViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
)
|
||||
}
|
Loading…
Add table
Reference in a new issue