Migrate to BTF2

This reverts commit 8a29d46b
This commit is contained in:
FooIbar committed 2024-10-16 11:14:54 +08:00
1 parent 59bdb4b2f0
commit faa54dedb1
7 files changed
+251 -165

No files matched your search

@@ -1,103 +0,0 @@
@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
@@ -0,0 +1,191 @@
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.fastEffectsSpec(),
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,8 +14,10 @@ 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
@@ -49,8 +51,6 @@ 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
@@ -82,8 +82,8 @@ fun SignInScreen(navigator: DestinationsNavigator) {
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
var showUsernameError by rememberSaveable { mutableStateOf(false) }
var showPasswordError by rememberSaveable { mutableStateOf(false) }
var username by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
val username = rememberTextFieldState()
val password = rememberTextFieldState()
var passwordHidden by rememberSaveable { mutableStateOf(true) }
val context = LocalContext.current
var signInJob by remember { mutableStateOf<Job?>(null) }
@@ -91,13 +91,13 @@ fun SignInScreen(navigator: DestinationsNavigator) {
fun signIn() {
if (signInJob?.isActive == true) return
if (username.isEmpty()) {
if (username.text.isEmpty()) {
showUsernameError = true
return
} else {
showUsernameError = false
}
if (password.isEmpty()) {
if (password.text.isEmpty()) {
showPasswordError = true
return
} else {
@@ -109,7 +109,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
EhUtils.signOut()
signInJob = coroutineScope.launchIO {
runCatching {
EhEngine.signIn(username, password)
EhEngine.signIn(username.text.toString(), password.text.toString())
}.onFailure {
withUIContext {
focusManager.clearFocus()
@@ -137,31 +137,33 @@ fun SignInScreen(navigator: DestinationsNavigator) {
@Composable
fun UsernameAndPasswordTextField() {
OutlinedTextField(
value = username,
onValueChange = { username = it },
state = username,
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
autofillTypes = listOf(AutofillType.Username),
onFill = { username = it },
onFill = { username.setTextAndPlaceCursorAtEnd(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),
singleLine = true,
lineLimits = TextFieldLineLimits.SingleLine,
isError = showUsernameError,
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
state = password,
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
autofillTypes = listOf(AutofillType.Password),
onFill = { password = it },
onFill = { password.setTextAndPlaceCursorAtEnd(it) },
),
label = { Text(stringResource(R.string.password)) },
visualTransformation = if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
outputTransformation = {
if (passwordHidden) {
replace(0, length, "\u2022".repeat(length))
}
},
supportingText = { if (showPasswordError) Text(stringResource(R.string.error_password_cannot_empty)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
keyboardActions = KeyboardActions(onDone = { signIn() }),
onKeyboardAction = { signIn() },
trailingIcon = {
if (showPasswordError) {
Icon(imageVector = Icons.Filled.Info, contentDescription = null)
@@ -172,7 +174,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
}
}
},
singleLine = true,
lineLimits = TextFieldLineLimits.SingleLine,
isError = showPasswordError,
)
}
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Spacer
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
@@ -103,16 +104,15 @@ fun EhScreen(navigator: DestinationsNavigator) {
) {
Column {
val warning = stringResource(id = R.string.settings_eh_identity_cookies_signed)
val str = cookies.joinToString("\n") { (k, v) -> "$k: $v" }
val state = rememberTextFieldState(cookies.joinToString("\n") { (k, v) -> "$k: $v" })
Text(text = AnnotatedString.fromHtml(warning))
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
OutlinedTextField(
value = str,
onValueChange = {},
state = state,
readOnly = true,
trailingIcon = {
IconButton(onClick = {
copyTextToClipboard(str, true)
copyTextToClipboard(state.text, true)
// Avoid double notify user since system have done that on Tiramisu above
if (!isAtLeastT) launchSnackBar(copiedToClipboard)
}) {
@@ -12,6 +12,9 @@ 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
@@ -84,17 +87,17 @@ fun FilterScreen(navigator: DestinationsNavigator) {
scope.launch {
dialogState.dialog { cont ->
val types = stringArrayResource(id = R.array.filter_entries)
var type by remember { mutableStateOf(types[0]) }
var value by remember { mutableStateOf("") }
val type = rememberTextFieldState(types[0])
val state = rememberTextFieldState()
var error by remember { mutableStateOf<String?>(null) }
fun invalidateAndSave() {
if (value.isBlank()) {
if (state.text.isBlank()) {
error = textIsEmpty
return
}
error = null
val mode = FilterMode.entries[types.indexOf(type)]
val filter = Filter(mode, value)
val mode = FilterMode.entries[types.indexOf(type.text)]
val filter = Filter(mode, state.text.toString())
filter.remember {
if (it) {
cont.resume(Unit)
@@ -129,8 +132,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
OutlinedTextField(
modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
readOnly = true,
value = type,
onValueChange = {},
state = type,
label = {
Text(text = stringResource(id = R.string.filter_label))
},
@@ -146,7 +148,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
text = { Text(text = it) },
onClick = {
expanded = false
type = it
type.setTextAndPlaceCursorAtEnd(it)
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
@@ -156,8 +158,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
Spacer(modifier = Modifier.size(16.dp))
val isError = error != null
OutlinedTextField(
value = value,
onValueChange = { value = it },
state = state,
label = { Text(text = stringResource(id = R.string.filter_text)) },
supportingText = { error?.let { Text(text = it) } },
trailingIcon = {
@@ -169,7 +170,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
}
},
isError = isError,
maxLines = 1,
lineLimits = TextFieldLineLimits.SingleLine,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
),
@@ -20,6 +20,8 @@ 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
@@ -163,7 +165,7 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
invalidator: (suspend Raise<String>.(String) -> Unit)? = null,
) = dialog { cont ->
val coroutineScope = rememberCoroutineScope()
var state by remember(cont) { mutableStateOf(initial) }
val state = rememberTextFieldState(initial)
var error by remember(cont) { mutableStateOf<String?>(null) }
AlertDialog(
onDismissRequest = {
@@ -172,12 +174,13 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
},
confirmButton = {
TextButton(onClick = {
val text = state.text.toString()
if (invalidator == null) {
cont.resume(state)
cont.resume(text)
} else {
coroutineScope.launch {
error = either { invalidator(state) }.leftOrNull()
error ?: cont.resume(state)
error = either { invalidator(text) }.leftOrNull()
error ?: cont.resume(text)
}
}
}) {
@@ -187,11 +190,8 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
title = title.ifNotNullThen { Text(text = title!!) },
text = {
OutlinedTextField(
value = state,
onValueChange = { state = it },
label = hint.ifNotNullThen {
Text(text = hint!!)
},
state = state,
label = hint?.let { { Text(text = it) } },
trailingIcon = error.ifNotNullThen {
Icon(
imageVector = Icons.Filled.Info,
@@ -218,19 +218,20 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
invalidator: (suspend Raise<String>.(String, Boolean) -> Unit)? = null,
): Pair<String, Boolean> = dialog { cont ->
val coroutineScope = rememberCoroutineScope()
var state by remember(cont) { mutableStateOf(initial) }
val state = rememberTextFieldState(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(state to checkedState)
cont.resume(text to checkedState)
} else {
coroutineScope.launch {
error = either { invalidator(state, checkedState) }.leftOrNull()
error ?: cont.resume(state to checkedState)
error = either { invalidator(text, checkedState) }.leftOrNull()
error ?: cont.resume(text to checkedState)
}
}
}) {
@@ -243,10 +244,9 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
OutlinedTextField(
value = state,
onValueChange = { state = it },
label = hint.ifNotNullThen {
Text(text = stringResource(id = hint!!))
state = state,
label = hint?.let {
{ Text(text = stringResource(id = it)) }
},
trailingIcon = error.ifNotNullThen {
Icon(
@@ -540,22 +540,21 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
modifier = Modifier.fillMaxWidth().aspectRatio(1F),
placeFirstItemInCenter = true,
) {
var note by remember { mutableStateOf(initialNote) }
val note = rememberTextFieldState(initialNote)
TextField(
value = note,
onValueChange = { note = it },
state = note,
modifier = Modifier.fillMaxWidth(0.45F).aspectRatio(1F),
label = { Text(text = stringResource(id = hint)) },
trailingIcon = {
if (note.isNotEmpty()) {
IconButton(onClick = { note = "" }) {
if (note.text.isNotEmpty()) {
IconButton(onClick = { note.clearText() }) {
Icon(imageVector = Icons.Default.Close, contentDescription = null)
}
}
},
supportingText = {
Text(
text = "${note.toByteArray().size} / $maxChar",
text = "${note.text.toString().toByteArray().size} / $maxChar",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End,
)
@@ -568,7 +567,7 @@ value class DialogState(val field: MutableComposable = mutableStateOf(null)) : M
)
items.forEachIndexed { index, (icon, text) ->
Column(
modifier = Modifier.clip(IconWithTextCorner).clickable { resume(index to note) }.fillMaxWidth(0.2F),
modifier = Modifier.clip(IconWithTextCorner).clickable { resume(index to note.text.toString()) }.fillMaxWidth(0.2F),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(imageVector = icon, contentDescription = null, tint = AlertDialogDefaults.iconContentColor)
@@ -5,7 +5,6 @@ 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
@@ -50,10 +49,7 @@ fun ReaderPageSheetMeta(
Spacer(modifier = Modifier.size(32.dp))
Text(text = stringResource(id = text))
}
Column(
modifier = Modifier.fillMaxSize() // Workaround for https://issuetracker.google.com/341594885
.verticalScroll(rememberScrollState()).navigationBarsPadding(),
) {
Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
showAds?.let { Item(icon = Icons.Default.Visibility, text = R.string.show_blocked_image, onClick = it) }
Item(icon = Icons.Default.Refresh, text = R.string.refresh, onClick = retry)
Item(icon = Icons.Default.Refresh, text = R.string.refresh_original, onClick = retryOrigin)