improvement: Align Expiry date's field styling with the common text fields

This commit is contained in:
Artem Chepurnoy 2024-03-30 12:09:14 +02:00
parent 7c9d9c1022
commit 2e68f469a7
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
2 changed files with 258 additions and 123 deletions

View File

@ -32,7 +32,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.CloudDone
import androidx.compose.material.icons.outlined.FileUpload
import androidx.compose.material.icons.outlined.Folder
@ -41,7 +40,6 @@ import androidx.compose.material.icons.outlined.Password
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
@ -64,9 +62,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.SpanStyle
@ -77,15 +73,11 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.model.UsernameVariationIcon
import com.artemchep.keyguard.common.model.fold
import com.artemchep.keyguard.common.model.titleH
import com.artemchep.keyguard.common.service.logging.LogRepository
import com.artemchep.keyguard.feature.auth.common.TextFieldModel2
import com.artemchep.keyguard.feature.auth.common.VisibilityState
import com.artemchep.keyguard.feature.auth.common.VisibilityToggle
import com.artemchep.keyguard.feature.home.vault.add.AddState
import com.artemchep.keyguard.feature.home.vault.component.FlatItemTextContent2
import com.artemchep.keyguard.feature.home.vault.component.Section
import com.artemchep.keyguard.feature.home.vault.component.VaultViewTotpBadge2
@ -107,12 +99,12 @@ import com.artemchep.keyguard.ui.DropdownScopeImpl
import com.artemchep.keyguard.ui.EmailFlatTextField
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
import com.artemchep.keyguard.ui.ExpandedIfNotEmptyForRow
import com.artemchep.keyguard.ui.FakeFlatTextField
import com.artemchep.keyguard.ui.FlatDropdown
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.FlatItemLayout
import com.artemchep.keyguard.ui.FlatItemTextContent
import com.artemchep.keyguard.ui.FlatSimpleNote
import com.artemchep.keyguard.ui.FlatTextField
import com.artemchep.keyguard.ui.FlatTextFieldBadge
import com.artemchep.keyguard.ui.LeMOdelBottomSheet
@ -142,7 +134,6 @@ import com.artemchep.keyguard.ui.theme.isDark
import com.artemchep.keyguard.ui.theme.monoFontFamily
import com.artemchep.keyguard.ui.util.DividerColor
import com.artemchep.keyguard.ui.util.HorizontalDivider
import com.artemchep.keyguard.ui.util.VerticalDivider
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.collections.immutable.ImmutableList
@ -1086,104 +1077,44 @@ private fun DateMonthYearField(
item: AddStateItem.DateMonthYear<*>,
) {
val state by item.state.flow.collectAsState()
BiFlatContainer(
val isEmpty by derivedStateOf {
val isEmpty = state.month.state.value.isEmpty() &&
state.year.state.value.isEmpty()
isEmpty
}
val onClear = remember {
// lambda
{
state.month.state.value = ""
state.year.state.value = ""
}
}
FakeFlatTextField(
modifier = modifier
.padding(horizontal = Dimens.horizontalPadding),
contentModifier = Modifier
.clickable(
indication = LocalIndication.current,
interactionSource = remember {
MutableInteractionSource()
},
role = Role.Button,
) {
state.onClick.invoke()
},
isError = rememberUpdatedState(newValue = false),
isFocused = rememberUpdatedState(newValue = false),
isEmpty = rememberUpdatedState(newValue = false),
label = {
Text(
text = item.label,
style = MaterialTheme.typography.bodySmall,
)
},
content = {
val density = LocalDensity.current
label = item.label,
value = {
Row(
modifier = Modifier
.graphicsLayer {
translationY = 12f + density.density
},
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
Row(
modifier = Modifier
.heightIn(min = BiFlatValueHeightMin)
.weight(1f, fill = false),
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
val month = state.month.text.ifBlank { "--" }
val year = state.year.text.ifBlank { "----" }
Text(
text = month,
)
Text(
text = "/",
color = LocalContentColor.current
.combineAlpha(DisabledEmphasisAlpha),
)
Text(
text = year,
)
}
// Spacer(
// modifier = Modifier
// .width(4.dp),
// )
// Icon(
// modifier = Modifier
// .alpha(MediumEmphasisAlpha),
// imageVector = Icons.Outlined.ArrowDropDown,
// contentDescription = null,
// )
}
},
trailing = {
val isEmpty = state.month.state.value.isEmpty() &&
state.year.state.value.isEmpty()
ExpandedIfNotEmptyForRow(
valueOrNull = Unit.takeUnless { isEmpty },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(
modifier = Modifier
.width(8.dp),
)
VerticalDivider(
modifier = Modifier
.height(24.dp),
)
Spacer(
modifier = Modifier
.width(8.dp),
)
IconButton(
enabled = true,
onClick = {
state.month.state.value = ""
state.year.state.value = ""
},
) {
Icon(
imageVector = Icons.Outlined.Clear,
contentDescription = null,
)
}
}
val month = state.month.text.ifBlank { "--" }
val year = state.year.text.ifBlank { "----" }
Text(
text = month,
)
Text(
text = "/",
color = LocalContentColor.current
.combineAlpha(DisabledEmphasisAlpha),
)
Text(
text = year,
)
}
},
onClick = state.onClick,
onClear = onClear.takeIf { !isEmpty },
)
}

View File

@ -9,6 +9,9 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
@ -72,6 +75,7 @@ import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
@ -296,6 +300,189 @@ fun ConcealedFlatTextField(
)
}
@Composable
fun FakeFlatTextField(
modifier: Modifier = Modifier,
label: String? = null,
error: String? = null,
value: @Composable () -> Unit,
onClick: (() -> Unit)?,
onClear: (() -> Unit)?,
textStyle: TextStyle = LocalTextStyle.current,
leading: (@Composable RowScope.() -> Unit)? = null,
trailing: (@Composable RowScope.() -> Unit)? = null,
content: (@Composable ColumnScope.() -> Unit)? = null,
) {
val isError = error != null
var hasFocus by remember {
mutableStateOf(false)
}
val updatedOnClick by rememberUpdatedState(onClick)
FlatTextFieldSurface(
modifier = modifier
.onFocusChanged { state ->
hasFocus = state.hasFocus
},
isError = isError,
isFocused = hasFocus,
) {
Column(
modifier = Modifier
.clickable(
indication = LocalIndication.current,
enabled = onClick != null,
interactionSource = remember {
MutableInteractionSource()
},
role = Role.Button,
) {
updatedOnClick?.invoke()
},
) {
Row(
modifier = Modifier
.padding(
start = 16.dp,
end = 8.dp,
top = 8.dp,
bottom = 8.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
val disabledAlphaTarget = if (onClick != null) 1f else DisabledEmphasisAlpha
val disabledAlphaState = animateFloatAsState(disabledAlphaTarget)
if (leading != null) {
Row(
modifier = Modifier
.graphicsLayer {
alpha = disabledAlphaState.value
},
verticalAlignment = Alignment.CenterVertically,
) {
leading()
}
Spacer(
modifier = Modifier
.width(16.dp),
)
}
Column(
modifier = Modifier
.weight(1f),
) {
val expanded = onClear != null
TextFieldLabelLayout(
modifier = Modifier
.heightIn(min = 50.dp),
expanded = onClear != null,
) {
if (label != null) {
TextFieldLabel(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
.graphicsLayer {
alpha = disabledAlphaState.value
},
text = label,
expanded = expanded,
hasFocus = hasFocus,
hasError = isError,
)
}
Box {
androidx.compose.animation.AnimatedVisibility(
modifier = Modifier
.fillMaxWidth(),
enter = fadeIn(),
exit = fadeOut(),
visible = expanded,
) {
// If color is not provided via the text style, use content color as a default
val textColor = run {
val color =
contentColorFor(backgroundColor = MaterialTheme.colorScheme.background)
val alpha =
if (onClick != null) DefaultEmphasisAlpha else DisabledEmphasisAlpha
color.copy(alpha = alpha)
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
ProvideTextStyle(mergedTextStyle) {
value()
}
}
}
}
Column(
modifier = Modifier
.padding(
end = 8.dp,
),
) {
ExpandedIfNotEmpty(
valueOrNull = error,
) { text ->
FlatTextFieldBadgeLegacy(
error = text,
badge = null,
)
}
if (content != null) {
content()
}
}
}
if (trailing != null) {
Spacer(
modifier = Modifier
.width(8.dp),
)
Row(
modifier = Modifier
.graphicsLayer {
alpha = disabledAlphaState.value
},
verticalAlignment = Alignment.CenterVertically,
) {
trailing()
}
}
ExpandedIfNotEmptyForRow(
valueOrNull = onClear,
) { lambda ->
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(
modifier = Modifier
.width(8.dp),
)
VerticalDivider(
modifier = Modifier
.height(24.dp),
)
Spacer(
modifier = Modifier
.width(8.dp),
)
IconButton(
onClick = lambda,
) {
Icon(
imageVector = Icons.Outlined.Clear,
contentDescription = null,
)
}
}
}
}
}
}
}
@Composable
fun FlatTextField(
modifier: Modifier = Modifier,
@ -376,24 +563,7 @@ fun FlatTextField(
expanded = focused,
) {
if (label != null) {
val focusedTextSize = MaterialTheme.typography.bodySmall.fontSize.value
val normalTextSize = LocalTextStyle.current.fontSize.value
val textSize by animateFloatAsState(
targetValue = if (focused) focusedTextSize else normalTextSize,
)
val focusedTextColor = when {
isError -> MaterialTheme.colorScheme.error
hasFocus -> MaterialTheme.colorScheme.primary
else -> LocalContentColor.current
}
val normalTextColor = LocalContentColor.current
.combineAlpha(HighEmphasisAlpha)
val textColor by animateColorAsState(
targetValue = if (focused) focusedTextColor else normalTextColor,
)
Text(
TextFieldLabel(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
@ -401,9 +571,9 @@ fun FlatTextField(
alpha = disabledAlphaState.value
},
text = label,
fontSize = textSize.sp,
maxLines = 1,
color = textColor,
expanded = focused,
hasFocus = hasFocus,
hasError = isError,
)
}
@ -627,6 +797,40 @@ private data class Afh(
val createdAt: Instant,
)
@Composable
private fun TextFieldLabel(
modifier: Modifier = Modifier,
text: String,
expanded: Boolean,
hasFocus: Boolean,
hasError: Boolean,
) {
val expandedTextSize = MaterialTheme.typography.bodySmall.fontSize.value
val normalTextSize = LocalTextStyle.current.fontSize.value
val textSize by animateFloatAsState(
targetValue = if (expanded) expandedTextSize else normalTextSize,
)
val expandedTextColor = when {
hasError -> MaterialTheme.colorScheme.error
hasFocus -> MaterialTheme.colorScheme.primary
else -> LocalContentColor.current
}
val normalTextColor = LocalContentColor.current
.combineAlpha(HighEmphasisAlpha)
val textColor by animateColorAsState(
targetValue = if (expanded) expandedTextColor else normalTextColor,
)
Text(
modifier = modifier,
text = text,
fontSize = textSize.sp,
maxLines = 1,
color = textColor,
)
}
@Composable
private fun TextFieldLabelLayout(
modifier: Modifier = Modifier,