mirror of
https://github.com/bitwarden/android.git
synced 2026-03-12 05:04:17 -05:00
Fix flicker on TextField autocomplete (#5456)
This commit is contained in:
@@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.platform.components.field
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -15,10 +14,13 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.MenuAnchorType
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -50,9 +52,9 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import com.bitwarden.ui.platform.base.util.cardStyle
|
||||
import com.bitwarden.ui.platform.base.util.nullableTestTag
|
||||
import com.bitwarden.ui.platform.base.util.simpleVerticalScrollbar
|
||||
import com.bitwarden.ui.platform.base.util.toPx
|
||||
import com.bitwarden.ui.platform.base.util.withLineBreaksAtWidth
|
||||
import com.bitwarden.ui.platform.components.appbar.color.bitwardenMenuItemColors
|
||||
@@ -206,6 +208,7 @@ fun BitwardenTextField(
|
||||
* defining the layout of the actions.
|
||||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BitwardenTextField(
|
||||
label: String?,
|
||||
@@ -269,7 +272,15 @@ fun BitwardenTextField(
|
||||
var lastTextValue by remember(value) { mutableStateOf(value = value) }
|
||||
CompositionLocalProvider(value = LocalTextToolbar provides textToolbar) {
|
||||
var hasFocused by remember { mutableStateOf(value = false) }
|
||||
Box(modifier = modifier.defaultMinSize(minHeight = 60.dp)) {
|
||||
val filteredAutoCompleteList = autoCompleteOptions
|
||||
.filter { it.startsWith(textFieldValue.text) && it != textFieldValue.text }
|
||||
.toImmutableList()
|
||||
val isDropDownExpanded = filteredAutoCompleteList.isNotEmpty() && hasFocused
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = isDropDownExpanded,
|
||||
onExpandedChange = { hasFocused = false },
|
||||
modifier = modifier.defaultMinSize(minHeight = 60.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.onGloballyPositioned { widthPx = it.size.width }
|
||||
@@ -364,6 +375,7 @@ fun BitwardenTextField(
|
||||
visualTransformation = visualTransformation,
|
||||
modifier = Modifier
|
||||
.nullableTestTag(tag = textFieldTestTag)
|
||||
.menuAnchor(type = MenuAnchorType.PrimaryEditable)
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged { focusState ->
|
||||
focused = focusState.isFocused
|
||||
@@ -387,17 +399,14 @@ fun BitwardenTextField(
|
||||
}
|
||||
?: Spacer(modifier = Modifier.height(height = cardStyle?.let { 6.dp } ?: 0.dp))
|
||||
}
|
||||
val filteredAutoCompleteList = autoCompleteOptions
|
||||
.filter { option ->
|
||||
option.startsWith(textFieldValue.text) && option != textFieldValue.text
|
||||
}
|
||||
.toImmutableList()
|
||||
DropdownMenu(
|
||||
expanded = filteredAutoCompleteList.isNotEmpty() && hasFocused,
|
||||
val scrollState = rememberScrollState()
|
||||
ExposedDropdownMenu(
|
||||
expanded = isDropDownExpanded,
|
||||
shape = BitwardenTheme.shapes.menu,
|
||||
containerColor = BitwardenTheme.colorScheme.background.primary,
|
||||
properties = PopupProperties(),
|
||||
onDismissRequest = { hasFocused = false },
|
||||
scrollState = scrollState,
|
||||
modifier = Modifier.simpleVerticalScrollbar(state = scrollState),
|
||||
) {
|
||||
filteredAutoCompleteList.forEach {
|
||||
DropdownMenuItem(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package com.bitwarden.ui.platform.base.util
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -21,9 +22,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.input.key.Key
|
||||
@@ -78,6 +82,42 @@ fun Modifier.scrolledContainerBackground(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a very simple non-intractable scrollbar on the end side of the component.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@Composable
|
||||
fun Modifier.simpleVerticalScrollbar(
|
||||
state: ScrollState,
|
||||
scrollbarWidth: Dp = 6.dp,
|
||||
color: Color = BitwardenTheme.colorScheme.stroke.divider,
|
||||
layoutDirection: LayoutDirection = LocalLayoutDirection.current,
|
||||
): Modifier =
|
||||
this then Modifier.drawWithContent {
|
||||
drawContent()
|
||||
val viewHeight = state.viewportSize.toFloat()
|
||||
val contentHeight = state.maxValue + viewHeight
|
||||
val scrollbarHeight = (10.dp.toPx()..viewHeight)
|
||||
.takeUnless { it.isEmpty() }
|
||||
?.let { (viewHeight * (viewHeight / contentHeight)).coerceIn(range = it) }
|
||||
?: 0f
|
||||
val variableZone = viewHeight - scrollbarHeight
|
||||
val scrollbarYOffset = (state.value.toFloat() / state.maxValue) * variableZone
|
||||
val halfScrollbarWidthPx = scrollbarWidth.toPx() / 2
|
||||
drawRoundRect(
|
||||
cornerRadius = CornerRadius(x = halfScrollbarWidthPx, y = halfScrollbarWidthPx),
|
||||
color = color,
|
||||
topLeft = Offset(
|
||||
x = when (layoutDirection) {
|
||||
LayoutDirection.Ltr -> this.size.width - scrollbarWidth.toPx()
|
||||
LayoutDirection.Rtl -> 0f
|
||||
},
|
||||
y = scrollbarYOffset,
|
||||
),
|
||||
size = Size(width = scrollbarWidth.toPx(), height = scrollbarHeight),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a bottom divider specified by the given [topAppBarScrollBehavior] and its current scroll
|
||||
* state.
|
||||
|
||||
Reference in New Issue
Block a user