You've already forked RekomenciMobile
fix: fix phone field on profile screen, bottom bar beautify; feat: show buttons only after change, on profile edit
This commit is contained in:
+6
-1
@@ -25,6 +25,7 @@ data class ValidationResult(
|
||||
@Single
|
||||
class ValidateAuthFieldsUseCase {
|
||||
fun validateProfile(
|
||||
chosenPattern: PhoneNumberPattern?,
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
email: String,
|
||||
@@ -34,7 +35,11 @@ class ValidateAuthFieldsUseCase {
|
||||
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
||||
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
||||
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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,6 +4,8 @@ import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
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.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -59,7 +61,6 @@ fun TBottomNavigation(modifier: Modifier = Modifier, selectedPage: Int, onSelect
|
||||
}
|
||||
target?.let { (it - center).toDp() }
|
||||
}
|
||||
AnimatedVisibility(indicatorOffset != null) {
|
||||
indicatorOffset?.let {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -71,7 +72,6 @@ fun TBottomNavigation(modifier: Modifier = Modifier, selectedPage: Int, onSelect
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
|
||||
+158
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-87
@@ -44,7 +44,6 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.presentation.components.standart.BigButton
|
||||
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.theme.Paddings
|
||||
import com.prodhack.moscow2025.presentation.theme.Shapes
|
||||
@@ -165,70 +166,16 @@ fun ErrorCollectorScope.FillProfileScreen(
|
||||
error = formState.errors[AuthField.LastName],
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
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 = viewModel.chosenPattern.value?.prefix ?: "",
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
textStyle = TextStyle(
|
||||
color = colorScheme.onPrimary
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
TPhoneField(
|
||||
currentPattern = viewModel.currentPattern.value,
|
||||
currentPhone = formState.phone,
|
||||
onPhoneChange = viewModel::onPhoneChange,
|
||||
error = formState.errors[AuthField.Phone],
|
||||
onOpenCountryList =
|
||||
{
|
||||
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))
|
||||
BigButton(
|
||||
@@ -241,32 +188,12 @@ fun ErrorCollectorScope.FillProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (isSheetOpen.value) {
|
||||
ModalBottomSheet(
|
||||
TPhoneCountryList(
|
||||
isSheetOpen = isSheetOpen,
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = {
|
||||
isSheetOpen.value = false
|
||||
},
|
||||
) {
|
||||
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
|
||||
patternList = viewModel.phoneNumberPatterns,
|
||||
setPattern = {
|
||||
viewModel.currentPattern.value = it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.annotation.KoinViewModel
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
data class FillProfileFormState(
|
||||
val firstName: String = "",
|
||||
@@ -95,7 +94,7 @@ class FillProfileViewModel(
|
||||
}
|
||||
|
||||
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)
|
||||
_formStateFillProfile.update {
|
||||
it.copy(
|
||||
@@ -153,14 +152,14 @@ class FillProfileViewModel(
|
||||
}
|
||||
|
||||
|
||||
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
||||
val currentPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
||||
|
||||
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
|
||||
|
||||
|
||||
fun update() {
|
||||
// Load default pattern
|
||||
chosenPattern.value = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
||||
currentPattern.value = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
||||
|
||||
// Load all phone number patterns
|
||||
phoneNumberPatterns.clear()
|
||||
@@ -173,7 +172,7 @@ class FillProfileViewModel(
|
||||
firstName = _formStateFillProfile.value.firstName,
|
||||
lastName = _formStateFillProfile.value.lastName,
|
||||
phone = _formStateFillProfile.value.phone,
|
||||
chosenPattern = chosenPattern.value?.mapToDomain()
|
||||
chosenPattern = currentPattern.value?.mapToDomain()
|
||||
)
|
||||
|
||||
if (!validation.isValid) {
|
||||
@@ -187,7 +186,7 @@ class FillProfileViewModel(
|
||||
UpdateUserData(
|
||||
firstName = _formStateFillProfile.value.firstName,
|
||||
lastName = _formStateFillProfile.value.lastName,
|
||||
phone = chosenPattern.value?.mapToDomain()?.let { phoneNumberPattern ->
|
||||
phone = currentPattern.value?.mapToDomain()?.let { phoneNumberPattern ->
|
||||
convertNumberToPattern(
|
||||
phoneNumberPattern,
|
||||
_formStateFillProfile.value.phone
|
||||
|
||||
+46
-90
@@ -51,6 +51,8 @@ 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.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.theme.Paddings
|
||||
import com.prodhack.moscow2025.presentation.theme.Shapes
|
||||
@@ -118,31 +120,31 @@ fun ErrorCollectorScope.ProfileScreen(
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding()
|
||||
.padding(horizontal = 30.dp),
|
||||
.padding(horizontal = Paddings.large),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Spacer(Modifier.height(Paddings.large))
|
||||
Text(
|
||||
text = "Профиль",
|
||||
style = typography.titleLarge,
|
||||
fontSize = 40.sp
|
||||
)
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Spacer(Modifier.height(Paddings.large))
|
||||
TTTextField(
|
||||
value = formState.firstName,
|
||||
onValueChange = viewModel::onFirstNameChange,
|
||||
label = "Имя",
|
||||
error = formState.errors[AuthField.FirstName],
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = formState.lastName,
|
||||
onValueChange = viewModel::onLastNameChange,
|
||||
label = "Фамилия",
|
||||
error = formState.errors[AuthField.LastName],
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = formState.email,
|
||||
onValueChange = viewModel::onEmailChange,
|
||||
@@ -150,75 +152,48 @@ fun ErrorCollectorScope.ProfileScreen(
|
||||
error = formState.errors[AuthField.Email],
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
|
||||
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]
|
||||
)
|
||||
TPhoneField(
|
||||
currentPattern = viewModel.chosenPattern.value,
|
||||
currentPhone = formState.phone,
|
||||
onPhoneChange = viewModel::onPhoneChange,
|
||||
error = formState.errors[AuthField.Phone],
|
||||
onOpenCountryList = {
|
||||
isSheetOpen.value = true
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
if (viewModel.madeChanges.collectAsState().value) {
|
||||
BigButton(
|
||||
onClick = viewModel::submit,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
buttonText = "Сохранить",
|
||||
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(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -250,34 +225,15 @@ fun ErrorCollectorScope.ProfileScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
}
|
||||
|
||||
if (isSheetOpen.value) {
|
||||
ModalBottomSheet(
|
||||
TPhoneCountryList(
|
||||
isSheetOpen = isSheetOpen,
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = { isSheetOpen.value = false },
|
||||
) {
|
||||
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
|
||||
viewModel.onPhoneChange(formState.phone)
|
||||
isSheetOpen.value = false
|
||||
patternList = viewModel.phoneNumberPatterns,
|
||||
setPattern = {
|
||||
viewModel.chosenPattern.value = it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-18
@@ -6,6 +6,8 @@ import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.toByteArray
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.annotation.KoinViewModel
|
||||
@@ -78,8 +83,8 @@ class ProfileScreenViewModel(
|
||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
|
||||
private val logOutUseCase: LogOutUseCase,
|
||||
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
||||
private val galleryRepository: GalleryRepository
|
||||
): BaseViewModel() {
|
||||
galleryRepository: GalleryRepository
|
||||
) : BaseViewModel() {
|
||||
private val _formStateProfile = MutableStateFlow(ProfileState())
|
||||
val formStateFillProfile: StateFlow<ProfileState> = _formStateProfile
|
||||
|
||||
@@ -89,6 +94,28 @@ class ProfileScreenViewModel(
|
||||
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
||||
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) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
@@ -175,7 +202,9 @@ class ProfileScreenViewModel(
|
||||
|
||||
fun submit() {
|
||||
viewModelScope.launch {
|
||||
val pattern = chosenPattern.value
|
||||
val validation = validateAuthFieldsUseCase.validateProfile(
|
||||
chosenPattern = pattern?.mapToDomain(),
|
||||
firstName = _formStateProfile.value.firstName,
|
||||
lastName = _formStateProfile.value.lastName,
|
||||
email = _formStateProfile.value.email,
|
||||
@@ -184,15 +213,6 @@ class ProfileScreenViewModel(
|
||||
|
||||
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()) {
|
||||
_formStateProfile.update { it.copy(errors = errors) }
|
||||
@@ -214,6 +234,7 @@ class ProfileScreenViewModel(
|
||||
)
|
||||
)
|
||||
result.map { it.id }.collectRequest(_profileState)
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +244,7 @@ class ProfileScreenViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
fun update() {
|
||||
viewModelScope.launch {
|
||||
loadPhonePatterns()
|
||||
|
||||
@@ -233,12 +254,8 @@ class ProfileScreenViewModel(
|
||||
val selectedPattern = phoneNumberPatterns.firstOrNull { pattern ->
|
||||
val codeDigits = pattern.countryCode.filter { it.isDigit() }
|
||||
digits.startsWith(codeDigits) && digits.length >= codeDigits.length
|
||||
} ?: phoneNumberPatterns.firstOrNull {
|
||||
it.countryCodeISO.equals(
|
||||
getDefaultPhoneNumberPatternUseCase.execute()?.countryCodeISO,
|
||||
ignoreCase = true
|
||||
)
|
||||
} ?: phoneNumberPatterns.firstOrNull()
|
||||
} ?: getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
||||
?: phoneNumberPatterns.firstOrNull()
|
||||
|
||||
selectedPattern?.let { chosenPattern.value = it }
|
||||
|
||||
@@ -257,10 +274,15 @@ class ProfileScreenViewModel(
|
||||
phone = digitsWithoutCode.take(maxDigits)
|
||||
)
|
||||
}
|
||||
realState.emit(_formStateProfile.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
update()
|
||||
}
|
||||
|
||||
private fun loadPhonePatterns() {
|
||||
phoneNumberPatterns.clear()
|
||||
phoneNumberPatterns.addAll(
|
||||
|
||||
Reference in New Issue
Block a user