fix: fix phone field on profile screen, bottom bar beautify; feat: show buttons only after change, on profile edit

This commit is contained in:
MaximOksiuta
2025-11-22 05:43:40 +03:00
parent 09ff18cb02
commit d1e38cdfe8
7 changed files with 536 additions and 469 deletions
@@ -25,6 +25,7 @@ data class ValidationResult(
@Single @Single
class ValidateAuthFieldsUseCase { class ValidateAuthFieldsUseCase {
fun validateProfile( fun validateProfile(
chosenPattern: PhoneNumberPattern?,
firstName: String, firstName: String,
lastName: String, lastName: String,
email: String, email: String,
@@ -34,7 +35,11 @@ class ValidateAuthFieldsUseCase {
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя") if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию") if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
if (!isEmailValid(email)) put(AuthField.Email, "Некорректный email") if (!isEmailValid(email)) put(AuthField.Email, "Некорректный email")
if (!isPhoneValid(phone)) put(AuthField.Phone, "Некорректный номер телефона") val maxCount = chosenPattern!!.pattern.count { it == '0' }
if (phone.isNotBlank() && !isPhoneValid(phone) && phone.length != maxCount) put(
AuthField.Phone,
"Некорректный номер телефона"
)
} }
return ValidationResult(errors) return ValidationResult(errors)
} }
@@ -4,6 +4,8 @@ import android.util.Log
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -59,7 +61,6 @@ fun TBottomNavigation(modifier: Modifier = Modifier, selectedPage: Int, onSelect
} }
target?.let { (it - center).toDp() } target?.let { (it - center).toDp() }
} }
AnimatedVisibility(indicatorOffset != null) {
indicatorOffset?.let { indicatorOffset?.let {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -71,7 +72,6 @@ fun TBottomNavigation(modifier: Modifier = Modifier, selectedPage: Int, onSelect
) )
) )
} }
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
Icon( Icon(
modifier = Modifier modifier = Modifier
@@ -0,0 +1,158 @@
package com.prodhack.moscow2025.presentation.components.standart
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.presentation.screens.fillProfile.UIPhoneNumberPattern
import com.prodhack.moscow2025.presentation.theme.Paddings
import com.prodhack.moscow2025.presentation.theme.Shapes
import com.prodhack.moscow2025.presentation.utils.PhoneVisualTransformation
@Composable
fun TPhoneField(
modifier: Modifier = Modifier,
currentPattern: UIPhoneNumberPattern?,
currentPhone: String,
onPhoneChange: (String) -> Unit,
error: String?,
onOpenCountryList: () -> Unit,
) {
val colorScheme = MaterialTheme.colorScheme
Row(
modifier = modifier.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
Paddings.medium
)
) {
FieldWrapper(
modifier = Modifier
.width(IntrinsicSize.Min)
.fillMaxHeight()
) {
BasicTextField(
modifier = Modifier
.fillMaxSize()
.offset(y = 5.dp)
.padding(bottom = 16.dp)
.background(colorScheme.primary, Shapes.smallRoundedBox)
.clip(Shapes.smallRoundedBox),
value = currentPattern?.prefix ?: "",
onValueChange = {},
readOnly = true,
textStyle = TextStyle(
color = colorScheme.onPrimary
),
decorationBox = { innerTextField ->
Row(
modifier = Modifier
.clickable {
onOpenCountryList()
}
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = Modifier.weight(1f)) {
innerTextField()
}
Icon(
modifier = Modifier.size(15.dp),
painter = painterResource(R.drawable.ic_arr_dropdown),
tint = colorScheme.onPrimary,
contentDescription = null
)
}
}
)
}
TTTextField(
value = currentPhone,
onValueChange = onPhoneChange,
label = "Ваш телефон",
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone
),
visualTransformation = currentPattern?.let {
PhoneVisualTransformation(
it.pattern,
'0'
)
} ?: VisualTransformation.None,
error = error
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TPhoneCountryList(
modifier: Modifier = Modifier,
isSheetOpen: MutableState<Boolean>,
sheetState: SheetState,
patternList: List<UIPhoneNumberPattern>,
setPattern: (UIPhoneNumberPattern) -> Unit
) {
if (isSheetOpen.value) {
ModalBottomSheet(
modifier = modifier,
sheetState = sheetState,
onDismissRequest = {
isSheetOpen.value = false
},
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(patternList) { pattern ->
Text(
text = pattern.name,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clickable {
setPattern(pattern)
isSheetOpen.value = false
}
)
}
}
}
}
}
}
@@ -44,7 +44,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -56,6 +55,8 @@ import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.domain.usecase.auth.AuthField import com.prodhack.moscow2025.domain.usecase.auth.AuthField
import com.prodhack.moscow2025.presentation.components.standart.BigButton import com.prodhack.moscow2025.presentation.components.standart.BigButton
import com.prodhack.moscow2025.presentation.components.standart.FieldWrapper import com.prodhack.moscow2025.presentation.components.standart.FieldWrapper
import com.prodhack.moscow2025.presentation.components.standart.TPhoneCountryList
import com.prodhack.moscow2025.presentation.components.standart.TPhoneField
import com.prodhack.moscow2025.presentation.components.standart.TTTextField import com.prodhack.moscow2025.presentation.components.standart.TTTextField
import com.prodhack.moscow2025.presentation.theme.Paddings import com.prodhack.moscow2025.presentation.theme.Paddings
import com.prodhack.moscow2025.presentation.theme.Shapes import com.prodhack.moscow2025.presentation.theme.Shapes
@@ -165,70 +166,16 @@ fun ErrorCollectorScope.FillProfileScreen(
error = formState.errors[AuthField.LastName], error = formState.errors[AuthField.LastName],
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(12.dp))
TPhoneField(
Row( currentPattern = viewModel.currentPattern.value,
modifier = Modifier.height(IntrinsicSize.Min), currentPhone = formState.phone,
verticalAlignment = Alignment.CenterVertically, onPhoneChange = viewModel::onPhoneChange,
horizontalArrangement = Arrangement.spacedBy( error = formState.errors[AuthField.Phone],
Paddings.medium onOpenCountryList =
) {
) {
FieldWrapper(modifier = Modifier
.width(IntrinsicSize.Min)
.fillMaxHeight()) {
BasicTextField(
modifier = Modifier
.fillMaxSize()
.offset(y = 5.dp)
.padding(bottom = 16.dp)
.background(colorScheme.primary, Shapes.smallRoundedBox)
.clip(Shapes.smallRoundedBox),
value = viewModel.chosenPattern.value?.prefix ?: "",
onValueChange = {},
readOnly = true,
textStyle = TextStyle(
color = colorScheme.onPrimary
),
decorationBox = { innerTextField ->
Row(
modifier = Modifier
.clickable {
isSheetOpen.value = true isSheetOpen.value = true
} }
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = Modifier.weight(1f)) {
innerTextField()
}
Icon(
modifier = Modifier.size(15.dp),
painter = painterResource(R.drawable.ic_arr_dropdown),
tint = colorScheme.onPrimary,
contentDescription = null
) )
}
}
)
}
TTTextField(
value = formState.phone,
onValueChange = viewModel::onPhoneChange,
label = "Ваш телефон",
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone
),
visualTransformation = viewModel.chosenPattern.value?.pattern?.let {
PhoneVisualTransformation(
it,
'0'
)
} ?: VisualTransformation.None,
error = formState.errors[AuthField.Phone]
)
Log.d("Test", formState.errors[AuthField.Phone].toString())
}
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
BigButton( BigButton(
@@ -241,32 +188,12 @@ fun ErrorCollectorScope.FillProfileScreen(
} }
} }
if (isSheetOpen.value) { TPhoneCountryList(
ModalBottomSheet( isSheetOpen = isSheetOpen,
sheetState = sheetState, sheetState = sheetState,
onDismissRequest = { patternList = viewModel.phoneNumberPatterns,
isSheetOpen.value = false setPattern = {
}, viewModel.currentPattern.value = it
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(viewModel.phoneNumberPatterns) { pattern ->
Text(
text = pattern.name,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clickable {
viewModel.chosenPattern.value = pattern
isSheetOpen.value = false
} }
) )
} }
}
}
}
}
}
@@ -29,7 +29,6 @@ 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.android.annotation.KoinViewModel
import org.koin.core.annotation.Single
data class FillProfileFormState( data class FillProfileFormState(
val firstName: String = "", val firstName: String = "",
@@ -95,7 +94,7 @@ class FillProfileViewModel(
} }
fun onPhoneChange(value: String) { fun onPhoneChange(value: String) {
val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE val maxDigits = currentPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
val digits = value.filter { it.isDigit() }.take(maxDigits) val digits = value.filter { it.isDigit() }.take(maxDigits)
_formStateFillProfile.update { _formStateFillProfile.update {
it.copy( it.copy(
@@ -153,14 +152,14 @@ class FillProfileViewModel(
} }
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null) val currentPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>() val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
fun update() { fun update() {
// Load default pattern // Load default pattern
chosenPattern.value = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI() currentPattern.value = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
// Load all phone number patterns // Load all phone number patterns
phoneNumberPatterns.clear() phoneNumberPatterns.clear()
@@ -173,7 +172,7 @@ class FillProfileViewModel(
firstName = _formStateFillProfile.value.firstName, firstName = _formStateFillProfile.value.firstName,
lastName = _formStateFillProfile.value.lastName, lastName = _formStateFillProfile.value.lastName,
phone = _formStateFillProfile.value.phone, phone = _formStateFillProfile.value.phone,
chosenPattern = chosenPattern.value?.mapToDomain() chosenPattern = currentPattern.value?.mapToDomain()
) )
if (!validation.isValid) { if (!validation.isValid) {
@@ -187,7 +186,7 @@ class FillProfileViewModel(
UpdateUserData( UpdateUserData(
firstName = _formStateFillProfile.value.firstName, firstName = _formStateFillProfile.value.firstName,
lastName = _formStateFillProfile.value.lastName, lastName = _formStateFillProfile.value.lastName,
phone = chosenPattern.value?.mapToDomain()?.let { phoneNumberPattern -> phone = currentPattern.value?.mapToDomain()?.let { phoneNumberPattern ->
convertNumberToPattern( convertNumberToPattern(
phoneNumberPattern, phoneNumberPattern,
_formStateFillProfile.value.phone _formStateFillProfile.value.phone
@@ -51,6 +51,8 @@ import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.domain.usecase.auth.AuthField import com.prodhack.moscow2025.domain.usecase.auth.AuthField
import com.prodhack.moscow2025.presentation.components.standart.BigButton import com.prodhack.moscow2025.presentation.components.standart.BigButton
import com.prodhack.moscow2025.presentation.components.standart.FieldWrapper import com.prodhack.moscow2025.presentation.components.standart.FieldWrapper
import com.prodhack.moscow2025.presentation.components.standart.TPhoneCountryList
import com.prodhack.moscow2025.presentation.components.standart.TPhoneField
import com.prodhack.moscow2025.presentation.components.standart.TTTextField import com.prodhack.moscow2025.presentation.components.standart.TTTextField
import com.prodhack.moscow2025.presentation.theme.Paddings import com.prodhack.moscow2025.presentation.theme.Paddings
import com.prodhack.moscow2025.presentation.theme.Shapes import com.prodhack.moscow2025.presentation.theme.Shapes
@@ -118,31 +120,31 @@ fun ErrorCollectorScope.ProfileScreen(
.fillMaxSize() .fillMaxSize()
.imePadding() .imePadding()
.systemBarsPadding() .systemBarsPadding()
.padding(horizontal = 30.dp), .padding(horizontal = Paddings.large),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
) { ) {
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(Paddings.large))
Text( Text(
text = "Профиль", text = "Профиль",
style = typography.titleLarge, style = typography.titleLarge,
fontSize = 40.sp fontSize = 40.sp
) )
Spacer(Modifier.height(20.dp)) Spacer(Modifier.height(Paddings.large))
TTTextField( TTTextField(
value = formState.firstName, value = formState.firstName,
onValueChange = viewModel::onFirstNameChange, onValueChange = viewModel::onFirstNameChange,
label = "Имя", label = "Имя",
error = formState.errors[AuthField.FirstName], error = formState.errors[AuthField.FirstName],
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(Paddings.medium))
TTTextField( TTTextField(
value = formState.lastName, value = formState.lastName,
onValueChange = viewModel::onLastNameChange, onValueChange = viewModel::onLastNameChange,
label = "Фамилия", label = "Фамилия",
error = formState.errors[AuthField.LastName], error = formState.errors[AuthField.LastName],
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(Paddings.medium))
TTTextField( TTTextField(
value = formState.email, value = formState.email,
onValueChange = viewModel::onEmailChange, onValueChange = viewModel::onEmailChange,
@@ -150,75 +152,48 @@ fun ErrorCollectorScope.ProfileScreen(
error = formState.errors[AuthField.Email], error = formState.errors[AuthField.Email],
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email) keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(Paddings.medium))
Row(
modifier = Modifier.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Paddings.medium)
) {
FieldWrapper(
modifier = Modifier
.width(IntrinsicSize.Min)
.fillMaxHeight()
) {
BasicTextField(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 16.dp)
.clip(Shapes.smallRoundedBox)
.background(
color = colorScheme.primary,
shape = Shapes.smallRoundedBox
),
value = viewModel.chosenPattern.value?.prefix ?: "",
onValueChange = {},
readOnly = true,
textStyle = TextStyle(
color = colorScheme.onPrimary
),
decorationBox = { innerTextField ->
Row(
modifier = Modifier
.clickable { isSheetOpen.value = true }
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = Modifier.weight(1f)) {
innerTextField()
}
Icon(
modifier = Modifier.size(15.dp),
painter = painterResource(R.drawable.ic_arr_dropdown),
tint = colorScheme.onPrimary,
contentDescription = null
)
}
}
)
}
TTTextField( TPhoneField(
value = formState.phone, currentPattern = viewModel.chosenPattern.value,
onValueChange = viewModel::onPhoneChange, currentPhone = formState.phone,
label = "Телефон", onPhoneChange = viewModel::onPhoneChange,
keyboardOptions = KeyboardOptions( error = formState.errors[AuthField.Phone],
keyboardType = KeyboardType.Phone onOpenCountryList = {
), isSheetOpen.value = true
visualTransformation = viewModel.chosenPattern.value?.pattern?.let {
PhoneVisualTransformation(it, '0')
} ?: VisualTransformation.None,
error = formState.errors[AuthField.Phone]
)
} }
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(Paddings.large))
if (viewModel.madeChanges.collectAsState().value) {
BigButton( BigButton(
onClick = viewModel::submit, onClick = viewModel::submit,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
buttonText = "Сохранить", buttonText = "Сохранить",
isLoading = profileState is UIState.Loading isLoading = profileState is UIState.Loading
) )
Spacer(Modifier.height(15.dp)) Spacer(Modifier.height(Paddings.medium))
Button(
modifier = modifier
.fillMaxWidth()
.height(60.dp),
shape = Shapes.smallRoundedBox,
onClick = viewModel::reset,
colors = ButtonColors(
containerColor = colorScheme.secondaryContainer,
contentColor = colorScheme.onSecondaryContainer,
disabledContainerColor = colorScheme.secondaryContainer,
disabledContentColor = colorScheme.onSecondaryContainer
)
) {
Text(
text = "Отменить",
style = typography.titleMedium,
fontSize = 24.sp
)
}
Spacer(Modifier.height(Paddings.medium))
}
Button( Button(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@@ -250,34 +225,15 @@ fun ErrorCollectorScope.ProfileScreen(
) )
} }
} }
Spacer(modifier = Modifier.height(48.dp)) Spacer(modifier = Modifier.height(Paddings.large))
} }
if (isSheetOpen.value) { TPhoneCountryList(
ModalBottomSheet( isSheetOpen = isSheetOpen,
sheetState = sheetState, sheetState = sheetState,
onDismissRequest = { isSheetOpen.value = false }, patternList = viewModel.phoneNumberPatterns,
) { setPattern = {
Column( viewModel.chosenPattern.value = it
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(viewModel.phoneNumberPatterns) { pattern ->
Text(
text = pattern.name,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clickable {
viewModel.chosenPattern.value = pattern
viewModel.onPhoneChange(formState.phone)
isSheetOpen.value = false
} }
) )
} }
}
}
}
}
}
@@ -6,6 +6,8 @@ import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.net.Uri import android.net.Uri
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -28,8 +30,11 @@ import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
import com.prodhack.moscow2025.presentation.utils.convertNumberToPattern import com.prodhack.moscow2025.presentation.utils.convertNumberToPattern
import com.prodhack.moscow2025.presentation.utils.toByteArray import com.prodhack.moscow2025.presentation.utils.toByteArray
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
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.android.annotation.KoinViewModel
@@ -78,7 +83,7 @@ class ProfileScreenViewModel(
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase, private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
private val logOutUseCase: LogOutUseCase, private val logOutUseCase: LogOutUseCase,
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase, private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
private val galleryRepository: GalleryRepository galleryRepository: GalleryRepository
) : BaseViewModel() { ) : BaseViewModel() {
private val _formStateProfile = MutableStateFlow(ProfileState()) private val _formStateProfile = MutableStateFlow(ProfileState())
val formStateFillProfile: StateFlow<ProfileState> = _formStateProfile val formStateFillProfile: StateFlow<ProfileState> = _formStateProfile
@@ -89,6 +94,28 @@ class ProfileScreenViewModel(
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null) val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>() val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
private val realState = MutableStateFlow(_formStateProfile.value)
val madeChanges = _formStateProfile.combine(realState) { current, real ->
current.phone != real.phone ||
current.firstName != real.firstName ||
current.lastName != real.lastName ||
current.email != real.email
}.stateIn(viewModelScope, SharingStarted.Lazily, false)
fun reset() {
if (madeChanges.value) {
_formStateProfile.update {
it.copy(
email = realState.value.email,
phone = realState.value.phone,
firstName = realState.value.firstName,
lastName = realState.value.lastName,
)
}
}
}
fun onEmailChange(value: String) { fun onEmailChange(value: String) {
_formStateProfile.update { _formStateProfile.update {
it.copy( it.copy(
@@ -175,7 +202,9 @@ class ProfileScreenViewModel(
fun submit() { fun submit() {
viewModelScope.launch { viewModelScope.launch {
val pattern = chosenPattern.value
val validation = validateAuthFieldsUseCase.validateProfile( val validation = validateAuthFieldsUseCase.validateProfile(
chosenPattern = pattern?.mapToDomain(),
firstName = _formStateProfile.value.firstName, firstName = _formStateProfile.value.firstName,
lastName = _formStateProfile.value.lastName, lastName = _formStateProfile.value.lastName,
email = _formStateProfile.value.email, email = _formStateProfile.value.email,
@@ -184,15 +213,6 @@ class ProfileScreenViewModel(
val errors = validation.errors.toMutableMap() val errors = validation.errors.toMutableMap()
val pattern = chosenPattern.value
if (pattern == null) {
errors[AuthField.Phone] = "Выберите код страны"
} else {
val expectedDigits = pattern.pattern.count { it == '0' }
if (_formStateProfile.value.phone.length != expectedDigits) {
errors[AuthField.Phone] = "Номер должен содержать $expectedDigits цифр"
}
}
if (errors.isNotEmpty()) { if (errors.isNotEmpty()) {
_formStateProfile.update { it.copy(errors = errors) } _formStateProfile.update { it.copy(errors = errors) }
@@ -214,6 +234,7 @@ class ProfileScreenViewModel(
) )
) )
result.map { it.id }.collectRequest(_profileState) result.map { it.id }.collectRequest(_profileState)
update()
} }
} }
@@ -223,7 +244,7 @@ class ProfileScreenViewModel(
} }
} }
init { fun update() {
viewModelScope.launch { viewModelScope.launch {
loadPhonePatterns() loadPhonePatterns()
@@ -233,12 +254,8 @@ class ProfileScreenViewModel(
val selectedPattern = phoneNumberPatterns.firstOrNull { pattern -> val selectedPattern = phoneNumberPatterns.firstOrNull { pattern ->
val codeDigits = pattern.countryCode.filter { it.isDigit() } val codeDigits = pattern.countryCode.filter { it.isDigit() }
digits.startsWith(codeDigits) && digits.length >= codeDigits.length digits.startsWith(codeDigits) && digits.length >= codeDigits.length
} ?: phoneNumberPatterns.firstOrNull { } ?: getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
it.countryCodeISO.equals( ?: phoneNumberPatterns.firstOrNull()
getDefaultPhoneNumberPatternUseCase.execute()?.countryCodeISO,
ignoreCase = true
)
} ?: phoneNumberPatterns.firstOrNull()
selectedPattern?.let { chosenPattern.value = it } selectedPattern?.let { chosenPattern.value = it }
@@ -257,10 +274,15 @@ class ProfileScreenViewModel(
phone = digitsWithoutCode.take(maxDigits) phone = digitsWithoutCode.take(maxDigits)
) )
} }
realState.emit(_formStateProfile.value)
} }
} }
} }
init {
update()
}
private fun loadPhonePatterns() { private fun loadPhonePatterns() {
phoneNumberPatterns.clear() phoneNumberPatterns.clear()
phoneNumberPatterns.addAll( phoneNumberPatterns.addAll(