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.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
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.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Info
|
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.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
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.style.TextDecoration
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -85,8 +85,8 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
|||||||
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
|
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
|
||||||
var showUsernameError by rememberSaveable { mutableStateOf(false) }
|
var showUsernameError by rememberSaveable { mutableStateOf(false) }
|
||||||
var showPasswordError by rememberSaveable { mutableStateOf(false) }
|
var showPasswordError by rememberSaveable { mutableStateOf(false) }
|
||||||
val username = rememberTextFieldState()
|
var username by rememberSaveable { mutableStateOf("") }
|
||||||
val password = rememberTextFieldState()
|
var password by rememberSaveable { mutableStateOf("") }
|
||||||
var passwordHidden by rememberSaveable { mutableStateOf(true) }
|
var passwordHidden by rememberSaveable { mutableStateOf(true) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var signInJob by remember { mutableStateOf<Job?>(null) }
|
var signInJob by remember { mutableStateOf<Job?>(null) }
|
||||||
@@ -94,13 +94,13 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
fun signIn() {
|
fun signIn() {
|
||||||
if (signInJob?.isActive == true) return
|
if (signInJob?.isActive == true) return
|
||||||
if (username.text.isEmpty()) {
|
if (username.isEmpty()) {
|
||||||
showUsernameError = true
|
showUsernameError = true
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
showUsernameError = false
|
showUsernameError = false
|
||||||
}
|
}
|
||||||
if (password.text.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
showPasswordError = true
|
showPasswordError = true
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -112,7 +112,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
|||||||
EhUtils.signOut()
|
EhUtils.signOut()
|
||||||
signInJob = coroutineScope.launchIO {
|
signInJob = coroutineScope.launchIO {
|
||||||
runCatching {
|
runCatching {
|
||||||
EhEngine.signIn(username.text.toString(), password.text.toString())
|
EhEngine.signIn(username, password)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
withUIContext {
|
withUIContext {
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
@@ -141,33 +141,31 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun UsernameAndPasswordTextField() {
|
fun UsernameAndPasswordTextField() {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
state = username,
|
value = username,
|
||||||
|
onValueChange = { username = it },
|
||||||
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
|
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
|
||||||
autofillTypes = listOf(AutofillType.Username),
|
autofillTypes = listOf(AutofillType.Username),
|
||||||
onFill = { username.setTextAndPlaceCursorAtEnd(it) },
|
onFill = { username = it },
|
||||||
),
|
),
|
||||||
label = { Text(stringResource(R.string.username)) },
|
label = { Text(stringResource(R.string.username)) },
|
||||||
supportingText = { if (showUsernameError) Text(stringResource(R.string.error_username_cannot_empty)) },
|
supportingText = { if (showUsernameError) Text(stringResource(R.string.error_username_cannot_empty)) },
|
||||||
trailingIcon = { if (showUsernameError) Icon(imageVector = Icons.Filled.Info, contentDescription = null) },
|
trailingIcon = { if (showUsernameError) Icon(imageVector = Icons.Filled.Info, contentDescription = null) },
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||||
lineLimits = TextFieldLineLimits.SingleLine,
|
singleLine = true,
|
||||||
isError = showUsernameError,
|
isError = showUsernameError,
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
state = password,
|
value = password,
|
||||||
|
onValueChange = { password = it },
|
||||||
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
|
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)).autofill(
|
||||||
autofillTypes = listOf(AutofillType.Password),
|
autofillTypes = listOf(AutofillType.Password),
|
||||||
onFill = { password.setTextAndPlaceCursorAtEnd(it) },
|
onFill = { password = it },
|
||||||
),
|
),
|
||||||
label = { Text(stringResource(R.string.password)) },
|
label = { Text(stringResource(R.string.password)) },
|
||||||
outputTransformation = {
|
visualTransformation = if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
|
||||||
if (passwordHidden) {
|
|
||||||
replace(0, length, "\u2022".repeat(length))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
supportingText = { if (showPasswordError) Text(stringResource(R.string.error_password_cannot_empty)) },
|
supportingText = { if (showPasswordError) Text(stringResource(R.string.error_password_cannot_empty)) },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
onKeyboardAction = { signIn() },
|
keyboardActions = KeyboardActions(onDone = { signIn() }),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (showPasswordError) {
|
if (showPasswordError) {
|
||||||
Icon(imageVector = Icons.Filled.Info, contentDescription = null)
|
Icon(imageVector = Icons.Filled.Info, contentDescription = null)
|
||||||
@@ -178,7 +176,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lineLimits = TextFieldLineLimits.SingleLine,
|
singleLine = true,
|
||||||
isError = showPasswordError,
|
isError = showPasswordError,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
@@ -126,15 +125,16 @@ fun EhScreen(navigator: DestinationsNavigator) {
|
|||||||
if (hasSignedIn) {
|
if (hasSignedIn) {
|
||||||
Column {
|
Column {
|
||||||
val warning = stringResource(id = R.string.settings_eh_identity_cookies_signed)
|
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))
|
Text(text = AnnotatedString.fromHtml(warning))
|
||||||
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
|
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
state = state,
|
value = str,
|
||||||
|
onValueChange = {},
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
copyTextToClipboard(state.text, true)
|
copyTextToClipboard(str, true)
|
||||||
// Avoid double notify user since system have done that on Tiramisu above
|
// Avoid double notify user since system have done that on Tiramisu above
|
||||||
if (!isAtLeastT) launchSnackBar(copiedToClipboard)
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
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.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.Help
|
import androidx.compose.material.icons.automirrored.filled.Help
|
||||||
@@ -87,17 +84,17 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
dialogState.dialog { cont ->
|
dialogState.dialog { cont ->
|
||||||
val types = stringArrayResource(id = R.array.filter_entries)
|
val types = stringArrayResource(id = R.array.filter_entries)
|
||||||
val type = rememberTextFieldState(types[0])
|
var type by remember { mutableStateOf(types[0]) }
|
||||||
val state = rememberTextFieldState()
|
var value by remember { mutableStateOf("") }
|
||||||
var error by remember { mutableStateOf<String?>(null) }
|
var error by remember { mutableStateOf<String?>(null) }
|
||||||
fun invalidateAndSave() {
|
fun invalidateAndSave() {
|
||||||
if (state.text.isBlank()) {
|
if (value.isBlank()) {
|
||||||
error = textIsEmpty
|
error = textIsEmpty
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
error = null
|
error = null
|
||||||
val mode = FilterMode.entries[types.indexOf(type.text)]
|
val mode = FilterMode.entries[types.indexOf(type)]
|
||||||
val filter = Filter(mode, state.text.toString())
|
val filter = Filter(mode, value)
|
||||||
filter.remember {
|
filter.remember {
|
||||||
if (it) {
|
if (it) {
|
||||||
cont.resume(Unit)
|
cont.resume(Unit)
|
||||||
@@ -132,7 +129,8 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
|
modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
state = type,
|
value = type,
|
||||||
|
onValueChange = {},
|
||||||
label = {
|
label = {
|
||||||
Text(text = stringResource(id = R.string.filter_label))
|
Text(text = stringResource(id = R.string.filter_label))
|
||||||
},
|
},
|
||||||
@@ -148,7 +146,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
text = { Text(text = it) },
|
text = { Text(text = it) },
|
||||||
onClick = {
|
onClick = {
|
||||||
expanded = false
|
expanded = false
|
||||||
type.setTextAndPlaceCursorAtEnd(it)
|
type = it
|
||||||
},
|
},
|
||||||
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
||||||
)
|
)
|
||||||
@@ -158,7 +156,8 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
Spacer(modifier = Modifier.size(16.dp))
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
val isError = error != null
|
val isError = error != null
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
state = state,
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
label = { Text(text = stringResource(id = R.string.filter_text)) },
|
label = { Text(text = stringResource(id = R.string.filter_text)) },
|
||||||
supportingText = { error?.let { Text(text = it) } },
|
supportingText = { error?.let { Text(text = it) } },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
@@ -170,7 +169,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isError = isError,
|
isError = isError,
|
||||||
lineLimits = TextFieldLineLimits.SingleLine,
|
maxLines = 1,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
imeAction = ImeAction.Done,
|
imeAction = ImeAction.Done,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
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.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
@@ -166,7 +164,7 @@ class DialogState {
|
|||||||
invalidator: (suspend Raise<String>.(String) -> Unit)? = null,
|
invalidator: (suspend Raise<String>.(String) -> Unit)? = null,
|
||||||
) = dialog { cont ->
|
) = dialog { cont ->
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val state = rememberTextFieldState(initial)
|
var state by remember(cont) { mutableStateOf(initial) }
|
||||||
var error by remember(cont) { mutableStateOf<String?>(null) }
|
var error by remember(cont) { mutableStateOf<String?>(null) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
@@ -175,13 +173,12 @@ class DialogState {
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
val text = state.text.toString()
|
|
||||||
if (invalidator == null) {
|
if (invalidator == null) {
|
||||||
cont.resume(text)
|
cont.resume(state)
|
||||||
} else {
|
} else {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
error = either { invalidator(text) }.leftOrNull()
|
error = either { invalidator(state) }.leftOrNull()
|
||||||
error ?: cont.resume(text)
|
error ?: cont.resume(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@@ -191,9 +188,10 @@ class DialogState {
|
|||||||
title = title.ifNotNullThen { Text(text = title!!) },
|
title = title.ifNotNullThen { Text(text = title!!) },
|
||||||
text = {
|
text = {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
state = state,
|
value = state,
|
||||||
label = hint?.let {
|
onValueChange = { state = it },
|
||||||
@Composable { Text(text = it) }
|
label = hint.ifNotNullThen {
|
||||||
|
Text(text = hint!!)
|
||||||
},
|
},
|
||||||
trailingIcon = error.ifNotNullThen {
|
trailingIcon = error.ifNotNullThen {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -221,20 +219,19 @@ class DialogState {
|
|||||||
invalidator: (suspend Raise<String>.(String, Boolean) -> Unit)? = null,
|
invalidator: (suspend Raise<String>.(String, Boolean) -> Unit)? = null,
|
||||||
): Pair<String, Boolean> = dialog { cont ->
|
): Pair<String, Boolean> = dialog { cont ->
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val state = rememberTextFieldState(initial)
|
var state by remember(cont) { mutableStateOf(initial) }
|
||||||
var error by remember(cont) { mutableStateOf<String?>(null) }
|
var error by remember(cont) { mutableStateOf<String?>(null) }
|
||||||
var checkedState by remember { mutableStateOf(checked) }
|
var checkedState by remember { mutableStateOf(checked) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { cont.cancel() },
|
onDismissRequest = { cont.cancel() },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
val text = state.text.toString()
|
|
||||||
if (invalidator == null) {
|
if (invalidator == null) {
|
||||||
cont.resume(text to checkedState)
|
cont.resume(state to checkedState)
|
||||||
} else {
|
} else {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
error = either { invalidator(text, checkedState) }.leftOrNull()
|
error = either { invalidator(state, checkedState) }.leftOrNull()
|
||||||
error ?: cont.resume(text to checkedState)
|
error ?: cont.resume(state to checkedState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@@ -247,9 +244,10 @@ class DialogState {
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
state = state,
|
value = state,
|
||||||
label = hint?.let {
|
onValueChange = { state = it },
|
||||||
{ Text(text = stringResource(id = it)) }
|
label = hint.ifNotNullThen {
|
||||||
|
Text(text = stringResource(id = hint!!))
|
||||||
},
|
},
|
||||||
trailingIcon = error.ifNotNullThen {
|
trailingIcon = error.ifNotNullThen {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -551,21 +549,22 @@ class DialogState {
|
|||||||
modifier = Modifier.fillMaxWidth().aspectRatio(1F),
|
modifier = Modifier.fillMaxWidth().aspectRatio(1F),
|
||||||
placeFirstItemInCenter = true,
|
placeFirstItemInCenter = true,
|
||||||
) {
|
) {
|
||||||
val note = rememberTextFieldState(initialNote)
|
var note by remember { mutableStateOf(initialNote) }
|
||||||
TextField(
|
TextField(
|
||||||
state = note,
|
value = note,
|
||||||
|
onValueChange = { note = it },
|
||||||
modifier = Modifier.fillMaxWidth(0.45F).aspectRatio(1F),
|
modifier = Modifier.fillMaxWidth(0.45F).aspectRatio(1F),
|
||||||
label = { Text(text = stringResource(id = hint)) },
|
label = { Text(text = stringResource(id = hint)) },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
if (note.text.isNotEmpty()) {
|
if (note.isNotEmpty()) {
|
||||||
IconButton(onClick = { note.clearText() }) {
|
IconButton(onClick = { note = "" }) {
|
||||||
Icon(imageVector = Icons.Default.Close, contentDescription = null)
|
Icon(imageVector = Icons.Default.Close, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
supportingText = {
|
supportingText = {
|
||||||
Text(
|
Text(
|
||||||
text = "${note.text.toString().toByteArray().size} / $maxChar",
|
text = "${note.toByteArray().size} / $maxChar",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
textAlign = TextAlign.End,
|
textAlign = TextAlign.End,
|
||||||
)
|
)
|
||||||
@@ -578,7 +577,7 @@ class DialogState {
|
|||||||
)
|
)
|
||||||
items.forEachIndexed { index, (icon, text) ->
|
items.forEachIndexed { index, (icon, text) ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.clip(IconWithTextCorner).clickable { dismissWith(index to note.text.toString()) }
|
modifier = Modifier.clip(IconWithTextCorner).clickable { dismissWith(index to note) }
|
||||||
.fillMaxWidth(0.2F),
|
.fillMaxWidth(0.2F),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
@@ -47,7 +48,10 @@ fun ReaderPageSheetMeta(
|
|||||||
Spacer(modifier = Modifier.size(32.dp))
|
Spacer(modifier = Modifier.size(32.dp))
|
||||||
Text(text = stringResource(id = text))
|
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, onClick = retry)
|
||||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh_original, onClick = retryOrigin)
|
Item(icon = Icons.Default.Refresh, text = R.string.refresh_original, onClick = retryOrigin)
|
||||||
Item(icon = Icons.Default.Share, text = R.string.action_share, onClick = share)
|
Item(icon = Icons.Default.Share, text = R.string.action_share, onClick = share)
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
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