BIT-957: Add color change animation for toolbars. (#163)

This commit is contained in:
David Perez 2023-10-25 17:05:50 -05:00 committed by Álison Fernandes
parent d81c146f33
commit 852176045b
5 changed files with 299 additions and 232 deletions

View file

@ -17,9 +17,13 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -29,6 +33,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
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
@ -72,6 +77,7 @@ import com.x8bit.bitwarden.ui.platform.theme.clickableSpanStyle
/**
* Top level composable for the create account screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongMethod")
@Composable
fun CreateAccountScreen(
@ -152,110 +158,121 @@ fun CreateAccountScreen(
null -> Unit
}
Column(
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
BitwardenTopAppBar(
title = stringResource(id = R.string.create_account),
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(CloseClick) }
},
actions = {
BitwardenTextButton(
label = stringResource(id = R.string.submit),
onClick = remember(viewModel) {
{ viewModel.trySendAction(SubmitClick) }
},
)
},
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenTextField(
label = stringResource(id = R.string.email_address),
value = state.emailInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(EmailInputChange(it)) }
},
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.create_account),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(CloseClick) }
},
actions = {
BitwardenTextButton(
label = stringResource(id = R.string.submit),
onClick = remember(viewModel) {
{ viewModel.trySendAction(SubmitClick) }
},
)
},
)
},
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
var showPassword by rememberSaveable { mutableStateOf(false) }
BitwardenPasswordField(
label = stringResource(id = R.string.master_password),
showPassword = showPassword,
showPasswordChange = { showPassword = it },
value = state.passwordInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(PasswordInputChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenPasswordField(
label = stringResource(id = R.string.retype_master_password),
value = state.confirmPasswordInput,
showPassword = showPassword,
showPasswordChange = { showPassword = it },
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(ConfirmPasswordInputChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenTextField(
label = stringResource(id = R.string.master_password_hint),
value = state.passwordHintInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(PasswordHintChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.master_password_hint_description),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier
.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(24.dp))
BitwardenSwitch(
label = stringResource(id = R.string.check_known_data_breaches_for_this_password),
isChecked = state.isCheckDataBreachesToggled,
onCheckedChange = remember(viewModel) {
{ newState ->
viewModel.trySendAction(CheckDataBreachesToggle(newState = newState))
}
},
modifier = Modifier
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(8.dp))
TermsAndPrivacySwitch(
isChecked = state.isAcceptPoliciesToggled,
onCheckedChange = remember(viewModel) {
{ viewModel.trySendAction(AcceptPoliciesToggle(it)) }
},
onTermsClick = remember(viewModel) {
{ viewModel.trySendAction(TermsClick) }
},
onPrivacyPolicyClick = remember(viewModel) {
{ viewModel.trySendAction(PrivacyPolicyClick) }
},
)
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
Spacer(modifier = Modifier.height(16.dp))
BitwardenTextField(
label = stringResource(id = R.string.email_address),
value = state.emailInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(EmailInputChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
var showPassword by rememberSaveable { mutableStateOf(false) }
BitwardenPasswordField(
label = stringResource(id = R.string.master_password),
showPassword = showPassword,
showPasswordChange = { showPassword = it },
value = state.passwordInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(PasswordInputChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenPasswordField(
label = stringResource(id = R.string.retype_master_password),
value = state.confirmPasswordInput,
showPassword = showPassword,
showPasswordChange = { showPassword = it },
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(ConfirmPasswordInputChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenTextField(
label = stringResource(id = R.string.master_password_hint),
value = state.passwordHintInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(PasswordHintChange(it)) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.master_password_hint_description),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier
.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(24.dp))
BitwardenSwitch(
label = stringResource(id = R.string.check_known_data_breaches_for_this_password),
isChecked = state.isCheckDataBreachesToggled,
onCheckedChange = remember(viewModel) {
{ newState ->
viewModel.trySendAction(CheckDataBreachesToggle(newState = newState))
}
},
modifier = Modifier
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(8.dp))
TermsAndPrivacySwitch(
isChecked = state.isAcceptPoliciesToggled,
onCheckedChange = remember(viewModel) {
{ viewModel.trySendAction(AcceptPoliciesToggle(it)) }
},
onTermsClick = remember(viewModel) {
{ viewModel.trySendAction(TermsClick) }
},
onPrivacyPolicyClick = remember(viewModel) {
{ viewModel.trySendAction(PrivacyPolicyClick) }
},
)
}
}
}

View file

@ -10,13 +10,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
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.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
@ -38,6 +43,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
/**
* The top level composable for the Login screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Suppress("LongMethod")
fun LoginScreen(
@ -60,122 +66,131 @@ fun LoginScreen(
}
}
val scrollState = rememberScrollState()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
.verticalScroll(scrollState),
) {
BitwardenLoadingDialog(
visibilityState = state.loadingDialogState,
)
BitwardenBasicDialog(
visibilityState = state.errorDialogState,
onDismissRequest = { viewModel.trySendAction(LoginAction.ErrorDialogDismiss) },
)
BitwardenTopAppBar(
title = stringResource(id = R.string.app_name),
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.CloseButtonClick) }
},
actions = {
BitwardenOverflowActionItem(
dropdownMenuItemContent = {
DropdownMenuItem(
text = {
Text(text = stringResource(id = R.string.get_password_hint))
},
onClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.MasterPasswordHintClick) }
},
)
},
)
},
)
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.app_name),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.CloseButtonClick) }
},
actions = {
BitwardenOverflowActionItem(
dropdownMenuItemContent = {
DropdownMenuItem(
text = {
Text(text = stringResource(id = R.string.get_password_hint))
},
onClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.MasterPasswordHintClick) }
},
)
},
)
},
)
},
) { innerPadding ->
Column(
modifier = Modifier.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
BitwardenPasswordField(
modifier = Modifier
.fillMaxWidth(),
value = state.passwordInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.PasswordInputChanged(it)) }
},
label = stringResource(id = R.string.master_password),
BitwardenLoadingDialog(
visibilityState = state.loadingDialogState,
)
BitwardenBasicDialog(
visibilityState = state.errorDialogState,
onDismissRequest = { viewModel.trySendAction(LoginAction.ErrorDialogDismiss) },
)
// TODO: Need to figure out better handling for very small clickable text (BIT-724)
Text(
text = stringResource(id = R.string.get_password_hint),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.clickable {
viewModel.trySendAction(LoginAction.MasterPasswordHintClick)
}
.padding(
vertical = 4.dp,
horizontal = 16.dp,
Column(
modifier = Modifier.padding(horizontal = 16.dp),
) {
BitwardenPasswordField(
modifier = Modifier
.fillMaxWidth(),
value = state.passwordInput,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.PasswordInputChanged(it)) }
},
label = stringResource(id = R.string.master_password),
)
// TODO: Need to figure out better handling for very small clickable text (BIT-724)
Text(
text = stringResource(id = R.string.get_password_hint),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.clickable {
viewModel.trySendAction(LoginAction.MasterPasswordHintClick)
}
.padding(
vertical = 4.dp,
horizontal = 16.dp,
),
)
BitwardenFilledButton(
label = stringResource(id = R.string.log_in_with_master_password),
onClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.LoginButtonClick) }
},
isEnabled = state.isLoginButtonEnabled,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
)
BitwardenOutlinedButtonWithIcon(
label = stringResource(id = R.string.log_in_sso),
icon = painterResource(id = R.drawable.ic_light_bulb),
onClick =
remember(viewModel) {
{ viewModel.trySendAction(LoginAction.SingleSignOnClick) }
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
isEnabled = state.isLoginButtonEnabled,
)
Text(
text = stringResource(
id = R.string.logging_in_as_x_on_y,
state.emailAddress,
state.environmentLabel(),
),
)
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
)
BitwardenFilledButton(
label = stringResource(id = R.string.log_in_with_master_password),
onClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.LoginButtonClick) }
},
isEnabled = state.isLoginButtonEnabled,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
)
BitwardenOutlinedButtonWithIcon(
label = stringResource(id = R.string.log_in_sso),
icon = painterResource(id = R.drawable.ic_light_bulb),
onClick =
remember(viewModel) {
{ viewModel.trySendAction(LoginAction.SingleSignOnClick) }
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp),
isEnabled = state.isLoginButtonEnabled,
)
Text(
text = stringResource(
id = R.string.logging_in_as_x_on_y,
state.emailAddress,
state.environmentLabel(),
),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
)
// TODO: Need to figure out better handling for very small clickable text (BIT-724)
Text(
modifier = Modifier
.clickable { viewModel.trySendAction(LoginAction.NotYouButtonClick) },
text = stringResource(id = R.string.not_you),
textAlign = TextAlign.Start,
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelLarge,
)
// TODO: Need to figure out better handling for very small clickable text (BIT-724)
Text(
modifier = Modifier
.clickable { viewModel.trySendAction(LoginAction.NotYouButtonClick) },
text = stringResource(id = R.string.not_you),
textAlign = TextAlign.Start,
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelLarge,
)
}
}
}
}

View file

@ -39,7 +39,8 @@ fun BitwardenMediumTopAppBar(
) {
MediumTopAppBar(
colors = TopAppBarDefaults.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.surface,
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
),
scrollBehavior = scrollBehavior,
title = {

View file

@ -7,6 +7,9 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
@ -29,12 +32,18 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@Composable
fun BitwardenTopAppBar(
title: String,
scrollBehavior: TopAppBarScrollBehavior,
navigationIcon: Painter,
navigationIconContentDescription: String,
onNavigationIconClick: () -> Unit,
actions: @Composable RowScope.() -> Unit = {},
) {
TopAppBar(
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
),
scrollBehavior = scrollBehavior,
navigationIcon = {
IconButton(
onClick = { onNavigationIconClick() },
@ -57,12 +66,17 @@ fun BitwardenTopAppBar(
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
private fun BitwardenTopAppBar_preview() {
BitwardenTheme {
BitwardenTopAppBar(
title = "Title",
scrollBehavior = TopAppBarDefaults
.exitUntilCollapsedScrollBehavior(
rememberTopAppBarState(),
),
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = {},

View file

@ -9,12 +9,19 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
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.compose.ui.unit.dp
@ -29,6 +36,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
/**
* Displays the account security screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AccountSecurityScreen(
onNavigateBack: () -> Unit,
@ -39,29 +47,41 @@ fun AccountSecurityScreen(
AccountSecurityEvent.NavigateBack -> onNavigateBack.invoke()
}
}
Column(
Modifier
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface),
) {
BitwardenTopAppBar(
title = stringResource(id = R.string.account),
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.BackClick) }
},
actions = {
BitwardenOverflowActionItem()
},
)
Spacer(Modifier.height(8.dp))
AccountSecurityRow(
text = R.string.log_out.asText(),
onClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.LogoutClick) }
},
)
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.account),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.BackClick) }
},
actions = {
BitwardenOverflowActionItem()
},
)
},
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
Spacer(Modifier.height(8.dp))
AccountSecurityRow(
text = R.string.log_out.asText(),
onClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.LogoutClick) }
},
)
}
}
}