You've already forked RekomenciMobile
feat: added filling profile info after registration
This commit is contained in:
Generated
+8
@@ -4,6 +4,14 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-11-21T15:23:03.580191Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/dany/.android/avd/Medium_Phone.avd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
+3
-5
@@ -5,7 +5,7 @@ import org.koin.core.annotation.Single
|
|||||||
|
|
||||||
enum class AuthField {
|
enum class AuthField {
|
||||||
FirstName,
|
FirstName,
|
||||||
SecondName,
|
LastName,
|
||||||
Email,
|
Email,
|
||||||
Password,
|
Password,
|
||||||
ConfirmPassword,
|
ConfirmPassword,
|
||||||
@@ -24,15 +24,13 @@ data class ValidationResult(
|
|||||||
class ValidateAuthFieldsUseCase {
|
class ValidateAuthFieldsUseCase {
|
||||||
|
|
||||||
fun validateFillProfile(
|
fun validateFillProfile(
|
||||||
displayName: String,
|
|
||||||
firstName: String,
|
firstName: String,
|
||||||
lastName: String,
|
lastName: String,
|
||||||
phone: String
|
phone: String
|
||||||
): ValidationResult {
|
): ValidationResult {
|
||||||
val errors = buildMap {
|
val errors = buildMap {
|
||||||
if (displayName.isBlank()) put(AuthField.FirstName, "Введите никнейм")
|
|
||||||
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
||||||
if (lastName.isBlank()) put(AuthField.SecondName, "Введите фамилию")
|
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
||||||
if (!isPhoneValid(phone)) put(AuthField.Phone, "Некорректный номер телефона")
|
if (!isPhoneValid(phone)) put(AuthField.Phone, "Некорректный номер телефона")
|
||||||
}
|
}
|
||||||
return ValidationResult(errors)
|
return ValidationResult(errors)
|
||||||
@@ -89,7 +87,7 @@ class ValidateAuthFieldsUseCase {
|
|||||||
): ValidationResult {
|
): ValidationResult {
|
||||||
val errors = buildMap {
|
val errors = buildMap {
|
||||||
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
||||||
if (secondName.isBlank()) put(AuthField.SecondName, "Введите фамилию")
|
if (secondName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
||||||
}
|
}
|
||||||
return ValidationResult(errors)
|
return ValidationResult(errors)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ sealed class AppDestination(val route: String) {
|
|||||||
|
|
||||||
data object Profile : AppDestination("app/profile")
|
data object Profile : AppDestination("app/profile")
|
||||||
|
|
||||||
|
data object FillProfile : AppDestination("app/fill_profile")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import com.prodhack.moscow2025.presentation.screens.main.MainScreen
|
import com.prodhack.moscow2025.presentation.screens.main.MainScreen
|
||||||
import com.prodhack.moscow2025.domain.utils.NetworkError
|
import com.prodhack.moscow2025.domain.utils.NetworkError
|
||||||
|
import com.prodhack.moscow2025.presentation.screens.fillProfile.FillProfileScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
|
import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
|
import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
|
||||||
@@ -64,7 +65,7 @@ fun TTasksNavHost(
|
|||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
},
|
},
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
navController.navigate(AppDestination.Main.route) {
|
navController.navigate(AppDestination.FillProfile.route) {
|
||||||
popUpTo(AppDestination.Register.route) {
|
popUpTo(AppDestination.Register.route) {
|
||||||
inclusive = true
|
inclusive = true
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,19 @@ fun TTasksNavHost(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(AppDestination.FillProfile.route) {
|
||||||
|
FillProfileScreen(
|
||||||
|
snackbarHostState = snackbarHostState,
|
||||||
|
onSuccess = {
|
||||||
|
navController.navigate(AppDestination.Main.route) {
|
||||||
|
popUpTo(AppDestination.FillProfile.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
composable(AppDestination.Main.route) {
|
composable(AppDestination.Main.route) {
|
||||||
MainScreen()
|
MainScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
+156
-2
@@ -1,9 +1,163 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.fillProfile
|
package com.prodhack.moscow2025.presentation.screens.fillProfile
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
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.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.prodhack.moscow2025.R
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.BigButton
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FillProfileScreen() {
|
fun ErrorCollectorScope.FillProfileScreen(
|
||||||
Text("Fill profile will be here soon :)")
|
snackbarHostState: SnackbarHostState,
|
||||||
|
onSuccess: () -> Unit,
|
||||||
|
viewModel: FillProfileViewModel = koinViewModel()
|
||||||
|
) {
|
||||||
|
val typography = MaterialTheme.typography
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
val formState by viewModel.formStateFillProfile.collectAsState()
|
||||||
|
|
||||||
|
var errorText by remember { mutableStateOf("") }
|
||||||
|
val fillProfileState by viewModel.profileFillState.collectAsStateWithCallbacks(
|
||||||
|
onInputError = {
|
||||||
|
errorText = it.error
|
||||||
|
},
|
||||||
|
onConnectionError = {
|
||||||
|
errorText = "Нет подключения к сети"
|
||||||
|
},
|
||||||
|
onUnexpectedError = {
|
||||||
|
errorText = it.error
|
||||||
|
},
|
||||||
|
onLoading = {
|
||||||
|
errorText = ""
|
||||||
|
},
|
||||||
|
onSuccess = {
|
||||||
|
errorText = ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(fillProfileState) {
|
||||||
|
if (fillProfileState is UIState.Success) {
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(errorText) {
|
||||||
|
if (errorText.isNotEmpty()) {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = "Ошибка: $errorText",
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.imePadding()
|
||||||
|
.systemBarsPadding(),
|
||||||
|
contentAlignment = Alignment.BottomStart
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.lottie),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.width(130.dp),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = 30.dp, end = 30.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Давайте\nзнакомиться!",
|
||||||
|
style = typography.titleLarge,
|
||||||
|
fontSize = 31.sp
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_launcher_foreground),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(140.dp),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(20.dp))
|
||||||
|
TTTextField(
|
||||||
|
value = formState.firstName,
|
||||||
|
onValueChange = viewModel::onFirstNameChange,
|
||||||
|
label = "Ваше имя",
|
||||||
|
error = formState.errors[AuthField.FirstName]
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
TTTextField(
|
||||||
|
value = formState.lastName,
|
||||||
|
onValueChange = viewModel::onLastNameChange,
|
||||||
|
label = "Ваша фамилия",
|
||||||
|
error = formState.errors[AuthField.LastName]
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
TTTextField(
|
||||||
|
value = formState.phone,
|
||||||
|
onValueChange = viewModel::onPhoneChange,
|
||||||
|
label = "Ваш телефон",
|
||||||
|
error = formState.errors[AuthField.Phone]
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
BigButton(
|
||||||
|
onClick = viewModel::submit,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
buttonText = "Сохранить данные",
|
||||||
|
isLoading = fillProfileState is UIState.Loading
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(80.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+8
-21
@@ -23,9 +23,10 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.android.annotation.KoinViewModel
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
data class FillProfileFormState(
|
data class FillProfileFormState(
|
||||||
val displayName: String = "",
|
|
||||||
val firstName: String = "",
|
val firstName: String = "",
|
||||||
val lastName: String = "",
|
val lastName: String = "",
|
||||||
val phone: String = "",
|
val phone: String = "",
|
||||||
@@ -38,7 +39,6 @@ data class FillProfileFormState(
|
|||||||
|
|
||||||
other as FillProfileFormState
|
other as FillProfileFormState
|
||||||
|
|
||||||
if (displayName != other.displayName) return false
|
|
||||||
if (firstName != other.firstName) return false
|
if (firstName != other.firstName) return false
|
||||||
if (lastName != other.lastName) return false
|
if (lastName != other.lastName) return false
|
||||||
if (phone != other.phone) return false
|
if (phone != other.phone) return false
|
||||||
@@ -49,8 +49,7 @@ data class FillProfileFormState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = displayName.hashCode()
|
var result = firstName.hashCode()
|
||||||
result = 31 * result + firstName.hashCode()
|
|
||||||
result = 31 * result + lastName.hashCode()
|
result = 31 * result + lastName.hashCode()
|
||||||
result = 31 * result + phone.hashCode()
|
result = 31 * result + phone.hashCode()
|
||||||
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
||||||
@@ -59,33 +58,23 @@ data class FillProfileFormState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@KoinViewModel
|
||||||
class FillProfileViewModel(
|
class FillProfileViewModel(
|
||||||
private val updateUserUseCase: UpdateUserUseCase,
|
private val updateUserUseCase: UpdateUserUseCase,
|
||||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
|
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
|
||||||
private val galleryRepository: GalleryRepository
|
private val galleryRepository: GalleryRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
private val _formStateFillProfile = MutableStateFlow(FillProfileFormState())
|
private val _formStateFillProfile = MutableStateFlow(FillProfileFormState())
|
||||||
val formStateSignUp: StateFlow<FillProfileFormState> = _formStateFillProfile
|
val formStateFillProfile: StateFlow<FillProfileFormState> = _formStateFillProfile
|
||||||
|
|
||||||
|
|
||||||
private val _profileFillState = MutableUIStateFlow<String>()
|
private val _profileFillState = MutableUIStateFlow<String>()
|
||||||
val profileFillState: StateFlow<UIState<String>> = _profileFillState
|
val profileFillState: StateFlow<UIState<String>> = _profileFillState
|
||||||
|
|
||||||
|
|
||||||
fun onDisplayNameChange(value: String) {
|
|
||||||
_formStateFillProfile.update {
|
|
||||||
it.copy(
|
|
||||||
displayName = value,
|
|
||||||
errors = it.errors - AuthField.Email
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onFirstNameChange(value: String) {
|
fun onFirstNameChange(value: String) {
|
||||||
_formStateFillProfile.update {
|
_formStateFillProfile.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
firstName = value,
|
firstName = value,
|
||||||
errors = it.errors - AuthField.Email
|
errors = it.errors - AuthField.FirstName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +83,7 @@ class FillProfileViewModel(
|
|||||||
_formStateFillProfile.update {
|
_formStateFillProfile.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
lastName = value,
|
lastName = value,
|
||||||
errors = it.errors - AuthField.Email
|
errors = it.errors - AuthField.LastName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +92,7 @@ class FillProfileViewModel(
|
|||||||
_formStateFillProfile.update {
|
_formStateFillProfile.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
phone = value,
|
phone = value,
|
||||||
errors = it.errors - AuthField.Email
|
errors = it.errors - AuthField.Phone
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +147,6 @@ class FillProfileViewModel(
|
|||||||
fun submit() {
|
fun submit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val validation = validateAuthFieldsUseCase.validateFillProfile(
|
val validation = validateAuthFieldsUseCase.validateFillProfile(
|
||||||
displayName = _formStateFillProfile.value.displayName,
|
|
||||||
firstName = _formStateFillProfile.value.firstName,
|
firstName = _formStateFillProfile.value.firstName,
|
||||||
lastName = _formStateFillProfile.value.lastName,
|
lastName = _formStateFillProfile.value.lastName,
|
||||||
phone = _formStateFillProfile.value.phone
|
phone = _formStateFillProfile.value.phone
|
||||||
@@ -173,7 +161,6 @@ class FillProfileViewModel(
|
|||||||
|
|
||||||
val result = updateUserUseCase(
|
val result = updateUserUseCase(
|
||||||
UpdateUserData(
|
UpdateUserData(
|
||||||
displayName = _formStateFillProfile.value.displayName,
|
|
||||||
firstName = _formStateFillProfile.value.firstName,
|
firstName = _formStateFillProfile.value.firstName,
|
||||||
lastName = _formStateFillProfile.value.lastName,
|
lastName = _formStateFillProfile.value.lastName,
|
||||||
phone = _formStateFillProfile.value.phone
|
phone = _formStateFillProfile.value.phone
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ fun ErrorCollectorScope.LoginScreen(
|
|||||||
painter = painterResource(R.drawable.ic_launcher_foreground),
|
painter = painterResource(R.drawable.ic_launcher_foreground),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(250.dp)
|
.size(200.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
showDialog.value = true
|
showDialog.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-14
@@ -45,6 +45,7 @@ import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
|
|||||||
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
||||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -114,24 +115,18 @@ fun ErrorCollectorScope.RegisterScreen(
|
|||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Давайте\nзнакомиться!",
|
|
||||||
style = typography.titleLarge,
|
|
||||||
fontSize = 31.sp
|
|
||||||
)
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(R.drawable.ic_launcher_foreground),
|
painter = painterResource(R.drawable.ic_launcher_foreground),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(140.dp),
|
modifier = Modifier
|
||||||
contentScale = ContentScale.Crop
|
.size(200.dp)
|
||||||
)
|
)
|
||||||
}
|
Text(
|
||||||
Spacer(Modifier.height(20.dp))
|
text = "Регистрация",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontSize = 40.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
TTTextField(
|
TTTextField(
|
||||||
value = formState.email,
|
value = formState.email,
|
||||||
onValueChange = viewModel::onEmailChange,
|
onValueChange = viewModel::onEmailChange,
|
||||||
|
|||||||
Reference in New Issue
Block a user