mirror of
https://github.com/bitwarden/android.git
synced 2025-12-12 00:08:00 -06:00
PM-28522: Update the Login With Device Screen (#6184)
This commit is contained in:
parent
f02b374e98
commit
ca7a65fc95
@ -1,16 +1,11 @@
|
|||||||
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
|
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@ -20,7 +15,6 @@ import androidx.compose.material3.rememberTopAppBarState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
@ -30,13 +24,17 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
|
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||||
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
|
import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
|
||||||
import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent
|
import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent
|
||||||
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||||
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
||||||
import com.bitwarden.ui.platform.components.indicator.BitwardenCircularProgressIndicator
|
import com.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||||
|
import com.bitwarden.ui.platform.components.field.model.TextToolbarType
|
||||||
|
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.bitwarden.ui.platform.components.text.BitwardenClickableText
|
import com.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
|
||||||
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||||
@ -120,111 +118,99 @@ private fun LoginWithDeviceScreenContent(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = state.title(),
|
text = state.title(),
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Center,
|
||||||
style = BitwardenTheme.typography.headlineMedium,
|
style = BitwardenTheme.typography.titleMedium,
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
color = BitwardenTheme.colorScheme.text.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = state.subtitle(),
|
text = state.subtitle(),
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Center,
|
||||||
style = BitwardenTheme.typography.bodyMedium,
|
style = BitwardenTheme.typography.bodyMedium,
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
color = BitwardenTheme.colorScheme.text.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = state.description(),
|
text = state.description(),
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Center,
|
||||||
style = BitwardenTheme.typography.bodyMedium,
|
style = BitwardenTheme.typography.bodyMedium,
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
color = BitwardenTheme.colorScheme.text.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Text(
|
BitwardenTextField(
|
||||||
text = stringResource(id = BitwardenString.fingerprint_phrase),
|
label = stringResource(id = BitwardenString.fingerprint_phrase),
|
||||||
textAlign = TextAlign.Start,
|
value = state.fingerprintPhrase,
|
||||||
style = BitwardenTheme.typography.titleLarge,
|
textFieldTestTag = "FingerprintPhraseValue",
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
onValueChange = { },
|
||||||
|
readOnly = true,
|
||||||
|
singleLine = false,
|
||||||
|
textToolbarType = TextToolbarType.NONE,
|
||||||
|
textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
|
||||||
|
textColor = BitwardenTheme.colorScheme.text.codePink,
|
||||||
|
cardStyle = CardStyle.Full,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = state.fingerprintPhrase,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
color = BitwardenTheme.colorScheme.text.codePink,
|
|
||||||
style = BitwardenTheme.typography.sensitiveInfoSmall,
|
|
||||||
minLines = 2,
|
|
||||||
modifier = Modifier
|
|
||||||
.testTag("FingerprintPhraseValue")
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (state.allowsResend) {
|
if (state.allowsResend) {
|
||||||
Column(
|
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||||
verticalArrangement = Arrangement.Center,
|
BitwardenOutlinedButton(
|
||||||
modifier = Modifier
|
|
||||||
.defaultMinSize(minHeight = 40.dp)
|
|
||||||
.align(Alignment.Start),
|
|
||||||
) {
|
|
||||||
if (state.isResendNotificationLoading) {
|
|
||||||
BitwardenCircularProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 64.dp)
|
|
||||||
.size(size = 16.dp),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
BitwardenClickableText(
|
|
||||||
modifier = Modifier.testTag("ResendNotificationButton"),
|
|
||||||
label = stringResource(id = BitwardenString.resend_notification),
|
label = stringResource(id = BitwardenString.resend_notification),
|
||||||
style = BitwardenTheme.typography.labelLarge,
|
|
||||||
innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
|
||||||
onClick = onResendNotificationClick,
|
onClick = onResendNotificationClick,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(tag = "ResendNotificationButton")
|
||||||
|
.standardHorizontalMargin()
|
||||||
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(28.dp))
|
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = state.otherOptions(),
|
text = state.otherOptions(),
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Center,
|
||||||
style = BitwardenTheme.typography.bodyMedium,
|
style = BitwardenTheme.typography.bodySmall,
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
color = BitwardenTheme.colorScheme.text.secondary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.standardHorizontalMargin()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
BitwardenClickableText(
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
modifier = Modifier.testTag("ViewAllLoginOptionsButton"),
|
|
||||||
label = stringResource(id = BitwardenString.view_all_login_options),
|
BitwardenHyperTextLink(
|
||||||
innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
annotatedResId = BitwardenString.need_another_option_view_all_login_options,
|
||||||
style = BitwardenTheme.typography.labelLarge,
|
annotationKey = "viewAll",
|
||||||
|
accessibilityString = stringResource(id = BitwardenString.view_all_login_options),
|
||||||
onClick = onViewAllLogInOptionsClick,
|
onClick = onViewAllLogInOptionsClick,
|
||||||
|
style = BitwardenTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(tag = "ViewAllLoginOptionsButton")
|
||||||
|
.standardHorizontalMargin()
|
||||||
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
private var authJob: Job = Job().apply { complete() }
|
private var authJob: Job = Job().apply { complete() }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sendNewAuthRequest(isResend = false)
|
sendNewAuthRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: LoginWithDeviceAction) {
|
override fun handleAction(action: LoginWithDeviceAction) {
|
||||||
@ -74,7 +74,14 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResendNotificationClicked() {
|
private fun handleResendNotificationClicked() {
|
||||||
sendNewAuthRequest(isResend = true)
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState = LoginWithDeviceState.DialogState.Loading(
|
||||||
|
message = BitwardenString.resending.asText(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sendNewAuthRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleViewAllLogInOptionsClicked() {
|
private fun handleViewAllLogInOptionsClicked() {
|
||||||
@ -99,9 +106,6 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
when (val result = action.result) {
|
when (val result = action.result) {
|
||||||
is CreateAuthRequestResult.Success -> {
|
is CreateAuthRequestResult.Success -> {
|
||||||
updateContent { content ->
|
|
||||||
content.copy(isResendNotificationLoading = false)
|
|
||||||
}
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
@ -123,7 +127,6 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
viewState = LoginWithDeviceState.ViewState.Content(
|
viewState = LoginWithDeviceState.ViewState.Content(
|
||||||
loginWithDeviceType = it.loginWithDeviceType,
|
loginWithDeviceType = it.loginWithDeviceType,
|
||||||
fingerprintPhrase = result.authRequest.fingerprint,
|
fingerprintPhrase = result.authRequest.fingerprint,
|
||||||
isResendNotificationLoading = false,
|
|
||||||
),
|
),
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
)
|
)
|
||||||
@ -131,9 +134,6 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is CreateAuthRequestResult.Error -> {
|
is CreateAuthRequestResult.Error -> {
|
||||||
updateContent { content ->
|
|
||||||
content.copy(isResendNotificationLoading = false)
|
|
||||||
}
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialogState = LoginWithDeviceState.DialogState.Error(
|
dialogState = LoginWithDeviceState.DialogState.Error(
|
||||||
@ -149,9 +149,6 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
CreateAuthRequestResult.Declined -> Unit
|
CreateAuthRequestResult.Declined -> Unit
|
||||||
|
|
||||||
CreateAuthRequestResult.Expired -> {
|
CreateAuthRequestResult.Expired -> {
|
||||||
updateContent { content ->
|
|
||||||
content.copy(isResendNotificationLoading = false)
|
|
||||||
}
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialogState = LoginWithDeviceState.DialogState.Error(
|
dialogState = LoginWithDeviceState.DialogState.Error(
|
||||||
@ -279,8 +276,7 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendNewAuthRequest(isResend: Boolean) {
|
private fun sendNewAuthRequest() {
|
||||||
setIsResendNotificationLoading(isResend)
|
|
||||||
authJob.cancel()
|
authJob.cancel()
|
||||||
authJob = authRepository
|
authJob = authRepository
|
||||||
.createAuthRequestWithUpdates(
|
.createAuthRequestWithUpdates(
|
||||||
@ -291,22 +287,6 @@ class LoginWithDeviceViewModel @Inject constructor(
|
|||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setIsResendNotificationLoading(isResend: Boolean) {
|
|
||||||
updateContent { it.copy(isResendNotificationLoading = isResend) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun updateContent(
|
|
||||||
crossinline block: (
|
|
||||||
LoginWithDeviceState.ViewState.Content,
|
|
||||||
) -> LoginWithDeviceState.ViewState.Content?,
|
|
||||||
) {
|
|
||||||
val currentViewState = state.viewState
|
|
||||||
val updatedContent = (currentViewState as? LoginWithDeviceState.ViewState.Content)
|
|
||||||
?.let(block)
|
|
||||||
?: return
|
|
||||||
mutableStateFlow.update { it.copy(viewState = updatedContent) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,13 +329,10 @@ data class LoginWithDeviceState(
|
|||||||
* Content state for the [LoginWithDeviceScreen] showing the actual content or items.
|
* Content state for the [LoginWithDeviceScreen] showing the actual content or items.
|
||||||
*
|
*
|
||||||
* @property fingerprintPhrase The fingerprint phrase to present to the user.
|
* @property fingerprintPhrase The fingerprint phrase to present to the user.
|
||||||
* @property isResendNotificationLoading Indicates if the resend loading spinner should be
|
|
||||||
* displayed.
|
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Content(
|
data class Content(
|
||||||
val fingerprintPhrase: String,
|
val fingerprintPhrase: String,
|
||||||
val isResendNotificationLoading: Boolean,
|
|
||||||
private val loginWithDeviceType: LoginWithDeviceType,
|
private val loginWithDeviceType: LoginWithDeviceType,
|
||||||
) : ViewState() {
|
) : ViewState() {
|
||||||
/**
|
/**
|
||||||
@ -401,14 +378,19 @@ data class LoginWithDeviceState(
|
|||||||
/**
|
/**
|
||||||
* The text to display indicating that there are other option for logging in.
|
* The text to display indicating that there are other option for logging in.
|
||||||
*/
|
*/
|
||||||
@Suppress("MaxLineLength")
|
|
||||||
val otherOptions: Text
|
val otherOptions: Text
|
||||||
get() = when (loginWithDeviceType) {
|
get() = when (loginWithDeviceType) {
|
||||||
LoginWithDeviceType.OTHER_DEVICE,
|
LoginWithDeviceType.OTHER_DEVICE,
|
||||||
LoginWithDeviceType.SSO_OTHER_DEVICE,
|
LoginWithDeviceType.SSO_OTHER_DEVICE,
|
||||||
-> BitwardenString.log_in_with_device_must_be_set_up_in_the_settings_of_the_bitwarden_app_need_another_option.asText()
|
-> {
|
||||||
|
BitwardenString
|
||||||
|
.log_in_with_device_must_be_set_up_in_the_settings_of_the_bitwarden_app
|
||||||
|
.asText()
|
||||||
|
}
|
||||||
|
|
||||||
LoginWithDeviceType.SSO_ADMIN_APPROVAL -> BitwardenString.trouble_logging_in.asText()
|
LoginWithDeviceType.SSO_ADMIN_APPROVAL -> {
|
||||||
|
BitwardenString.trouble_logging_in.asText()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import androidx.compose.ui.test.isDialog
|
|||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performFirstLinkClick
|
||||||
import androidx.compose.ui.test.performScrollTo
|
import androidx.compose.ui.test.performScrollTo
|
||||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
import com.bitwarden.ui.platform.manager.IntentManager
|
import com.bitwarden.ui.platform.manager.IntentManager
|
||||||
@ -92,7 +93,10 @@ class LoginWithDeviceScreenTest : BitwardenComposeTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `view all log in options click should send ViewAllLogInOptionsClick action`() {
|
fun `view all log in options click should send ViewAllLogInOptionsClick action`() {
|
||||||
composeTestRule.onNodeWithText("View all log in options").performScrollTo().performClick()
|
composeTestRule
|
||||||
|
.onNodeWithText(text = "Need another option? View all login options")
|
||||||
|
.performScrollTo()
|
||||||
|
.performFirstLinkClick()
|
||||||
verify {
|
verify {
|
||||||
viewModel.trySendAction(LoginWithDeviceAction.ViewAllLogInOptionsClick)
|
viewModel.trySendAction(LoginWithDeviceAction.ViewAllLogInOptionsClick)
|
||||||
}
|
}
|
||||||
@ -168,7 +172,6 @@ private val DEFAULT_STATE = LoginWithDeviceState(
|
|||||||
emailAddress = EMAIL,
|
emailAddress = EMAIL,
|
||||||
viewState = LoginWithDeviceState.ViewState.Content(
|
viewState = LoginWithDeviceState.ViewState.Content(
|
||||||
fingerprintPhrase = "alabster-drinkable-mystified-rapping-irrigate",
|
fingerprintPhrase = "alabster-drinkable-mystified-rapping-irrigate",
|
||||||
isResendNotificationLoading = false,
|
|
||||||
loginWithDeviceType = LoginWithDeviceType.OTHER_DEVICE,
|
loginWithDeviceType = LoginWithDeviceType.OTHER_DEVICE,
|
||||||
),
|
),
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
|
|||||||
@ -121,8 +121,8 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||||||
viewModel.trySendAction(LoginWithDeviceAction.ResendNotificationClick)
|
viewModel.trySendAction(LoginWithDeviceAction.ResendNotificationClick)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
DEFAULT_STATE.copy(
|
DEFAULT_STATE.copy(
|
||||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
dialogState = LoginWithDeviceState.DialogState.Loading(
|
||||||
isResendNotificationLoading = true,
|
message = BitwardenString.resending.asText(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
viewModel.stateFlow.value,
|
viewModel.stateFlow.value,
|
||||||
@ -610,7 +610,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||||||
DEFAULT_STATE.copy(
|
DEFAULT_STATE.copy(
|
||||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
||||||
fingerprintPhrase = FINGERPRINT,
|
fingerprintPhrase = FINGERPRINT,
|
||||||
isResendNotificationLoading = false,
|
|
||||||
),
|
),
|
||||||
dialogState = LoginWithDeviceState.DialogState.Error(
|
dialogState = LoginWithDeviceState.DialogState.Error(
|
||||||
title = BitwardenString.an_error_has_occurred.asText(),
|
title = BitwardenString.an_error_has_occurred.asText(),
|
||||||
@ -661,7 +660,6 @@ class LoginWithDeviceViewModelTest : BaseViewModelTest() {
|
|||||||
DEFAULT_STATE.copy(
|
DEFAULT_STATE.copy(
|
||||||
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
viewState = DEFAULT_CONTENT_VIEW_STATE.copy(
|
||||||
fingerprintPhrase = FINGERPRINT,
|
fingerprintPhrase = FINGERPRINT,
|
||||||
isResendNotificationLoading = false,
|
|
||||||
),
|
),
|
||||||
dialogState = LoginWithDeviceState.DialogState.Error(
|
dialogState = LoginWithDeviceState.DialogState.Error(
|
||||||
title = null,
|
title = null,
|
||||||
@ -693,7 +691,6 @@ private const val FINGERPRINT = "fingerprint"
|
|||||||
|
|
||||||
private val DEFAULT_CONTENT_VIEW_STATE = LoginWithDeviceState.ViewState.Content(
|
private val DEFAULT_CONTENT_VIEW_STATE = LoginWithDeviceState.ViewState.Content(
|
||||||
fingerprintPhrase = FINGERPRINT,
|
fingerprintPhrase = FINGERPRINT,
|
||||||
isResendNotificationLoading = false,
|
|
||||||
loginWithDeviceType = LoginWithDeviceType.OTHER_DEVICE,
|
loginWithDeviceType = LoginWithDeviceType.OTHER_DEVICE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.focus.onFocusEvent
|
import androidx.compose.ui.focus.onFocusEvent
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalClipboard
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
@ -94,6 +95,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
* @param readOnly `true` if the input should be read-only and not accept user interactions.
|
* @param readOnly `true` if the input should be read-only and not accept user interactions.
|
||||||
* @param enabled Whether or not the text field is enabled.
|
* @param enabled Whether or not the text field is enabled.
|
||||||
* @param textStyle An optional style that may be used to override the default used.
|
* @param textStyle An optional style that may be used to override the default used.
|
||||||
|
* @param textColor An optional color that may be used to override the text color.
|
||||||
* @param shouldAddCustomLineBreaks If `true`, line breaks will be inserted to allow for filling
|
* @param shouldAddCustomLineBreaks If `true`, line breaks will be inserted to allow for filling
|
||||||
* an entire line before breaking. `false` by default.
|
* an entire line before breaking. `false` by default.
|
||||||
* @param visualTransformation Transforms the visual representation of the input [value].
|
* @param visualTransformation Transforms the visual representation of the input [value].
|
||||||
@ -123,6 +125,7 @@ fun BitwardenTextField(
|
|||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
textStyle: TextStyle = BitwardenTheme.typography.bodyLarge,
|
textStyle: TextStyle = BitwardenTheme.typography.bodyLarge,
|
||||||
|
textColor: Color = BitwardenTheme.colorScheme.text.primary,
|
||||||
shouldAddCustomLineBreaks: Boolean = false,
|
shouldAddCustomLineBreaks: Boolean = false,
|
||||||
keyboardType: KeyboardType = KeyboardType.Text,
|
keyboardType: KeyboardType = KeyboardType.Text,
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
@ -158,6 +161,7 @@ fun BitwardenTextField(
|
|||||||
readOnly = readOnly,
|
readOnly = readOnly,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
textStyle = textStyle,
|
textStyle = textStyle,
|
||||||
|
textColor = textColor,
|
||||||
shouldAddCustomLineBreaks = shouldAddCustomLineBreaks,
|
shouldAddCustomLineBreaks = shouldAddCustomLineBreaks,
|
||||||
keyboardType = keyboardType,
|
keyboardType = keyboardType,
|
||||||
keyboardActions = keyboardActions,
|
keyboardActions = keyboardActions,
|
||||||
@ -194,6 +198,7 @@ fun BitwardenTextField(
|
|||||||
* @param readOnly `true` if the input should be read-only and not accept user interactions.
|
* @param readOnly `true` if the input should be read-only and not accept user interactions.
|
||||||
* @param enabled Whether or not the text field is enabled.
|
* @param enabled Whether or not the text field is enabled.
|
||||||
* @param textStyle An optional style that may be used to override the default used.
|
* @param textStyle An optional style that may be used to override the default used.
|
||||||
|
* @param textColor An optional color that may be used to override the text color.
|
||||||
* @param shouldAddCustomLineBreaks If `true`, line breaks will be inserted to allow for filling
|
* @param shouldAddCustomLineBreaks If `true`, line breaks will be inserted to allow for filling
|
||||||
* an entire line before breaking. `false` by default.
|
* an entire line before breaking. `false` by default.
|
||||||
* @param visualTransformation Transforms the visual representation of the input [value].
|
* @param visualTransformation Transforms the visual representation of the input [value].
|
||||||
@ -226,6 +231,7 @@ fun BitwardenTextField(
|
|||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
textStyle: TextStyle = BitwardenTheme.typography.bodyLarge,
|
textStyle: TextStyle = BitwardenTheme.typography.bodyLarge,
|
||||||
|
textColor: Color = BitwardenTheme.colorScheme.text.primary,
|
||||||
shouldAddCustomLineBreaks: Boolean = false,
|
shouldAddCustomLineBreaks: Boolean = false,
|
||||||
keyboardType: KeyboardType = KeyboardType.Text,
|
keyboardType: KeyboardType = KeyboardType.Text,
|
||||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
@ -312,7 +318,7 @@ fun BitwardenTextField(
|
|||||||
var focused by remember { mutableStateOf(false) }
|
var focused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
colors = bitwardenTextFieldColors(),
|
colors = bitwardenTextFieldColors(textColor = textColor),
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
label = label?.let {
|
label = label?.let {
|
||||||
{
|
{
|
||||||
|
|||||||
@ -24,6 +24,7 @@ fun bitwardenTextFieldButtonColors(): TextFieldColors = bitwardenTextFieldColors
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun bitwardenTextFieldColors(
|
fun bitwardenTextFieldColors(
|
||||||
|
textColor: Color = BitwardenTheme.colorScheme.text.primary,
|
||||||
disabledTextColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
disabledTextColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
||||||
disabledLeadingIconColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
disabledLeadingIconColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
||||||
disabledTrailingIconColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
disabledTrailingIconColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
||||||
@ -31,8 +32,8 @@ fun bitwardenTextFieldColors(
|
|||||||
disabledPlaceholderColor: Color = BitwardenTheme.colorScheme.text.secondary,
|
disabledPlaceholderColor: Color = BitwardenTheme.colorScheme.text.secondary,
|
||||||
disabledSupportingTextColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
disabledSupportingTextColor: Color = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
|
||||||
): TextFieldColors = TextFieldColors(
|
): TextFieldColors = TextFieldColors(
|
||||||
focusedTextColor = BitwardenTheme.colorScheme.text.primary,
|
focusedTextColor = textColor,
|
||||||
unfocusedTextColor = BitwardenTheme.colorScheme.text.primary,
|
unfocusedTextColor = textColor,
|
||||||
disabledTextColor = disabledTextColor,
|
disabledTextColor = disabledTextColor,
|
||||||
errorTextColor = BitwardenTheme.colorScheme.text.primary,
|
errorTextColor = BitwardenTheme.colorScheme.text.primary,
|
||||||
focusedContainerColor = Color.Transparent,
|
focusedContainerColor = Color.Transparent,
|
||||||
|
|||||||
@ -653,7 +653,8 @@ Do you want to switch to this account?</string>
|
|||||||
<string name="invalid_uri">Invalid URI</string>
|
<string name="invalid_uri">Invalid URI</string>
|
||||||
<string name="the_urix_is_already_blocked">The URI %1$s is already blocked</string>
|
<string name="the_urix_is_already_blocked">The URI %1$s is already blocked</string>
|
||||||
<string name="login_approved">Login approved</string>
|
<string name="login_approved">Login approved</string>
|
||||||
<string name="log_in_with_device_must_be_set_up_in_the_settings_of_the_bitwarden_app_need_another_option">Log in with device must be set up in the settings of the Bitwarden app. Need another option?</string>
|
<string name="log_in_with_device_must_be_set_up_in_the_settings_of_the_bitwarden_app">Log in with device must be set up in the settings of the Bitwarden app.</string>
|
||||||
|
<string name="need_another_option_view_all_login_options">Need another option? <annotation link="viewAll">View all login options</annotation></string>
|
||||||
<string name="log_in_with_device">Log in with device</string>
|
<string name="log_in_with_device">Log in with device</string>
|
||||||
<string name="logging_in_on">Logging in on</string>
|
<string name="logging_in_on">Logging in on</string>
|
||||||
<string name="logging_in_on_with_colon">Logging in on:</string>
|
<string name="logging_in_on_with_colon">Logging in on:</string>
|
||||||
@ -1156,4 +1157,5 @@ Do you want to switch to this account?</string>
|
|||||||
<string name="lock_app">Lock app</string>
|
<string name="lock_app">Lock app</string>
|
||||||
<string name="use_your_devices_lock_method_to_unlock_the_app">Use your device’s lock method to unlock the app</string>
|
<string name="use_your_devices_lock_method_to_unlock_the_app">Use your device’s lock method to unlock the app</string>
|
||||||
<string name="loading_vault_data">Loading vault data…</string>
|
<string name="loading_vault_data">Loading vault data…</string>
|
||||||
|
<string name="resending">Resending</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user