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)
|
||||
}
|
||||
|
||||
+12
-12
@@ -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,18 +61,16 @@ fun TBottomNavigation(modifier: Modifier = Modifier, selectedPage: Int, onSelect
|
||||
}
|
||||
target?.let { (it - center).toDp() }
|
||||
}
|
||||
AnimatedVisibility(indicatorOffset != null) {
|
||||
indicatorOffset?.let {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(85.dp, 45.dp)
|
||||
.offset(x = animateDpAsState(it).value)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.primary,
|
||||
shape = Shapes.smallRoundedBox
|
||||
)
|
||||
)
|
||||
}
|
||||
indicatorOffset?.let {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(85.dp, 45.dp)
|
||||
.offset(x = animateDpAsState(it).value)
|
||||
.background(
|
||||
MaterialTheme.colorScheme.primary,
|
||||
shape = Shapes.smallRoundedBox
|
||||
)
|
||||
)
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
Icon(
|
||||
|
||||
+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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-92
@@ -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 {
|
||||
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())
|
||||
}
|
||||
TPhoneField(
|
||||
currentPattern = viewModel.currentPattern.value,
|
||||
currentPhone = formState.phone,
|
||||
onPhoneChange = viewModel::onPhoneChange,
|
||||
error = formState.errors[AuthField.Phone],
|
||||
onOpenCountryList =
|
||||
{
|
||||
isSheetOpen.value = true
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
BigButton(
|
||||
@@ -241,32 +188,12 @@ fun ErrorCollectorScope.FillProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (isSheetOpen.value) {
|
||||
ModalBottomSheet(
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
TPhoneCountryList(
|
||||
isSheetOpen = isSheetOpen,
|
||||
sheetState = sheetState,
|
||||
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
|
||||
|
||||
+116
-160
@@ -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
|
||||
@@ -66,7 +68,7 @@ import org.koin.androidx.compose.koinViewModel
|
||||
fun ErrorCollectorScope.ProfileScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
navigateToLoginScreen: () -> Unit,
|
||||
navigateToLoginScreen: () -> Unit,
|
||||
viewModel: ProfileScreenViewModel = koinViewModel()
|
||||
) {
|
||||
val typography = androidx.compose.material3.MaterialTheme.typography
|
||||
@@ -113,171 +115,125 @@ fun ErrorCollectorScope.ProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding()
|
||||
.padding(horizontal = 30.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Text(
|
||||
text = "Профиль",
|
||||
style = typography.titleLarge,
|
||||
fontSize = 40.sp
|
||||
)
|
||||
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.email,
|
||||
onValueChange = viewModel::onEmailChange,
|
||||
label = "Email",
|
||||
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()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.systemBarsPadding()
|
||||
.padding(horizontal = Paddings.large),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Spacer(Modifier.height(Paddings.large))
|
||||
Text(
|
||||
text = "Профиль",
|
||||
style = typography.titleLarge,
|
||||
fontSize = 40.sp
|
||||
)
|
||||
Spacer(Modifier.height(Paddings.large))
|
||||
TTTextField(
|
||||
value = formState.firstName,
|
||||
onValueChange = viewModel::onFirstNameChange,
|
||||
label = "Имя",
|
||||
error = formState.errors[AuthField.FirstName],
|
||||
)
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = formState.lastName,
|
||||
onValueChange = viewModel::onLastNameChange,
|
||||
label = "Фамилия",
|
||||
error = formState.errors[AuthField.LastName],
|
||||
)
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = formState.email,
|
||||
onValueChange = viewModel::onEmailChange,
|
||||
label = "Email",
|
||||
error = formState.errors[AuthField.Email],
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
|
||||
)
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
|
||||
TPhoneField(
|
||||
currentPattern = viewModel.chosenPattern.value,
|
||||
currentPhone = formState.phone,
|
||||
onPhoneChange = viewModel::onPhoneChange,
|
||||
error = formState.errors[AuthField.Phone],
|
||||
onOpenCountryList = {
|
||||
isSheetOpen.value = true
|
||||
}
|
||||
)
|
||||
|
||||
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(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
|
||||
)
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "Отменить",
|
||||
style = typography.titleMedium,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
}
|
||||
|
||||
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]
|
||||
)
|
||||
Spacer(Modifier.height(Paddings.medium))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
BigButton(
|
||||
onClick = viewModel::submit,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
buttonText = "Сохранить",
|
||||
isLoading = profileState is UIState.Loading
|
||||
)
|
||||
Spacer(Modifier.height(15.dp))
|
||||
Button(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp),
|
||||
shape = Shapes.smallRoundedBox,
|
||||
onClick = {
|
||||
viewModel.logout()
|
||||
navigateToLoginScreen()
|
||||
},
|
||||
colors = ButtonColors(
|
||||
containerColor = colorScheme.errorContainer,
|
||||
contentColor = colorScheme.onErrorContainer,
|
||||
disabledContainerColor = colorScheme.errorContainer,
|
||||
disabledContentColor = colorScheme.onErrorContainer
|
||||
)
|
||||
) {
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "Выйти из аккаунта",
|
||||
style = typography.titleMedium,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
Spacer(Modifier.width(Paddings.small))
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.logout_icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
}
|
||||
|
||||
if (isSheetOpen.value) {
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = { isSheetOpen.value = false },
|
||||
Button(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp),
|
||||
shape = Shapes.smallRoundedBox,
|
||||
onClick = {
|
||||
viewModel.logout()
|
||||
navigateToLoginScreen()
|
||||
},
|
||||
colors = ButtonColors(
|
||||
containerColor = colorScheme.errorContainer,
|
||||
contentColor = colorScheme.onErrorContainer,
|
||||
disabledContainerColor = colorScheme.errorContainer,
|
||||
disabledContentColor = colorScheme.onErrorContainer
|
||||
)
|
||||
) {
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "Выйти из аккаунта",
|
||||
style = typography.titleMedium,
|
||||
fontSize = 24.sp
|
||||
)
|
||||
Spacer(Modifier.width(Paddings.small))
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.logout_icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
}
|
||||
|
||||
TPhoneCountryList(
|
||||
isSheetOpen = isSheetOpen,
|
||||
sheetState = sheetState,
|
||||
patternList = viewModel.phoneNumberPatterns,
|
||||
setPattern = {
|
||||
viewModel.chosenPattern.value = it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+220
-198
@@ -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,247 +30,267 @@ 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
|
||||
|
||||
data class ProfileState(
|
||||
val email: String = "",
|
||||
val firstName: String = "",
|
||||
val lastName: String = "",
|
||||
val phone: String = "",
|
||||
val avatar: ByteArray? = null,
|
||||
val errors: Map<AuthField, String> = emptyMap()
|
||||
val email: String = "",
|
||||
val firstName: String = "",
|
||||
val lastName: String = "",
|
||||
val phone: String = "",
|
||||
val avatar: ByteArray? = null,
|
||||
val errors: Map<AuthField, String> = emptyMap()
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ProfileState
|
||||
other as ProfileState
|
||||
|
||||
if (email != other.email) return false
|
||||
if (firstName != other.firstName) return false
|
||||
if (lastName != other.lastName) return false
|
||||
if (phone != other.phone) return false
|
||||
if (!avatar.contentEquals(other.avatar)) return false
|
||||
if (errors != other.errors) return false
|
||||
if (email != other.email) return false
|
||||
if (firstName != other.firstName) return false
|
||||
if (lastName != other.lastName) return false
|
||||
if (phone != other.phone) return false
|
||||
if (!avatar.contentEquals(other.avatar)) return false
|
||||
if (errors != other.errors) return false
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = email.hashCode()
|
||||
result = 31 * result + firstName.hashCode()
|
||||
result = 31 * result + lastName.hashCode()
|
||||
result = 31 * result + phone.hashCode()
|
||||
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
||||
result = 31 * result + errors.hashCode()
|
||||
return result
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
var result = email.hashCode()
|
||||
result = 31 * result + firstName.hashCode()
|
||||
result = 31 * result + lastName.hashCode()
|
||||
result = 31 * result + phone.hashCode()
|
||||
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
||||
result = 31 * result + errors.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@KoinViewModel
|
||||
class ProfileScreenViewModel(
|
||||
private val getUserUseCase: GetUserUseCase,
|
||||
private val updateUserUseCase: UpdateUserUseCase,
|
||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
|
||||
private val logOutUseCase: LogOutUseCase,
|
||||
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
||||
private val galleryRepository: GalleryRepository
|
||||
): BaseViewModel() {
|
||||
private val _formStateProfile = MutableStateFlow(ProfileState())
|
||||
val formStateFillProfile: StateFlow<ProfileState> = _formStateProfile
|
||||
private val getUserUseCase: GetUserUseCase,
|
||||
private val updateUserUseCase: UpdateUserUseCase,
|
||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
|
||||
private val logOutUseCase: LogOutUseCase,
|
||||
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
||||
galleryRepository: GalleryRepository
|
||||
) : BaseViewModel() {
|
||||
private val _formStateProfile = MutableStateFlow(ProfileState())
|
||||
val formStateFillProfile: StateFlow<ProfileState> = _formStateProfile
|
||||
|
||||
private val _profileState = MutableUIStateFlow<String>()
|
||||
val profileState: StateFlow<UIState<String>> = _profileState
|
||||
private val _profileState = MutableUIStateFlow<String>()
|
||||
val profileState: StateFlow<UIState<String>> = _profileState
|
||||
|
||||
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
||||
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
|
||||
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
||||
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
|
||||
|
||||
fun onEmailChange(value: String) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
email = value,
|
||||
errors = it.errors - AuthField.Email
|
||||
)
|
||||
}
|
||||
}
|
||||
private val realState = MutableStateFlow(_formStateProfile.value)
|
||||
|
||||
fun onFirstNameChange(value: String) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
firstName = value,
|
||||
errors = it.errors - AuthField.FirstName
|
||||
)
|
||||
}
|
||||
}
|
||||
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 onLastNameChange(value: String) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
lastName = value,
|
||||
errors = it.errors - AuthField.LastName
|
||||
)
|
||||
}
|
||||
}
|
||||
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 onPhoneChange(value: String) {
|
||||
val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
||||
val digits = value.filter { it.isDigit() }.take(maxDigits)
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
phone = digits,
|
||||
errors = it.errors - AuthField.Phone
|
||||
)
|
||||
}
|
||||
}
|
||||
fun onEmailChange(value: String) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
email = value,
|
||||
errors = it.errors - AuthField.Email
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val galleryItems = galleryRepository.getImagesIds().map {
|
||||
it.map { id ->
|
||||
ContentUris.withAppendedId(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
id
|
||||
)
|
||||
}
|
||||
}
|
||||
fun onFirstNameChange(value: String) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
firstName = value,
|
||||
errors = it.errors - AuthField.FirstName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun post(context: Context) {
|
||||
viewModelScope.launch {
|
||||
post(
|
||||
(ImageLoader(context).execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(currentPhoto).build()
|
||||
).drawable as BitmapDrawable).bitmap
|
||||
)
|
||||
}
|
||||
}
|
||||
fun onLastNameChange(value: String) {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
lastName = value,
|
||||
errors = it.errors - AuthField.LastName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun post(bitmap: Bitmap) {
|
||||
viewModelScope.launch {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
avatar = bitmap.toByteArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onPhoneChange(value: String) {
|
||||
val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
||||
val digits = value.filter { it.isDigit() }.take(maxDigits)
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
phone = digits,
|
||||
errors = it.errors - AuthField.Phone
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAvatar() {
|
||||
viewModelScope.launch {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
avatar = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val galleryItems = galleryRepository.getImagesIds().map {
|
||||
it.map { id ->
|
||||
ContentUris.withAppendedId(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var currentPhoto: Uri? = null
|
||||
fun post(context: Context) {
|
||||
viewModelScope.launch {
|
||||
post(
|
||||
(ImageLoader(context).execute(
|
||||
ImageRequest.Builder(context)
|
||||
.data(currentPhoto).build()
|
||||
).drawable as BitmapDrawable).bitmap
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun selectImage(photo: Uri) {
|
||||
currentPhoto = photo
|
||||
}
|
||||
fun post(bitmap: Bitmap) {
|
||||
viewModelScope.launch {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
avatar = bitmap.toByteArray()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
viewModelScope.launch {
|
||||
val validation = validateAuthFieldsUseCase.validateProfile(
|
||||
firstName = _formStateProfile.value.firstName,
|
||||
lastName = _formStateProfile.value.lastName,
|
||||
email = _formStateProfile.value.email,
|
||||
phone = _formStateProfile.value.phone
|
||||
)
|
||||
fun clearAvatar() {
|
||||
viewModelScope.launch {
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
avatar = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val errors = validation.errors.toMutableMap()
|
||||
var currentPhoto: Uri? = null
|
||||
|
||||
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 цифр"
|
||||
}
|
||||
}
|
||||
fun selectImage(photo: Uri) {
|
||||
currentPhoto = photo
|
||||
}
|
||||
|
||||
if (errors.isNotEmpty()) {
|
||||
_formStateProfile.update { it.copy(errors = errors) }
|
||||
return@launch
|
||||
}
|
||||
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,
|
||||
phone = _formStateProfile.value.phone
|
||||
)
|
||||
|
||||
_profileState.emit(UIState.Loading())
|
||||
val errors = validation.errors.toMutableMap()
|
||||
|
||||
val formattedPhone = pattern?.mapToDomain()?.let { phonePattern ->
|
||||
convertNumberToPattern(phonePattern, _formStateProfile.value.phone)
|
||||
} ?: _formStateProfile.value.phone
|
||||
|
||||
val result = updateUserUseCase(
|
||||
UpdateUserData(
|
||||
firstName = _formStateProfile.value.firstName,
|
||||
lastName = _formStateProfile.value.lastName,
|
||||
email = _formStateProfile.value.email,
|
||||
phone = formattedPhone
|
||||
)
|
||||
)
|
||||
result.map { it.id }.collectRequest(_profileState)
|
||||
}
|
||||
}
|
||||
if (errors.isNotEmpty()) {
|
||||
_formStateProfile.update { it.copy(errors = errors) }
|
||||
return@launch
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
viewModelScope.launch {
|
||||
logOutUseCase()
|
||||
}
|
||||
}
|
||||
_profileState.emit(UIState.Loading())
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
loadPhonePatterns()
|
||||
val formattedPhone = pattern?.mapToDomain()?.let { phonePattern ->
|
||||
convertNumberToPattern(phonePattern, _formStateProfile.value.phone)
|
||||
} ?: _formStateProfile.value.phone
|
||||
|
||||
val user = getUserUseCase().getOrNull()
|
||||
if (user != null) {
|
||||
val digits = user.phone.orEmpty().filter { it.isDigit() }
|
||||
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()
|
||||
val result = updateUserUseCase(
|
||||
UpdateUserData(
|
||||
firstName = _formStateProfile.value.firstName,
|
||||
lastName = _formStateProfile.value.lastName,
|
||||
email = _formStateProfile.value.email,
|
||||
phone = formattedPhone
|
||||
)
|
||||
)
|
||||
result.map { it.id }.collectRequest(_profileState)
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
selectedPattern?.let { chosenPattern.value = it }
|
||||
fun logout() {
|
||||
viewModelScope.launch {
|
||||
logOutUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
val digitsWithoutCode = selectedPattern?.let {
|
||||
val codeDigits = it.countryCode.filter { d -> d.isDigit() }
|
||||
if (digits.startsWith(codeDigits)) digits.drop(codeDigits.length) else digits
|
||||
} ?: digits
|
||||
fun update() {
|
||||
viewModelScope.launch {
|
||||
loadPhonePatterns()
|
||||
|
||||
val maxDigits = selectedPattern?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
||||
val user = getUserUseCase().getOrNull()
|
||||
if (user != null) {
|
||||
val digits = user.phone.orEmpty().filter { it.isDigit() }
|
||||
val selectedPattern = phoneNumberPatterns.firstOrNull { pattern ->
|
||||
val codeDigits = pattern.countryCode.filter { it.isDigit() }
|
||||
digits.startsWith(codeDigits) && digits.length >= codeDigits.length
|
||||
} ?: getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
||||
?: phoneNumberPatterns.firstOrNull()
|
||||
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
firstName = user.firstName.orEmpty(),
|
||||
lastName = user.lastName.orEmpty(),
|
||||
email = user.email,
|
||||
phone = digitsWithoutCode.take(maxDigits)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedPattern?.let { chosenPattern.value = it }
|
||||
|
||||
private fun loadPhonePatterns() {
|
||||
phoneNumberPatterns.clear()
|
||||
phoneNumberPatterns.addAll(
|
||||
PhoneNumberPatternsProvider.phoneNumberPatterns.map { it.mapToUI() }
|
||||
)
|
||||
if (chosenPattern.value == null) {
|
||||
val defaultPattern = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
||||
chosenPattern.value = defaultPattern ?: phoneNumberPatterns.firstOrNull()
|
||||
}
|
||||
}
|
||||
val digitsWithoutCode = selectedPattern?.let {
|
||||
val codeDigits = it.countryCode.filter { d -> d.isDigit() }
|
||||
if (digits.startsWith(codeDigits)) digits.drop(codeDigits.length) else digits
|
||||
} ?: digits
|
||||
|
||||
val maxDigits = selectedPattern?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
||||
|
||||
_formStateProfile.update {
|
||||
it.copy(
|
||||
firstName = user.firstName.orEmpty(),
|
||||
lastName = user.lastName.orEmpty(),
|
||||
email = user.email,
|
||||
phone = digitsWithoutCode.take(maxDigits)
|
||||
)
|
||||
}
|
||||
realState.emit(_formStateProfile.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
update()
|
||||
}
|
||||
|
||||
private fun loadPhonePatterns() {
|
||||
phoneNumberPatterns.clear()
|
||||
phoneNumberPatterns.addAll(
|
||||
PhoneNumberPatternsProvider.phoneNumberPatterns.map { it.mapToUI() }
|
||||
)
|
||||
if (chosenPattern.value == null) {
|
||||
val defaultPattern = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
||||
chosenPattern.value = defaultPattern ?: phoneNumberPatterns.firstOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user