mirror of
https://github.com/bitwarden/android.git
synced 2026-06-08 08:06:32 -05:00
BIT-74: Add Login with Device screen (#438)
This commit is contained in:
committed by
Álison Fernandes
parent
0dd162598f
commit
5ac493fa89
@@ -16,6 +16,8 @@ import com.x8bit.bitwarden.ui.auth.feature.landing.LANDING_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.landingDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.login.loginDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.login.navigateToLogin
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.loginWithDeviceDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.navigateToLoginWithDevice
|
||||
|
||||
const val AUTH_GRAPH_ROUTE: String = "auth_graph"
|
||||
|
||||
@@ -57,6 +59,10 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
|
||||
loginDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToEnterpriseSignOn = { navController.navigateToEnterpriseSignOn() },
|
||||
onNavigateToLoginWithDevice = { navController.navigateToLoginWithDevice() },
|
||||
)
|
||||
loginWithDeviceDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
)
|
||||
environmentDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
|
||||
@@ -45,6 +45,7 @@ fun NavController.navigateToLogin(
|
||||
fun NavGraphBuilder.loginDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToEnterpriseSignOn: () -> Unit,
|
||||
onNavigateToLoginWithDevice: () -> Unit,
|
||||
) {
|
||||
composable(
|
||||
route = LOGIN_ROUTE,
|
||||
@@ -63,6 +64,7 @@ fun NavGraphBuilder.loginDestination(
|
||||
LoginScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
onNavigateToEnterpriseSignOn = onNavigateToEnterpriseSignOn,
|
||||
onNavigateToLoginWithDevice = onNavigateToLoginWithDevice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenClickableText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOutlinedButtonWithIcon
|
||||
@@ -63,6 +64,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
fun LoginScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToEnterpriseSignOn: () -> Unit,
|
||||
onNavigateToLoginWithDevice: () -> Unit,
|
||||
viewModel: LoginViewModel = hiltViewModel(),
|
||||
intentHandler: IntentHandler = IntentHandler(context = LocalContext.current),
|
||||
) {
|
||||
@@ -76,6 +78,7 @@ fun LoginScreen(
|
||||
}
|
||||
|
||||
LoginEvent.NavigateToEnterpriseSignOn -> onNavigateToEnterpriseSignOn()
|
||||
LoginEvent.NavigateToLoginWithDevice -> onNavigateToLoginWithDevice()
|
||||
is LoginEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@@ -132,6 +135,9 @@ fun LoginScreen(
|
||||
onLoginButtonClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginAction.LoginButtonClick) }
|
||||
},
|
||||
onLoginWithDeviceClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginAction.LoginWithDeviceButtonClick) }
|
||||
},
|
||||
onSingleSignOnClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginAction.SingleSignOnClick) }
|
||||
},
|
||||
@@ -176,6 +182,7 @@ private fun LoginScreenContent(
|
||||
onPasswordInputChanged: (String) -> Unit,
|
||||
onMasterPasswordClick: () -> Unit,
|
||||
onLoginButtonClick: () -> Unit,
|
||||
onLoginWithDeviceClick: () -> Unit,
|
||||
onSingleSignOnClick: () -> Unit,
|
||||
onNotYouButtonClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -236,6 +243,18 @@ private fun LoginScreenContent(
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// TODO BIT-808: Hide button for first-time users
|
||||
BitwardenOutlinedButtonWithIcon(
|
||||
label = stringResource(id = R.string.log_in_with_device),
|
||||
icon = painterResource(id = R.drawable.ic_device),
|
||||
onClick = onLoginWithDeviceClick,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "LogInWithAnotherDeviceButton" }
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
BitwardenOutlinedButtonWithIcon(
|
||||
label = stringResource(id = R.string.log_in_sso),
|
||||
icon = painterResource(id = R.drawable.ic_briefcase),
|
||||
@@ -263,15 +282,11 @@ private fun LoginScreenContent(
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// TODO: Need to figure out better handling for very small clickable text (BIT-724)
|
||||
Text(
|
||||
BitwardenClickableText(
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "NotYouLabel" }
|
||||
.clickable { onNotYouButtonClick() },
|
||||
text = stringResource(id = R.string.not_you),
|
||||
textAlign = TextAlign.Start,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
.semantics { testTag = "NotYouLabel" },
|
||||
onClick = onNotYouButtonClick,
|
||||
label = stringResource(id = R.string.not_you),
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ class LoginViewModel @Inject constructor(
|
||||
is LoginAction.SwitchAccountClick -> handleSwitchAccountClicked(action)
|
||||
is LoginAction.CloseButtonClick -> handleCloseButtonClicked()
|
||||
LoginAction.LoginButtonClick -> handleLoginButtonClicked()
|
||||
LoginAction.LoginWithDeviceButtonClick -> handleLoginWithDeviceButtonClicked()
|
||||
LoginAction.MasterPasswordHintClick -> handleMasterPasswordHintClicked()
|
||||
LoginAction.NotYouButtonClick -> handleNotYouButtonClicked()
|
||||
LoginAction.SingleSignOnClick -> handleSingleSignOnClicked()
|
||||
@@ -172,6 +173,10 @@ class LoginViewModel @Inject constructor(
|
||||
attemptLogin()
|
||||
}
|
||||
|
||||
private fun handleLoginWithDeviceButtonClicked() {
|
||||
sendEvent(LoginEvent.NavigateToLoginWithDevice)
|
||||
}
|
||||
|
||||
private fun attemptLogin() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
@@ -251,6 +256,11 @@ sealed class LoginEvent {
|
||||
*/
|
||||
data object NavigateToEnterpriseSignOn : LoginEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the login with device screen.
|
||||
*/
|
||||
data object NavigateToLoginWithDevice : LoginEvent()
|
||||
|
||||
/**
|
||||
* Shows a toast with the given [message].
|
||||
*/
|
||||
@@ -301,6 +311,11 @@ sealed class LoginAction {
|
||||
*/
|
||||
data object LoginButtonClick : LoginAction()
|
||||
|
||||
/**
|
||||
* Indicates that the Login With Device button has been clicked.
|
||||
*/
|
||||
data object LoginWithDeviceButtonClick : LoginAction()
|
||||
|
||||
/**
|
||||
* Indicates that the "Not you?" text was clicked.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
import com.x8bit.bitwarden.ui.platform.theme.TransitionProviders
|
||||
|
||||
private const val LOGIN_WITH_DEVICE_ROUTE = "login_with_device"
|
||||
|
||||
/**
|
||||
* Navigate to the Login with Device screen.
|
||||
*/
|
||||
fun NavController.navigateToLoginWithDevice(navOptions: NavOptions? = null) {
|
||||
this.navigate(LOGIN_WITH_DEVICE_ROUTE, navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Login with Device screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.loginWithDeviceDestination(
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
composable(
|
||||
route = LOGIN_WITH_DEVICE_ROUTE,
|
||||
enterTransition = TransitionProviders.Enter.slideUp,
|
||||
exitTransition = TransitionProviders.Exit.stay,
|
||||
popEnterTransition = TransitionProviders.Enter.stay,
|
||||
popExitTransition = TransitionProviders.Exit.slideDown,
|
||||
) {
|
||||
LoginWithDeviceScreen(
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.imePadding
|
||||
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.CircularProgressIndicator
|
||||
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
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
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.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenClickableText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||
|
||||
/**
|
||||
* The top level composable for the Login with Device screen.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginWithDeviceScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
viewModel: LoginWithDeviceViewModel = hiltViewModel(),
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
LoginWithDeviceEvent.NavigateBack -> onNavigateBack()
|
||||
is LoginWithDeviceEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.log_in_with_device),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||
onNavigationIconClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginWithDeviceAction.CloseButtonClick) }
|
||||
},
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
val modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
when (val viewState = state.viewState) {
|
||||
is LoginWithDeviceState.ViewState.Content -> {
|
||||
LoginWithDeviceScreenContent(
|
||||
state = viewState,
|
||||
onResendNotificationClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginWithDeviceAction.ResendNotificationClick) }
|
||||
},
|
||||
onViewAllLogInOptionsClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(LoginWithDeviceAction.ViewAllLogInOptionsClick) }
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is LoginWithDeviceState.ViewState.Error -> {
|
||||
BitwardenErrorContent(
|
||||
message = viewState.message(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
LoginWithDeviceState.ViewState.Loading -> {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun LoginWithDeviceScreenContent(
|
||||
state: LoginWithDeviceState.ViewState.Content,
|
||||
onResendNotificationClick: () -> Unit,
|
||||
onViewAllLogInOptionsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier
|
||||
.semantics { testTagsAsResourceId = true }
|
||||
.imePadding()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.log_in_initiated),
|
||||
textAlign = TextAlign.Start,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.a_notification_has_been_sent_to_your_device),
|
||||
textAlign = TextAlign.Start,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
Text(
|
||||
text = stringResource(id = R.string.please_make_sure_your_vault_is_unlocked_and_the_fingerprint_phrase_matches_on_the_other_device),
|
||||
textAlign = TextAlign.Start,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.fingerprint_phrase),
|
||||
textAlign = TextAlign.Start,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = state.fingerprintPhrase,
|
||||
textAlign = TextAlign.Start,
|
||||
color = LocalNonMaterialColors.current.fingerprint,
|
||||
style = LocalNonMaterialTypography.current.sensitiveInfoSmall,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "FingerprintValueLabel" }
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
BitwardenClickableText(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.semantics { testTag = "ResendNotificationButton" }
|
||||
.fillMaxWidth(),
|
||||
label = stringResource(id = R.string.resend_notification),
|
||||
onClick = onResendNotificationClick,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.need_another_option),
|
||||
textAlign = TextAlign.Start,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
BitwardenClickableText(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.semantics { testTag = "ViewAllLoginOptionsButton" }
|
||||
.fillMaxWidth(),
|
||||
label = stringResource(id = R.string.view_all_login_options),
|
||||
onClick = onViewAllLogInOptionsClick,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
|
||||
|
||||
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
/**
|
||||
* Manages application state for the Login with Device screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class LoginWithDeviceViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<LoginWithDeviceState, LoginWithDeviceEvent, LoginWithDeviceAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: LoginWithDeviceState(
|
||||
viewState = LoginWithDeviceState.ViewState.Loading,
|
||||
),
|
||||
) {
|
||||
init {
|
||||
mutableStateFlow.update {
|
||||
// TODO BIT-809: Pull phrase from SDK
|
||||
it.copy(
|
||||
viewState = LoginWithDeviceState.ViewState.Content(
|
||||
fingerprintPhrase = "alabster-drinkable-mystified-rapping-irrigate",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: LoginWithDeviceAction) {
|
||||
when (action) {
|
||||
LoginWithDeviceAction.CloseButtonClick -> handleCloseButtonClicked()
|
||||
LoginWithDeviceAction.ResendNotificationClick -> handleResendNotificationClicked()
|
||||
LoginWithDeviceAction.ViewAllLogInOptionsClick -> handleViewAllLogInOptionsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCloseButtonClicked() {
|
||||
sendEvent(LoginWithDeviceEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleResendNotificationClicked() {
|
||||
// TODO BIT-810: implement Resend Notification button
|
||||
sendEvent(LoginWithDeviceEvent.ShowToast("Not yet implemented."))
|
||||
}
|
||||
|
||||
private fun handleViewAllLogInOptionsClicked() {
|
||||
sendEvent(LoginWithDeviceEvent.NavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models state of the Login with Device screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data class LoginWithDeviceState(
|
||||
val viewState: ViewState,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Represents the specific view states for the [LoginWithDeviceScreen].
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class ViewState : Parcelable {
|
||||
/**
|
||||
* Loading state for the [LoginWithDeviceScreen], signifying that the content is being
|
||||
* processed.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Loading : ViewState()
|
||||
|
||||
/**
|
||||
* Represents a state where the [LoginWithDeviceScreen] is unable to display data due to an
|
||||
* error retrieving it.
|
||||
*
|
||||
* @property message The message to display on the error screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Error(
|
||||
val message: Text,
|
||||
) : ViewState()
|
||||
|
||||
/**
|
||||
* Content state for the [LoginWithDeviceScreen] showing the actual content or items.
|
||||
*
|
||||
* @property fingerprintPhrase The fingerprint phrase to present to the user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Content(
|
||||
val fingerprintPhrase: String,
|
||||
) : ViewState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models events for the Login with Device screen.
|
||||
*/
|
||||
sealed class LoginWithDeviceEvent {
|
||||
/**
|
||||
* Navigates back to the previous screen.
|
||||
*/
|
||||
data object NavigateBack : LoginWithDeviceEvent()
|
||||
|
||||
/**
|
||||
* Shows a toast with the given [message].
|
||||
*/
|
||||
data class ShowToast(
|
||||
val message: String,
|
||||
) : LoginWithDeviceEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the Login with Device screen.
|
||||
*/
|
||||
sealed class LoginWithDeviceAction {
|
||||
/**
|
||||
* Indicates that the top-bar close button was clicked.
|
||||
*/
|
||||
data object CloseButtonClick : LoginWithDeviceAction()
|
||||
|
||||
/**
|
||||
* Indicates that the "Resend notification" text has been clicked.
|
||||
*/
|
||||
data object ResendNotificationClick : LoginWithDeviceAction()
|
||||
|
||||
/**
|
||||
* Indicates that the "View all log in options" text has been clicked.
|
||||
*/
|
||||
data object ViewAllLogInOptionsClick : LoginWithDeviceAction()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
/**
|
||||
* Represents a Bitwarden-styled clickable text.
|
||||
*
|
||||
* @param label The label for the button.
|
||||
* @param onClick The callback when the button is clicked.
|
||||
* @param modifier The [Modifier] to be applied to the button.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenClickableText(
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier
|
||||
// TODO: Need to figure out better handling for very small clickable text (BIT-724)
|
||||
.clickable { onClick() },
|
||||
text = label,
|
||||
textAlign = TextAlign.Start,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenTextButton_preview() {
|
||||
BitwardenTextButton(
|
||||
label = "Label",
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user