diff --git a/app/shared/src/commonMain/composeResources/values-ru/strings.xml b/app/shared/src/commonMain/composeResources/values-ru/strings.xml index f4708aa..5521743 100644 --- a/app/shared/src/commonMain/composeResources/values-ru/strings.xml +++ b/app/shared/src/commonMain/composeResources/values-ru/strings.xml @@ -31,6 +31,7 @@ От 1 до 64 символов Заполните все поля Имя пользователя — от 3 до 20 символов + Имя пользователя может содержать только английские буквы, цифры, дефисы и подчеркивания Пароль — от 5 до 50 символов Пароли не совпадают Добро пожаловать в FromChat diff --git a/app/shared/src/commonMain/composeResources/values/strings.xml b/app/shared/src/commonMain/composeResources/values/strings.xml index 3041ff8..fca2c67 100644 --- a/app/shared/src/commonMain/composeResources/values/strings.xml +++ b/app/shared/src/commonMain/composeResources/values/strings.xml @@ -38,6 +38,7 @@ Please fill in every field Username must be 3 to 20 characters + Username can only contain English letters, numbers, hyphens and underscores Password must be 5 to 50 characters The two passwords don’t match Welcome to FromChat diff --git a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/UsernameStep.kt b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/UsernameStep.kt index d08ad13..140fe65 100644 --- a/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/UsernameStep.kt +++ b/app/shared/src/commonMain/kotlin/ru/fromchat/ui/auth/UsernameStep.kt @@ -42,8 +42,13 @@ import ru.fromchat.ui.components.expressiveStepFieldColors import ru.fromchat.ui.components.trackImeScrollTarget import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding import ru.fromchat.username +import ru.fromchat.username_chars_error import ru.fromchat.username_length_error +/** Matches backend `is_valid_username`: English letters, digits, hyphen, underscore. */ +private fun isAllowedUsernameChar(ch: Char) = + ch in 'a'..'z' || ch in 'A'..'Z' || ch in '0'..'9' || ch == '_' || ch == '-' + @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable internal fun usernameStepPage( @@ -59,9 +64,11 @@ internal fun usernameStepPage( val fillAll = stringResource(Res.string.fill_all_fields) val usernameLenError = stringResource(Res.string.username_length_error) + val usernameCharsError = stringResource(Res.string.username_chars_error) val serverFail = stringResource(Res.string.auth_server_connect_failed) val unexpected = stringResource(Res.string.error_unexpected) val nextLabel = stringResource(Res.string.settings_next) + val hasProhibitedChars = username.trim().any { !isAllowedUsernameChar(it) } return ExpressiveStepPage( hero = ExpressiveHeroSpec( @@ -90,6 +97,12 @@ internal fun usernameStepPage( .trackImeScrollTarget(imeScroll, ExpressiveStepLazyListIndices.STEPS_BODY) .padding(horizontal = SettingsStepHorizontalPadding), singleLine = true, + isError = hasProhibitedChars, + supportingText = if (hasProhibitedChars) { + { Text(usernameCharsError) } + } else { + null + }, colors = expressiveStepFieldColors(), shape = SettingsPasswordOutlineFieldShape, ) @@ -98,7 +111,7 @@ internal fun usernameStepPage( button = { ActionButton( onClick = { - if (busy) return@ActionButton + if (busy || hasProhibitedChars) return@ActionButton val trimmed = username.trim() if (trimmed.isBlank()) { onSnackbar(fillAll, null) @@ -136,7 +149,7 @@ internal fun usernameStepPage( } } }, - enabled = !busy, + enabled = !busy && !hasProhibitedChars, loading = busy, modifier = Modifier.fillMaxWidth(), ) {