Files
app/composeApp/src/commonMain/kotlin/ru/fromchat/ui/auth/RegisterScreen.kt
T

177 lines
7.5 KiB
Kotlin

package ru.fromchat.ui.auth
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.PersonAdd
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import ru.fromchat.R
import ru.fromchat.api.ApiClient
import ru.fromchat.api.RegisterRequest
import ru.fromchat.api.apiRequest
import ru.fromchat.ui.LocalNavController
import ru.fromchat.ui.RowHeader
import com.pr0gramm3r101.utils.crypto.deriveAuthSecret
@Composable
fun RegisterScreen(
onRegistered: () -> Unit
) {
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
var username by remember { mutableStateOf("") }
var displayName by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }
var alert by remember { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope()
val navController = LocalNavController.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
RowHeader(
icon = Icons.Filled.PersonAdd,
title = stringResource(R.string.register),
subtitle = stringResource(R.string.register_d)
)
if (alert != null) {
Text(text = alert!!, color = MaterialTheme.colorScheme.error)
}
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text(stringResource(R.string.username)) },
singleLine = true
)
OutlinedTextField(
value = displayName,
onValueChange = { displayName = it },
label = { Text("Display Name") },
singleLine = true
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text(stringResource(R.string.password)) },
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
OutlinedTextField(
value = confirmPassword,
onValueChange = { confirmPassword = it },
label = { Text(stringResource(R.string.confirm_password)) },
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
val alert_error_filling = stringResource(R.string.fill_all_fields)
val alert_error_password_little = stringResource(R.string.password_length_error)
val alert_error_name_little = stringResource(R.string.username_length_error)
val alert_error_password_confrim = stringResource(R.string.passwords_dont_match)
IconButton(
onClick = { navController.navigateUp() }
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
Button(
onClick = {
// Checks
if (username.isBlank() || displayName.isBlank() || password.isBlank() || confirmPassword.isBlank()) {
alert = alert_error_filling
return@Button
}
if (password != confirmPassword) {
alert = alert_error_password_confrim
return@Button
}
if (username.length !in 3..20) {
alert = alert_error_name_little
return@Button
}
if (displayName.isBlank() || displayName.length > 64) {
alert = "Display name must be between 1 and 64 characters"
return@Button
}
if (password.length !in 5..50) {
alert = alert_error_password_little
return@Button
}
// Derive auth secret before sending (matches frontend implementation)
scope.launch {
val derived = deriveAuthSecret(username.trim(), password)
apiRequest(
onError = { message, _ ->
alert = message
},
onSuccess = { onRegistered() }
) {
ApiClient.register(
RegisterRequest(
username.trim(),
displayName.trim(),
derived,
derived
)
)
}
}
}
) {
Text(stringResource(R.string.register_button))
}
}
}
}
}
}