mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Auto-submit on Enter
Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
@@ -45,6 +45,9 @@ import ru.fromchat.ui.components.ExpressiveStepPageHeader
|
|||||||
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
||||||
import ru.fromchat.ui.components.Text
|
import ru.fromchat.ui.components.Text
|
||||||
import ru.fromchat.ui.components.expressiveStepFieldColors
|
import ru.fromchat.ui.components.expressiveStepFieldColors
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSingleLineKeyboardOptions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitKeyboardActions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitOnEnter
|
||||||
import ru.fromchat.ui.components.trackImeScrollTarget
|
import ru.fromchat.ui.components.trackImeScrollTarget
|
||||||
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
||||||
|
|
||||||
@@ -73,54 +76,12 @@ internal fun passwordStepPage(
|
|||||||
val unexpected = stringResource(Res.string.error_unexpected)
|
val unexpected = stringResource(Res.string.error_unexpected)
|
||||||
val loginLabel = stringResource(Res.string.login)
|
val loginLabel = stringResource(Res.string.login)
|
||||||
|
|
||||||
return ExpressiveStepPage(
|
fun attemptSubmit() {
|
||||||
hero = ExpressiveHeroSpec(
|
if (busy) return
|
||||||
icon = Icons.Filled.Lock,
|
|
||||||
polygon = MaterialShapes.Cookie7Sided.normalized(),
|
|
||||||
containerColor = colorScheme.secondaryContainer,
|
|
||||||
contentColor = colorScheme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
content = { imeScroll ->
|
|
||||||
var visible by rememberSaveable { mutableStateOf(false) }
|
|
||||||
|
|
||||||
ExpressiveStepPageHeader(
|
|
||||||
title = stringResource(Res.string.auth_step_password_title),
|
|
||||||
body = stringResource(Res.string.auth_step_password_body),
|
|
||||||
)
|
|
||||||
OutlinedTextField(
|
|
||||||
value = password,
|
|
||||||
onValueChange = onPasswordChange,
|
|
||||||
label = { Text(stringResource(Res.string.password)) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
|
||||||
.padding(horizontal = SettingsStepHorizontalPadding),
|
|
||||||
enabled = !busy,
|
|
||||||
singleLine = true,
|
|
||||||
visualTransformation = if (visible) VisualTransformation.None else PasswordVisualTransformation(),
|
|
||||||
trailingIcon = {
|
|
||||||
IconButton(onClick = { visible = !visible }) {
|
|
||||||
Icon(
|
|
||||||
imageVector = if (visible) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
|
|
||||||
contentDescription = stringResource(
|
|
||||||
if (visible) Res.string.hide_password else Res.string.show_password,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = expressiveStepFieldColors(),
|
|
||||||
shape = SettingsPasswordOutlineFieldShape,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
listFooter = { ChangeServerButton() },
|
|
||||||
button = {
|
|
||||||
ActionButton(
|
|
||||||
onClick = {
|
|
||||||
if (busy) return@ActionButton
|
|
||||||
|
|
||||||
if (password.length !in 5..50) {
|
if (password.length !in 5..50) {
|
||||||
onSnackbar(pwdLen, null)
|
onSnackbar(pwdLen, null)
|
||||||
return@ActionButton
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -165,7 +126,54 @@ internal fun passwordStepPage(
|
|||||||
busy = false
|
busy = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpressiveStepPage(
|
||||||
|
hero = ExpressiveHeroSpec(
|
||||||
|
icon = Icons.Filled.Lock,
|
||||||
|
polygon = MaterialShapes.Cookie7Sided.normalized(),
|
||||||
|
containerColor = colorScheme.secondaryContainer,
|
||||||
|
contentColor = colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
content = { imeScroll ->
|
||||||
|
var visible by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
ExpressiveStepPageHeader(
|
||||||
|
title = stringResource(Res.string.auth_step_password_title),
|
||||||
|
body = stringResource(Res.string.auth_step_password_body),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = password,
|
||||||
|
onValueChange = onPasswordChange,
|
||||||
|
label = { Text(stringResource(Res.string.password)) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
||||||
|
.expressiveStepSubmitOnEnter(::attemptSubmit)
|
||||||
|
.padding(horizontal = SettingsStepHorizontalPadding),
|
||||||
|
enabled = !busy,
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = expressiveStepSingleLineKeyboardOptions(),
|
||||||
|
keyboardActions = expressiveStepSubmitKeyboardActions(::attemptSubmit),
|
||||||
|
visualTransformation = if (visible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { visible = !visible }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (visible) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
|
||||||
|
contentDescription = stringResource(
|
||||||
|
if (visible) Res.string.hide_password else Res.string.show_password,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
colors = expressiveStepFieldColors(),
|
||||||
|
shape = SettingsPasswordOutlineFieldShape,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
listFooter = { ChangeServerButton() },
|
||||||
|
button = {
|
||||||
|
ActionButton(
|
||||||
|
onClick = ::attemptSubmit,
|
||||||
enabled = !busy,
|
enabled = !busy,
|
||||||
loading = busy,
|
loading = busy,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ import ru.fromchat.ui.components.ExpressiveStepPageHeader
|
|||||||
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
||||||
import ru.fromchat.ui.components.Text
|
import ru.fromchat.ui.components.Text
|
||||||
import ru.fromchat.ui.components.expressiveStepFieldColors
|
import ru.fromchat.ui.components.expressiveStepFieldColors
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSingleLineKeyboardOptions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitKeyboardActions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitOnEnter
|
||||||
import ru.fromchat.ui.components.trackImeScrollTarget
|
import ru.fromchat.ui.components.trackImeScrollTarget
|
||||||
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
||||||
import ru.fromchat.username
|
import ru.fromchat.username
|
||||||
@@ -70,56 +73,16 @@ internal fun usernameStepPage(
|
|||||||
val nextLabel = stringResource(Res.string.settings_next)
|
val nextLabel = stringResource(Res.string.settings_next)
|
||||||
val hasProhibitedChars = username.trim().any { !isAllowedUsernameChar(it) }
|
val hasProhibitedChars = username.trim().any { !isAllowedUsernameChar(it) }
|
||||||
|
|
||||||
return ExpressiveStepPage(
|
fun attemptSubmit() {
|
||||||
hero = ExpressiveHeroSpec(
|
if (busy || hasProhibitedChars) return
|
||||||
icon = Icons.Filled.Person,
|
|
||||||
polygon = MaterialShapes.Cookie4Sided.normalized(),
|
|
||||||
containerColor = colorScheme.primaryContainer,
|
|
||||||
contentColor = colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
autoFocusPrimaryField = true,
|
|
||||||
content = { imeScroll ->
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
ExpressiveStepAutoFocusEffect(focusRequester)
|
|
||||||
|
|
||||||
ExpressiveStepPageHeader(
|
|
||||||
title = stringResource(Res.string.auth_step_username_title),
|
|
||||||
body = stringResource(Res.string.auth_step_username_body),
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = username,
|
|
||||||
onValueChange = onUsernameChange,
|
|
||||||
label = { Text(stringResource(Res.string.username)) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.focusRequester(focusRequester)
|
|
||||||
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
|
||||||
.padding(horizontal = SettingsStepHorizontalPadding),
|
|
||||||
singleLine = true,
|
|
||||||
isError = hasProhibitedChars,
|
|
||||||
supportingText = if (hasProhibitedChars) {
|
|
||||||
{ Text(usernameCharsError) }
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
},
|
|
||||||
colors = expressiveStepFieldColors(),
|
|
||||||
shape = SettingsPasswordOutlineFieldShape,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
listFooter = { ChangeServerButton() },
|
|
||||||
button = {
|
|
||||||
ActionButton(
|
|
||||||
onClick = {
|
|
||||||
if (busy || hasProhibitedChars) return@ActionButton
|
|
||||||
val trimmed = username.trim()
|
val trimmed = username.trim()
|
||||||
if (trimmed.isBlank()) {
|
if (trimmed.isBlank()) {
|
||||||
onSnackbar(fillAll, null)
|
onSnackbar(fillAll, null)
|
||||||
return@ActionButton
|
return
|
||||||
}
|
}
|
||||||
if (trimmed.length !in 3..20) {
|
if (trimmed.length !in 3..20) {
|
||||||
onSnackbar(usernameLenError, null)
|
onSnackbar(usernameLenError, null)
|
||||||
return@ActionButton
|
return
|
||||||
}
|
}
|
||||||
onUsernameChange(trimmed)
|
onUsernameChange(trimmed)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -148,7 +111,52 @@ internal fun usernameStepPage(
|
|||||||
busy = false
|
busy = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpressiveStepPage(
|
||||||
|
hero = ExpressiveHeroSpec(
|
||||||
|
icon = Icons.Filled.Person,
|
||||||
|
polygon = MaterialShapes.Cookie4Sided.normalized(),
|
||||||
|
containerColor = colorScheme.primaryContainer,
|
||||||
|
contentColor = colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
autoFocusPrimaryField = true,
|
||||||
|
content = { imeScroll ->
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
ExpressiveStepAutoFocusEffect(focusRequester)
|
||||||
|
|
||||||
|
ExpressiveStepPageHeader(
|
||||||
|
title = stringResource(Res.string.auth_step_username_title),
|
||||||
|
body = stringResource(Res.string.auth_step_username_body),
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = username,
|
||||||
|
onValueChange = onUsernameChange,
|
||||||
|
label = { Text(stringResource(Res.string.username)) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
||||||
|
.expressiveStepSubmitOnEnter(::attemptSubmit)
|
||||||
|
.padding(horizontal = SettingsStepHorizontalPadding),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = expressiveStepSingleLineKeyboardOptions(),
|
||||||
|
keyboardActions = expressiveStepSubmitKeyboardActions(::attemptSubmit),
|
||||||
|
isError = hasProhibitedChars,
|
||||||
|
supportingText = if (hasProhibitedChars) {
|
||||||
|
{ Text(usernameCharsError) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
},
|
},
|
||||||
|
colors = expressiveStepFieldColors(),
|
||||||
|
shape = SettingsPasswordOutlineFieldShape,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
listFooter = { ChangeServerButton() },
|
||||||
|
button = {
|
||||||
|
ActionButton(
|
||||||
|
onClick = ::attemptSubmit,
|
||||||
enabled = !busy && !hasProhibitedChars,
|
enabled = !busy && !hasProhibitedChars,
|
||||||
loading = busy,
|
loading = busy,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
+21
-13
@@ -40,6 +40,9 @@ import ru.fromchat.ui.components.ExpressiveStepPageHeader
|
|||||||
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
||||||
import ru.fromchat.ui.components.Text
|
import ru.fromchat.ui.components.Text
|
||||||
import ru.fromchat.ui.components.expressiveStepFieldColors
|
import ru.fromchat.ui.components.expressiveStepFieldColors
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSingleLineKeyboardOptions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitKeyboardActions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitOnEnter
|
||||||
import ru.fromchat.ui.components.trackImeScrollTarget
|
import ru.fromchat.ui.components.trackImeScrollTarget
|
||||||
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
||||||
|
|
||||||
@@ -59,6 +62,20 @@ internal fun confirmPasswordStepPage(
|
|||||||
val pwdMatch = stringResource(Res.string.passwords_dont_match)
|
val pwdMatch = stringResource(Res.string.passwords_dont_match)
|
||||||
val nextLabel = stringResource(Res.string.settings_next)
|
val nextLabel = stringResource(Res.string.settings_next)
|
||||||
|
|
||||||
|
fun attemptSubmit() {
|
||||||
|
if (confirmPassword.isBlank()) {
|
||||||
|
onSnackbar(fillAll, null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmPassword != password) {
|
||||||
|
onSnackbar(pwdMatch, null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.launch { onContinue() }
|
||||||
|
}
|
||||||
|
|
||||||
return ExpressiveStepPage(
|
return ExpressiveStepPage(
|
||||||
hero = ExpressiveHeroSpec(
|
hero = ExpressiveHeroSpec(
|
||||||
icon = Icons.Filled.VerifiedUser,
|
icon = Icons.Filled.VerifiedUser,
|
||||||
@@ -81,8 +98,11 @@ internal fun confirmPasswordStepPage(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
||||||
|
.expressiveStepSubmitOnEnter(::attemptSubmit)
|
||||||
.padding(horizontal = SettingsStepHorizontalPadding),
|
.padding(horizontal = SettingsStepHorizontalPadding),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = expressiveStepSingleLineKeyboardOptions(),
|
||||||
|
keyboardActions = expressiveStepSubmitKeyboardActions(::attemptSubmit),
|
||||||
visualTransformation = if (visible) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (visible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(onClick = { visible = !visible }) {
|
IconButton(onClick = { visible = !visible }) {
|
||||||
@@ -100,19 +120,7 @@ internal fun confirmPasswordStepPage(
|
|||||||
},
|
},
|
||||||
button = {
|
button = {
|
||||||
ActionButton(
|
ActionButton(
|
||||||
onClick = {
|
onClick = ::attemptSubmit,
|
||||||
if (confirmPassword.isBlank()) {
|
|
||||||
onSnackbar(fillAll, null)
|
|
||||||
return@ActionButton
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmPassword != password) {
|
|
||||||
onSnackbar(pwdMatch, null)
|
|
||||||
return@ActionButton
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.launch { onContinue() }
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(nextLabel)
|
Text(nextLabel)
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ import ru.fromchat.ui.components.ExpressiveStepPageHeader
|
|||||||
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
import ru.fromchat.ui.components.SettingsPasswordOutlineFieldShape
|
||||||
import ru.fromchat.ui.components.Text
|
import ru.fromchat.ui.components.Text
|
||||||
import ru.fromchat.ui.components.expressiveStepFieldColors
|
import ru.fromchat.ui.components.expressiveStepFieldColors
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSingleLineKeyboardOptions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitKeyboardActions
|
||||||
|
import ru.fromchat.ui.components.expressiveStepSubmitOnEnter
|
||||||
import ru.fromchat.ui.components.trackImeScrollTarget
|
import ru.fromchat.ui.components.trackImeScrollTarget
|
||||||
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
||||||
|
|
||||||
@@ -72,71 +75,17 @@ internal fun profileStepPage(
|
|||||||
val usernameTaken = stringResource(Res.string.auth_username_taken)
|
val usernameTaken = stringResource(Res.string.auth_username_taken)
|
||||||
val registerLabel = stringResource(Res.string.register_button)
|
val registerLabel = stringResource(Res.string.register_button)
|
||||||
|
|
||||||
return ExpressiveStepPage(
|
fun attemptSubmit() {
|
||||||
hero = ExpressiveHeroSpec(
|
if (busy) return
|
||||||
icon = Icons.Filled.Face,
|
|
||||||
polygon = MaterialShapes.Cookie6Sided.normalized(),
|
|
||||||
containerColor = colorScheme.surfaceContainerHighest,
|
|
||||||
contentColor = colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
content = { imeScroll ->
|
|
||||||
ExpressiveStepPageHeader(
|
|
||||||
title = stringResource(Res.string.auth_step_profile_title),
|
|
||||||
body = stringResource(Res.string.auth_step_profile_body),
|
|
||||||
)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = displayName,
|
|
||||||
onValueChange = onDisplayNameChange,
|
|
||||||
label = { Text(stringResource(Res.string.display_name)) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
|
||||||
.padding(horizontal = SettingsStepHorizontalPadding),
|
|
||||||
enabled = !busy,
|
|
||||||
singleLine = true,
|
|
||||||
supportingText = {
|
|
||||||
Text(stringResource(Res.string.auth_char_count, displayName.length, DISPLAY_NAME_MAX))
|
|
||||||
},
|
|
||||||
colors = fieldColors,
|
|
||||||
shape = SettingsPasswordOutlineFieldShape,
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(12.dp))
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = bio,
|
|
||||||
onValueChange = onBioChange,
|
|
||||||
label = { Text(stringResource(Res.string.profile_headline_bio)) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
|
||||||
.padding(horizontal = SettingsStepHorizontalPadding),
|
|
||||||
enabled = !busy,
|
|
||||||
minLines = 3,
|
|
||||||
maxLines = 6,
|
|
||||||
supportingText = {
|
|
||||||
if (bio.isNotEmpty()) {
|
|
||||||
Text(stringResource(Res.string.auth_char_count, bio.length, BIO_MAX))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = fieldColors,
|
|
||||||
shape = SettingsPasswordOutlineFieldShape,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
button = {
|
|
||||||
ActionButton(
|
|
||||||
onClick = {
|
|
||||||
if (busy) return@ActionButton
|
|
||||||
|
|
||||||
if (displayName.isBlank() || displayName.trim().length > DISPLAY_NAME_MAX) {
|
if (displayName.isBlank() || displayName.trim().length > DISPLAY_NAME_MAX) {
|
||||||
onSnackbar(displayNameError, null)
|
onSnackbar(displayNameError, null)
|
||||||
return@ActionButton
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bio.trim().length > BIO_MAX) {
|
if (bio.trim().length > BIO_MAX) {
|
||||||
onSnackbar(unexpected, null)
|
onSnackbar(unexpected, null)
|
||||||
return@ActionButton
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -171,7 +120,66 @@ internal fun profileStepPage(
|
|||||||
busy = false
|
busy = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpressiveStepPage(
|
||||||
|
hero = ExpressiveHeroSpec(
|
||||||
|
icon = Icons.Filled.Face,
|
||||||
|
polygon = MaterialShapes.Cookie6Sided.normalized(),
|
||||||
|
containerColor = colorScheme.surfaceContainerHighest,
|
||||||
|
contentColor = colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
content = { imeScroll ->
|
||||||
|
ExpressiveStepPageHeader(
|
||||||
|
title = stringResource(Res.string.auth_step_profile_title),
|
||||||
|
body = stringResource(Res.string.auth_step_profile_body),
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = displayName,
|
||||||
|
onValueChange = onDisplayNameChange,
|
||||||
|
label = { Text(stringResource(Res.string.display_name)) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
||||||
|
.expressiveStepSubmitOnEnter(::attemptSubmit)
|
||||||
|
.padding(horizontal = SettingsStepHorizontalPadding),
|
||||||
|
enabled = !busy,
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = expressiveStepSingleLineKeyboardOptions(),
|
||||||
|
keyboardActions = expressiveStepSubmitKeyboardActions(::attemptSubmit),
|
||||||
|
supportingText = {
|
||||||
|
Text(stringResource(Res.string.auth_char_count, displayName.length, DISPLAY_NAME_MAX))
|
||||||
},
|
},
|
||||||
|
colors = fieldColors,
|
||||||
|
shape = SettingsPasswordOutlineFieldShape,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = bio,
|
||||||
|
onValueChange = onBioChange,
|
||||||
|
label = { Text(stringResource(Res.string.profile_headline_bio)) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY)
|
||||||
|
.padding(horizontal = SettingsStepHorizontalPadding),
|
||||||
|
enabled = !busy,
|
||||||
|
minLines = 3,
|
||||||
|
maxLines = 6,
|
||||||
|
supportingText = {
|
||||||
|
if (bio.isNotEmpty()) {
|
||||||
|
Text(stringResource(Res.string.auth_char_count, bio.length, BIO_MAX))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = fieldColors,
|
||||||
|
shape = SettingsPasswordOutlineFieldShape,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
button = {
|
||||||
|
ActionButton(
|
||||||
|
onClick = ::attemptSubmit,
|
||||||
enabled = !busy,
|
enabled = !busy,
|
||||||
loading = busy,
|
loading = busy,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package ru.fromchat.ui.components
|
|||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.gestures.BringIntoViewSpec
|
import androidx.compose.foundation.gestures.BringIntoViewSpec
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
||||||
import androidx.compose.foundation.gestures.animateScrollBy
|
import androidx.compose.foundation.gestures.animateScrollBy
|
||||||
import androidx.compose.foundation.gestures.scrollBy
|
import androidx.compose.foundation.gestures.scrollBy
|
||||||
@@ -20,6 +22,13 @@ import androidx.compose.runtime.mutableIntStateOf
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
|
import androidx.compose.ui.input.key.isShiftPressed
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||||
|
import androidx.compose.ui.input.key.type
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.focus.focusProperties
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
@@ -48,6 +57,21 @@ val LocalExpressiveStepFocusEnabled = compositionLocalOf { true }
|
|||||||
/** When true, the current step's primary field should request focus once. */
|
/** When true, the current step's primary field should request focus once. */
|
||||||
val LocalExpressiveStepAutoFocusPrimary = compositionLocalOf { false }
|
val LocalExpressiveStepAutoFocusPrimary = compositionLocalOf { false }
|
||||||
|
|
||||||
|
/** Desktop Enter / Android IME action for expressive step single-line fields. */
|
||||||
|
fun Modifier.expressiveStepSubmitOnEnter(onSubmit: () -> Unit): Modifier = onPreviewKeyEvent { event ->
|
||||||
|
if (event.type != KeyEventType.KeyDown) return@onPreviewKeyEvent false
|
||||||
|
if (event.key != Key.Enter && event.key != Key.NumPadEnter) return@onPreviewKeyEvent false
|
||||||
|
if (event.isShiftPressed) return@onPreviewKeyEvent false
|
||||||
|
onSubmit()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expressiveStepSingleLineKeyboardOptions(): KeyboardOptions =
|
||||||
|
KeyboardOptions(imeAction = ImeAction.Done)
|
||||||
|
|
||||||
|
fun expressiveStepSubmitKeyboardActions(onSubmit: () -> Unit): KeyboardActions =
|
||||||
|
KeyboardActions(onDone = { onSubmit() })
|
||||||
|
|
||||||
/** Requests focus for [focusRequester] when [LocalExpressiveStepAutoFocusPrimary] becomes true. */
|
/** Requests focus for [focusRequester] when [LocalExpressiveStepAutoFocusPrimary] becomes true. */
|
||||||
@Composable
|
@Composable
|
||||||
fun ExpressiveStepAutoFocusEffect(focusRequester: FocusRequester) {
|
fun ExpressiveStepAutoFocusEffect(focusRequester: FocusRequester) {
|
||||||
|
|||||||
Reference in New Issue
Block a user