Revert^3 "Update Compose snapshot and migrate to BTF2 (#1474)"
This reverts commit 22b5a175f4.
This commit is contained in:
1 parent
75d8ff4b64
commit
8a29d46b38
8 files changed
+165
-253
No files matched your search
@@ -0,0 +1,103 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package androidx.compose.material3
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.material3.SearchBarDefaults.InputFieldHeight
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun SearchBarInputField(
|
||||
state: TextFieldState,
|
||||
onSearch: () -> Unit,
|
||||
expanded: Boolean,
|
||||
onExpandedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
colors: TextFieldColors = SearchBarDefaults.inputFieldColors(),
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
val textColor = LocalTextStyle.current.color.takeOrElse {
|
||||
if (focused) colors.focusedTextColor else colors.unfocusedTextColor
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
state = state,
|
||||
modifier = modifier.sizeIn(
|
||||
minWidth = SearchBarMinWidth,
|
||||
maxWidth = SearchBarMaxWidth,
|
||||
minHeight = InputFieldHeight,
|
||||
).focusRequester(focusRequester).onFocusChanged {
|
||||
if (it.isFocused) onExpandedChange(true)
|
||||
}.fillMaxWidth(),
|
||||
enabled = enabled,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
textStyle = LocalTextStyle.current.merge(TextStyle(color = textColor)),
|
||||
cursorBrush = SolidColor(colors.cursorColor),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
onKeyboardAction = { onSearch() },
|
||||
interactionSource = interactionSource,
|
||||
decorator = @Composable { innerTextField ->
|
||||
TextFieldDefaults.DecorationBox(
|
||||
value = state.text.toString(),
|
||||
innerTextField = innerTextField,
|
||||
enabled = enabled,
|
||||
singleLine = true,
|
||||
visualTransformation = VisualTransformation.None,
|
||||
interactionSource = interactionSource,
|
||||
placeholder = placeholder,
|
||||
leadingIcon = leadingIcon?.let { leading -> { Box(Modifier.offset(x = SearchBarIconOffsetX)) { leading() } } },
|
||||
trailingIcon = trailingIcon?.let { trailing -> { Box(Modifier.offset(x = -SearchBarIconOffsetX)) { trailing() } } },
|
||||
shape = SearchBarDefaults.inputFieldShape,
|
||||
colors = colors,
|
||||
container = {},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val shouldClearFocus = !expanded && focused
|
||||
LaunchedEffect(expanded) {
|
||||
if (shouldClearFocus) {
|
||||
// Not strictly needed according to the motion spec, but since the animation
|
||||
// already has a delay, this works around b/261632544.
|
||||
delay(AnimationDelayMillis.toLong())
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val SearchBarIconOffsetX = 4.dp
|
||||
val SearchBarMinWidth = 360.dp
|
||||
val SearchBarMaxWidth = 720.dp
|
||||
const val AnimationDelayMillis = 100
|
||||
@@ -1,191 +0,0 @@
|
||||
package androidx.compose.material3
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.interaction.Interaction
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.InputTransformation
|
||||
import androidx.compose.foundation.text.input.OutputTransformation
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.material3.SearchBarDefaults.InputFieldHeight
|
||||
import androidx.compose.material3.SearchBarDefaults.inputFieldColors
|
||||
import androidx.compose.material3.SearchBarDefaults.inputFieldShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.ColorProducer
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.drawOutline
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* A text field to input a query in a search bar.
|
||||
*
|
||||
* @param state [TextFieldState] that holds the internal editing state of the input field.
|
||||
* @param onSearch the callback to be invoked when the input service triggers the
|
||||
* [ImeAction.Search] action. The current query in the [state] comes as a parameter of the
|
||||
* callback.
|
||||
* @param expanded whether the search bar is expanded and showing search results.
|
||||
* @param onExpandedChange the callback to be invoked when the search bar's expanded state is
|
||||
* changed.
|
||||
* @param modifier the [Modifier] to be applied to this input field.
|
||||
* @param enabled the enabled state of this input field. When `false`, this component will not
|
||||
* respond to user input, and it will appear visually disabled and disabled to accessibility
|
||||
* services.
|
||||
* @param readOnly controls the editable state of the input field. When `true`, the field cannot
|
||||
* be modified. However, a user can focus it and copy text from it.
|
||||
* @param textStyle the style to be applied to the input text. Defaults to [LocalTextStyle].
|
||||
* @param placeholder the placeholder to be displayed when the input text is empty.
|
||||
* @param leadingIcon the leading icon to be displayed at the start of the input field.
|
||||
* @param trailingIcon the trailing icon to be displayed at the end of the input field.
|
||||
* @param prefix the optional prefix to be displayed before the input text.
|
||||
* @param suffix the optional suffix to be displayed after the input text.
|
||||
* @param inputTransformation optional [InputTransformation] that will be used to transform
|
||||
* changes to the [TextFieldState] made by the user. The transformation will be applied to
|
||||
* changes made by hardware and software keyboard events, pasting or dropping text,
|
||||
* accessibility services, and tests. The transformation will _not_ be applied when changing
|
||||
* the [state] programmatically, or when the transformation is changed. If the transformation
|
||||
* is changed on an existing text field, it will be applied to the next user edit. The
|
||||
* transformation will not immediately affect the current [state].
|
||||
* @param outputTransformation optional [OutputTransformation] that transforms how the contents
|
||||
* of the text field are presented.
|
||||
* @param scrollState scroll state that manages the horizontal scroll of the input field.
|
||||
* @param shape the shape of the input field.
|
||||
* @param colors [TextFieldColors] that will be used to resolve the colors used for this input
|
||||
* field in different states. See [SearchBarDefaults.inputFieldColors].
|
||||
* @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
|
||||
* emitting [Interaction]s for this input field. You can use this to change the search bar's
|
||||
* appearance or preview the search bar in different states. Note that if `null` is provided,
|
||||
* interactions will still happen internally.
|
||||
*/
|
||||
@ExperimentalMaterial3Api
|
||||
@Composable
|
||||
fun SearchBarInputField(
|
||||
state: TextFieldState,
|
||||
onSearch: (String) -> Unit,
|
||||
expanded: Boolean,
|
||||
onExpandedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
prefix: @Composable (() -> Unit)? = null,
|
||||
suffix: @Composable (() -> Unit)? = null,
|
||||
inputTransformation: InputTransformation? = null,
|
||||
outputTransformation: OutputTransformation? = null,
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
shape: Shape = inputFieldShape,
|
||||
colors: TextFieldColors = inputFieldColors(),
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
|
||||
|
||||
val focused = interactionSource.collectIsFocusedAsState().value
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val textColor = textStyle.color.takeOrElse {
|
||||
if (focused) colors.focusedTextColor else colors.unfocusedTextColor
|
||||
}
|
||||
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
|
||||
|
||||
BasicTextField(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.sizeIn(
|
||||
minWidth = SearchBarMinWidth,
|
||||
maxWidth = SearchBarMaxWidth,
|
||||
minHeight = InputFieldHeight,
|
||||
)
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged { if (it.isFocused) onExpandedChange(true) }
|
||||
.fillMaxWidth(),
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
textStyle = mergedTextStyle,
|
||||
cursorBrush = SolidColor(colors.cursorColor),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
onKeyboardAction = { onSearch(state.text.toString()) },
|
||||
interactionSource = interactionSource,
|
||||
inputTransformation = inputTransformation,
|
||||
outputTransformation = outputTransformation,
|
||||
scrollState = scrollState,
|
||||
decorator = TextFieldDefaults.decorator(
|
||||
state = state,
|
||||
enabled = enabled,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
outputTransformation = outputTransformation,
|
||||
interactionSource = interactionSource,
|
||||
placeholder = placeholder,
|
||||
leadingIcon = leadingIcon?.let { leading ->
|
||||
{ Box(Modifier.offset(x = SearchBarIconOffsetX)) { leading() } }
|
||||
},
|
||||
trailingIcon = trailingIcon?.let { trailing ->
|
||||
{ Box(Modifier.offset(x = -SearchBarIconOffsetX)) { trailing() } }
|
||||
},
|
||||
prefix = prefix,
|
||||
suffix = suffix,
|
||||
colors = colors,
|
||||
contentPadding = TextFieldDefaults.contentPaddingWithoutLabel(),
|
||||
container = {
|
||||
val containerColor =
|
||||
animateColorAsState(
|
||||
targetValue = if (focused) colors.focusedContainerColor else colors.unfocusedContainerColor,
|
||||
animationSpec = MaterialTheme.motionScheme.rememberFastEffectsSpec(),
|
||||
label = "ContainerColor",
|
||||
)
|
||||
Box(Modifier.textFieldBackground(containerColor::value, shape))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
val shouldClearFocus = !expanded && focused
|
||||
LaunchedEffect(expanded) {
|
||||
if (shouldClearFocus) {
|
||||
// Not strictly needed according to the motion spec, but since the animation
|
||||
// already has a delay, this works around b/261632544.
|
||||
delay(AnimationDelayMillis.toLong())
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.textFieldBackground(
|
||||
color: ColorProducer,
|
||||
shape: Shape,
|
||||
) = drawWithCache {
|
||||
val outline = shape.createOutline(size, layoutDirection, this)
|
||||
onDrawBehind { drawOutline(outline, color = color()) }
|
||||
}
|
||||
|
||||
private val SearchBarMinWidth = 360.dp
|
||||
private val SearchBarMaxWidth = 720.dp
|
||||
private val SearchBarIconOffsetX = 4.dp
|
||||
|
||||
@Suppress("ktlint:standard:property-naming")
|
||||
private const val AnimationDelayMillis = 100
|
||||
@@ -14,10 +14,8 @@ import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
@@ -51,6 +49,8 @@ import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
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.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -85,8 +85,8 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
||||
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
|
||||
var showUsernameError by rememberSaveable { mutableStateOf(false) }
|
||||
var showPasswordError by rememberSaveable { mutableStateOf(false) }
|
||||
val username = rememberTextFieldState()
|
||||
val password = rememberTextFieldState()
|
||||
var username by rememberSaveable { mutableStateOf("") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
var passwordHidden by rememberSaveable { mutableStateOf(true) }
|
||||
val context = LocalContext.current
|
||||
var signInJob by remember { mutableStateOf<Job?>(null) }
|
||||
@@ -94,13 +94,13 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
||||
|
||||
fun signIn() {
|
||||
if (signInJob?.isActive == true) return
|
||||
if (username.text.isEmpty()) {
|
||||
if (username.isEmpty()) {
|
||||
showUsernameError = true
|
||||
return
|
||||
} else {
|
||||
showUsernameError = false
|
||||
}
|
||||
if (password.text.isEmpty()) {
|
||||
if (password.isEmpty()) {
|
||||
showPasswordError = true
|
||||
return
|
||||
} else {
|
||||
@@ -112,7 +112,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
||||
EhUtils.signOut()
|
||||
signInJob = coroutineScope.launchIO {
|
||||
runCatching {
|
||||
EhEngine.signIn(username.text.toString(), password.text.toString())
|
||||
EhEngine.signIn(username, password)
|
||||
}.onFailure {
|
||||
withUIContext {
|
||||
focusManager.clearFocus()
|
||||
@@ -141,33 +141,31 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
||||
@Composable
|
||||
fun UsernameAndPasswordTextField() {
|
||||
OutlinedTextField(
|
||||
state = username,
|
||||
value = username,
|
||||
onValueChange = { username = it },
|
||||
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
|
||||
autofillTypes = listOf(AutofillType.Username),
|
||||
onFill = { username.setTextAndPlaceCursorAtEnd(it) },
|
||||
onFill = { username = it },
|
||||
),
|
||||
label = { Text(stringResource(R.string.username)) },
|
||||
supportingText = { if (showUsernameError) Text(stringResource(R.string.error_username_cannot_empty)) },
|
||||
trailingIcon = { if (showUsernameError) Icon(imageVector = Icons.Filled.Info, contentDescription = null) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
singleLine = true,
|
||||
isError = showUsernameError,
|
||||
)
|
||||
OutlinedTextField(
|
||||
state = password,
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
|
||||
autofillTypes = listOf(AutofillType.Password),
|
||||
onFill = { password.setTextAndPlaceCursorAtEnd(it) },
|
||||
onFill = { password = it },
|
||||
),
|
||||
label = { Text(stringResource(R.string.password)) },
|
||||
outputTransformation = {
|
||||
if (passwordHidden) {
|
||||
replace(0, length, "\u2022".repeat(length))
|
||||
}
|
||||
},
|
||||
visualTransformation = if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
|
||||
supportingText = { if (showPasswordError) Text(stringResource(R.string.error_password_cannot_empty)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
onKeyboardAction = { signIn() },
|
||||
keyboardActions = KeyboardActions(onDone = { signIn() }),
|
||||
trailingIcon = {
|
||||
if (showPasswordError) {
|
||||
Icon(imageVector = Icons.Filled.Info, contentDescription = null)
|
||||
@@ -178,7 +176,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
},
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
singleLine = true,
|
||||
isError = showPasswordError,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
@@ -126,15 +125,16 @@ fun EhScreen(navigator: DestinationsNavigator) {
|
||||
if (hasSignedIn) {
|
||||
Column {
|
||||
val warning = stringResource(id = R.string.settings_eh_identity_cookies_signed)
|
||||
val state = rememberTextFieldState(cookies.joinToString("\n") { (k, v) -> "$k: $v" })
|
||||
val str = cookies.joinToString("\n") { (k, v) -> "$k: $v" }
|
||||
Text(text = AnnotatedString.fromHtml(warning))
|
||||
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
|
||||
OutlinedTextField(
|
||||
state = state,
|
||||
value = str,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
trailingIcon = {
|
||||
IconButton(onClick = {
|
||||
copyTextToClipboard(state.text, true)
|
||||
copyTextToClipboard(str, true)
|
||||
// Avoid double notify user since system have done that on Tiramisu above
|
||||
if (!isAtLeastT) launchSnackBar(copiedToClipboard)
|
||||
}) {
|
||||
|
||||
@@ -12,9 +12,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Help
|
||||
@@ -87,17 +84,17 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
||||
scope.launch {
|
||||
dialogState.dialog { cont ->
|
||||
val types = stringArrayResource(id = R.array.filter_entries)
|
||||
val type = rememberTextFieldState(types[0])
|
||||
val state = rememberTextFieldState()
|
||||
var type by remember { mutableStateOf(types[0]) }
|
||||
var value by remember { mutableStateOf("") }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
fun invalidateAndSave() {
|
||||
if (state.text.isBlank()) {
|
||||
if (value.isBlank()) {
|
||||
error = textIsEmpty
|
||||
return
|
||||
}
|
||||
error = null
|
||||
val mode = FilterMode.entries[types.indexOf(type.text)]
|
||||
val filter = Filter(mode, state.text.toString())
|
||||
val mode = FilterMode.entries[types.indexOf(type)]
|
||||
val filter = Filter(mode, value)
|
||||
filter.remember {
|
||||
if (it) {
|
||||
cont.resume(Unit)
|
||||
@@ -132,7 +129,8 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
|
||||
readOnly = true,
|
||||
state = type,
|
||||
value = type,
|
||||
onValueChange = {},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.filter_label))
|
||||
},
|
||||
@@ -148,7 +146,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
||||
text = { Text(text = it) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
type.setTextAndPlaceCursorAtEnd(it)
|
||||
type = it
|
||||
},
|
||||
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
||||
)
|
||||
@@ -158,7 +156,8 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
val isError = error != null
|
||||
OutlinedTextField(
|
||||
state = state,
|
||||
value = value,
|
||||
onValueChange = { value = it },
|
||||
label = { Text(text = stringResource(id = R.string.filter_text)) },
|
||||
supportingText = { error?.let { Text(text = it) } },
|
||||
trailingIcon = {
|
||||
@@ -170,7 +169,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
},
|
||||
isError = isError,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
maxLines = 1,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done,
|
||||
),
|
||||
|
||||
@@ -20,8 +20,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
@@ -166,7 +164,7 @@ class DialogState {
|
||||
invalidator: (suspend Raise<String>.(String) -> Unit)? = null,
|
||||
) = dialog { cont ->
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val state = rememberTextFieldState(initial)
|
||||
var state by remember(cont) { mutableStateOf(initial) }
|
||||
var error by remember(cont) { mutableStateOf<String?>(null) }
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
@@ -175,13 +173,12 @@ class DialogState {
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val text = state.text.toString()
|
||||
if (invalidator == null) {
|
||||
cont.resume(text)
|
||||
cont.resume(state)
|
||||
} else {
|
||||
coroutineScope.launch {
|
||||
error = either { invalidator(text) }.leftOrNull()
|
||||
error ?: cont.resume(text)
|
||||
error = either { invalidator(state) }.leftOrNull()
|
||||
error ?: cont.resume(state)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
@@ -191,9 +188,10 @@ class DialogState {
|
||||
title = title.ifNotNullThen { Text(text = title!!) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
state = state,
|
||||
label = hint?.let {
|
||||
@Composable { Text(text = it) }
|
||||
value = state,
|
||||
onValueChange = { state = it },
|
||||
label = hint.ifNotNullThen {
|
||||
Text(text = hint!!)
|
||||
},
|
||||
trailingIcon = error.ifNotNullThen {
|
||||
Icon(
|
||||
@@ -221,20 +219,19 @@ class DialogState {
|
||||
invalidator: (suspend Raise<String>.(String, Boolean) -> Unit)? = null,
|
||||
): Pair<String, Boolean> = dialog { cont ->
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val state = rememberTextFieldState(initial)
|
||||
var state by remember(cont) { mutableStateOf(initial) }
|
||||
var error by remember(cont) { mutableStateOf<String?>(null) }
|
||||
var checkedState by remember { mutableStateOf(checked) }
|
||||
AlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val text = state.text.toString()
|
||||
if (invalidator == null) {
|
||||
cont.resume(text to checkedState)
|
||||
cont.resume(state to checkedState)
|
||||
} else {
|
||||
coroutineScope.launch {
|
||||
error = either { invalidator(text, checkedState) }.leftOrNull()
|
||||
error ?: cont.resume(text to checkedState)
|
||||
error = either { invalidator(state, checkedState) }.leftOrNull()
|
||||
error ?: cont.resume(state to checkedState)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
@@ -247,9 +244,10 @@ class DialogState {
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
state = state,
|
||||
label = hint?.let {
|
||||
{ Text(text = stringResource(id = it)) }
|
||||
value = state,
|
||||
onValueChange = { state = it },
|
||||
label = hint.ifNotNullThen {
|
||||
Text(text = stringResource(id = hint!!))
|
||||
},
|
||||
trailingIcon = error.ifNotNullThen {
|
||||
Icon(
|
||||
@@ -551,21 +549,22 @@ class DialogState {
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(1F),
|
||||
placeFirstItemInCenter = true,
|
||||
) {
|
||||
val note = rememberTextFieldState(initialNote)
|
||||
var note by remember { mutableStateOf(initialNote) }
|
||||
TextField(
|
||||
state = note,
|
||||
value = note,
|
||||
onValueChange = { note = it },
|
||||
modifier = Modifier.fillMaxWidth(0.45F).aspectRatio(1F),
|
||||
label = { Text(text = stringResource(id = hint)) },
|
||||
trailingIcon = {
|
||||
if (note.text.isNotEmpty()) {
|
||||
IconButton(onClick = { note.clearText() }) {
|
||||
if (note.isNotEmpty()) {
|
||||
IconButton(onClick = { note = "" }) {
|
||||
Icon(imageVector = Icons.Default.Close, contentDescription = null)
|
||||
}
|
||||
}
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = "${note.text.toString().toByteArray().size} / $maxChar",
|
||||
text = "${note.toByteArray().size} / $maxChar",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
@@ -578,7 +577,7 @@ class DialogState {
|
||||
)
|
||||
items.forEachIndexed { index, (icon, text) ->
|
||||
Column(
|
||||
modifier = Modifier.clip(IconWithTextCorner).clickable { dismissWith(index to note.text.toString()) }
|
||||
modifier = Modifier.clip(IconWithTextCorner).clickable { dismissWith(index to note) }
|
||||
.fillMaxWidth(0.2F),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.navigationBarsPadding
|
||||
@@ -47,7 +48,10 @@ fun ReaderPageSheetMeta(
|
||||
Spacer(modifier = Modifier.size(32.dp))
|
||||
Text(text = stringResource(id = text))
|
||||
}
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize() // Workaround for https://issuetracker.google.com/341594885
|
||||
.verticalScroll(rememberScrollState()).navigationBarsPadding(),
|
||||
) {
|
||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh, onClick = retry)
|
||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh_original, onClick = retryOrigin)
|
||||
Item(icon = Icons.Default.Share, text = R.string.action_share, onClick = share)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven("https://androidx.dev/snapshots/builds/12183479/artifacts/repository")
|
||||
maven("https://androidx.dev/snapshots/builds/12043948/artifacts/repository")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user