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
|
@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)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -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,18 +61,16 @@ 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
|
.size(85.dp, 45.dp)
|
||||||
.size(85.dp, 45.dp)
|
.offset(x = animateDpAsState(it).value)
|
||||||
.offset(x = animateDpAsState(it).value)
|
.background(
|
||||||
.background(
|
MaterialTheme.colorScheme.primary,
|
||||||
MaterialTheme.colorScheme.primary,
|
shape = Shapes.smallRoundedBox
|
||||||
shape = Shapes.smallRoundedBox
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
Icon(
|
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.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 =
|
||||||
)
|
{
|
||||||
) {
|
isSheetOpen.value = true
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
+5
-6
@@ -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
|
||||||
|
|||||||
+116
-160
@@ -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
|
||||||
@@ -66,7 +68,7 @@ import org.koin.androidx.compose.koinViewModel
|
|||||||
fun ErrorCollectorScope.ProfileScreen(
|
fun ErrorCollectorScope.ProfileScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
navigateToLoginScreen: () -> Unit,
|
navigateToLoginScreen: () -> Unit,
|
||||||
viewModel: ProfileScreenViewModel = koinViewModel()
|
viewModel: ProfileScreenViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val typography = androidx.compose.material3.MaterialTheme.typography
|
val typography = androidx.compose.material3.MaterialTheme.typography
|
||||||
@@ -113,171 +115,125 @@ fun ErrorCollectorScope.ProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.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,
|
||||||
label = "Email",
|
label = "Email",
|
||||||
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),
|
TPhoneField(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
currentPattern = viewModel.chosenPattern.value,
|
||||||
horizontalArrangement = Arrangement.spacedBy(Paddings.medium)
|
currentPhone = formState.phone,
|
||||||
) {
|
onPhoneChange = viewModel::onPhoneChange,
|
||||||
FieldWrapper(
|
error = formState.errors[AuthField.Phone],
|
||||||
modifier = Modifier
|
onOpenCountryList = {
|
||||||
.width(IntrinsicSize.Min)
|
isSheetOpen.value = true
|
||||||
.fillMaxHeight()
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
Text(
|
||||||
modifier = Modifier
|
text = "Отменить",
|
||||||
.fillMaxSize()
|
style = typography.titleMedium,
|
||||||
.padding(bottom = 16.dp)
|
fontSize = 24.sp
|
||||||
.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]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
Button(
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
modifier = modifier
|
||||||
BigButton(
|
.fillMaxWidth()
|
||||||
onClick = viewModel::submit,
|
.height(60.dp),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
shape = Shapes.smallRoundedBox,
|
||||||
buttonText = "Сохранить",
|
onClick = {
|
||||||
isLoading = profileState is UIState.Loading
|
viewModel.logout()
|
||||||
)
|
navigateToLoginScreen()
|
||||||
Spacer(Modifier.height(15.dp))
|
},
|
||||||
Button(
|
colors = ButtonColors(
|
||||||
modifier = modifier
|
containerColor = colorScheme.errorContainer,
|
||||||
.fillMaxWidth()
|
contentColor = colorScheme.onErrorContainer,
|
||||||
.height(60.dp),
|
disabledContainerColor = colorScheme.errorContainer,
|
||||||
shape = Shapes.smallRoundedBox,
|
disabledContentColor = colorScheme.onErrorContainer
|
||||||
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 },
|
|
||||||
) {
|
) {
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
verticalArrangement = Arrangement.Center
|
Text(
|
||||||
) {
|
text = "Выйти из аккаунта",
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
style = typography.titleMedium,
|
||||||
items(viewModel.phoneNumberPatterns) { pattern ->
|
fontSize = 24.sp
|
||||||
Text(
|
)
|
||||||
text = pattern.name,
|
Spacer(Modifier.width(Paddings.small))
|
||||||
modifier = Modifier
|
Icon(
|
||||||
.fillMaxWidth()
|
painter = painterResource(R.drawable.logout_icon),
|
||||||
.padding(10.dp)
|
contentDescription = null,
|
||||||
.clickable {
|
modifier = Modifier.size(24.dp)
|
||||||
viewModel.chosenPattern.value = pattern
|
)
|
||||||
viewModel.onPhoneChange(formState.phone)
|
|
||||||
isSheetOpen.value = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.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,247 +30,267 @@ 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
|
||||||
|
|
||||||
data class ProfileState(
|
data class ProfileState(
|
||||||
val email: String = "",
|
val email: String = "",
|
||||||
val firstName: String = "",
|
val firstName: String = "",
|
||||||
val lastName: String = "",
|
val lastName: String = "",
|
||||||
val phone: String = "",
|
val phone: String = "",
|
||||||
val avatar: ByteArray? = null,
|
val avatar: ByteArray? = null,
|
||||||
val errors: Map<AuthField, String> = emptyMap()
|
val errors: Map<AuthField, String> = emptyMap()
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
other as ProfileState
|
other as ProfileState
|
||||||
|
|
||||||
if (email != other.email) return false
|
if (email != other.email) return false
|
||||||
if (firstName != other.firstName) return false
|
if (firstName != other.firstName) return false
|
||||||
if (lastName != other.lastName) return false
|
if (lastName != other.lastName) return false
|
||||||
if (phone != other.phone) return false
|
if (phone != other.phone) return false
|
||||||
if (!avatar.contentEquals(other.avatar)) return false
|
if (!avatar.contentEquals(other.avatar)) return false
|
||||||
if (errors != other.errors) return false
|
if (errors != other.errors) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = email.hashCode()
|
var result = email.hashCode()
|
||||||
result = 31 * result + firstName.hashCode()
|
result = 31 * result + firstName.hashCode()
|
||||||
result = 31 * result + lastName.hashCode()
|
result = 31 * result + lastName.hashCode()
|
||||||
result = 31 * result + phone.hashCode()
|
result = 31 * result + phone.hashCode()
|
||||||
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
result = 31 * result + (avatar?.contentHashCode() ?: 0)
|
||||||
result = 31 * result + errors.hashCode()
|
result = 31 * result + errors.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@KoinViewModel
|
@KoinViewModel
|
||||||
class ProfileScreenViewModel(
|
class ProfileScreenViewModel(
|
||||||
private val getUserUseCase: GetUserUseCase,
|
private val getUserUseCase: GetUserUseCase,
|
||||||
private val updateUserUseCase: UpdateUserUseCase,
|
private val updateUserUseCase: UpdateUserUseCase,
|
||||||
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
|
||||||
|
|
||||||
private val _profileState = MutableUIStateFlow<String>()
|
private val _profileState = MutableUIStateFlow<String>()
|
||||||
val profileState: StateFlow<UIState<String>> = _profileState
|
val profileState: StateFlow<UIState<String>> = _profileState
|
||||||
|
|
||||||
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
|
||||||
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
|
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
|
||||||
|
|
||||||
fun onEmailChange(value: String) {
|
private val realState = MutableStateFlow(_formStateProfile.value)
|
||||||
_formStateProfile.update {
|
|
||||||
it.copy(
|
|
||||||
email = value,
|
|
||||||
errors = it.errors - AuthField.Email
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onFirstNameChange(value: String) {
|
val madeChanges = _formStateProfile.combine(realState) { current, real ->
|
||||||
_formStateProfile.update {
|
current.phone != real.phone ||
|
||||||
it.copy(
|
current.firstName != real.firstName ||
|
||||||
firstName = value,
|
current.lastName != real.lastName ||
|
||||||
errors = it.errors - AuthField.FirstName
|
current.email != real.email
|
||||||
)
|
}.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onLastNameChange(value: String) {
|
fun reset() {
|
||||||
_formStateProfile.update {
|
if (madeChanges.value) {
|
||||||
it.copy(
|
_formStateProfile.update {
|
||||||
lastName = value,
|
it.copy(
|
||||||
errors = it.errors - AuthField.LastName
|
email = realState.value.email,
|
||||||
)
|
phone = realState.value.phone,
|
||||||
}
|
firstName = realState.value.firstName,
|
||||||
}
|
lastName = realState.value.lastName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onPhoneChange(value: String) {
|
fun onEmailChange(value: String) {
|
||||||
val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
_formStateProfile.update {
|
||||||
val digits = value.filter { it.isDigit() }.take(maxDigits)
|
it.copy(
|
||||||
_formStateProfile.update {
|
email = value,
|
||||||
it.copy(
|
errors = it.errors - AuthField.Email
|
||||||
phone = digits,
|
)
|
||||||
errors = it.errors - AuthField.Phone
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val galleryItems = galleryRepository.getImagesIds().map {
|
fun onFirstNameChange(value: String) {
|
||||||
it.map { id ->
|
_formStateProfile.update {
|
||||||
ContentUris.withAppendedId(
|
it.copy(
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
firstName = value,
|
||||||
id
|
errors = it.errors - AuthField.FirstName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun post(context: Context) {
|
fun onLastNameChange(value: String) {
|
||||||
viewModelScope.launch {
|
_formStateProfile.update {
|
||||||
post(
|
it.copy(
|
||||||
(ImageLoader(context).execute(
|
lastName = value,
|
||||||
ImageRequest.Builder(context)
|
errors = it.errors - AuthField.LastName
|
||||||
.data(currentPhoto).build()
|
)
|
||||||
).drawable as BitmapDrawable).bitmap
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun post(bitmap: Bitmap) {
|
fun onPhoneChange(value: String) {
|
||||||
viewModelScope.launch {
|
val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
||||||
_formStateProfile.update {
|
val digits = value.filter { it.isDigit() }.take(maxDigits)
|
||||||
it.copy(
|
_formStateProfile.update {
|
||||||
avatar = bitmap.toByteArray()
|
it.copy(
|
||||||
)
|
phone = digits,
|
||||||
}
|
errors = it.errors - AuthField.Phone
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun clearAvatar() {
|
val galleryItems = galleryRepository.getImagesIds().map {
|
||||||
viewModelScope.launch {
|
it.map { id ->
|
||||||
_formStateProfile.update {
|
ContentUris.withAppendedId(
|
||||||
it.copy(
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||||
avatar = null
|
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) {
|
fun post(bitmap: Bitmap) {
|
||||||
currentPhoto = photo
|
viewModelScope.launch {
|
||||||
}
|
_formStateProfile.update {
|
||||||
|
it.copy(
|
||||||
|
avatar = bitmap.toByteArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun submit() {
|
fun clearAvatar() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val validation = validateAuthFieldsUseCase.validateProfile(
|
_formStateProfile.update {
|
||||||
firstName = _formStateProfile.value.firstName,
|
it.copy(
|
||||||
lastName = _formStateProfile.value.lastName,
|
avatar = null
|
||||||
email = _formStateProfile.value.email,
|
)
|
||||||
phone = _formStateProfile.value.phone
|
}
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val errors = validation.errors.toMutableMap()
|
var currentPhoto: Uri? = null
|
||||||
|
|
||||||
val pattern = chosenPattern.value
|
fun selectImage(photo: Uri) {
|
||||||
if (pattern == null) {
|
currentPhoto = photo
|
||||||
errors[AuthField.Phone] = "Выберите код страны"
|
}
|
||||||
} else {
|
|
||||||
val expectedDigits = pattern.pattern.count { it == '0' }
|
|
||||||
if (_formStateProfile.value.phone.length != expectedDigits) {
|
|
||||||
errors[AuthField.Phone] = "Номер должен содержать $expectedDigits цифр"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.isNotEmpty()) {
|
fun submit() {
|
||||||
_formStateProfile.update { it.copy(errors = errors) }
|
viewModelScope.launch {
|
||||||
return@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(
|
if (errors.isNotEmpty()) {
|
||||||
UpdateUserData(
|
_formStateProfile.update { it.copy(errors = errors) }
|
||||||
firstName = _formStateProfile.value.firstName,
|
return@launch
|
||||||
lastName = _formStateProfile.value.lastName,
|
}
|
||||||
email = _formStateProfile.value.email,
|
|
||||||
phone = formattedPhone
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result.map { it.id }.collectRequest(_profileState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logout() {
|
_profileState.emit(UIState.Loading())
|
||||||
viewModelScope.launch {
|
|
||||||
logOutUseCase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
val formattedPhone = pattern?.mapToDomain()?.let { phonePattern ->
|
||||||
viewModelScope.launch {
|
convertNumberToPattern(phonePattern, _formStateProfile.value.phone)
|
||||||
loadPhonePatterns()
|
} ?: _formStateProfile.value.phone
|
||||||
|
|
||||||
val user = getUserUseCase().getOrNull()
|
val result = updateUserUseCase(
|
||||||
if (user != null) {
|
UpdateUserData(
|
||||||
val digits = user.phone.orEmpty().filter { it.isDigit() }
|
firstName = _formStateProfile.value.firstName,
|
||||||
val selectedPattern = phoneNumberPatterns.firstOrNull { pattern ->
|
lastName = _formStateProfile.value.lastName,
|
||||||
val codeDigits = pattern.countryCode.filter { it.isDigit() }
|
email = _formStateProfile.value.email,
|
||||||
digits.startsWith(codeDigits) && digits.length >= codeDigits.length
|
phone = formattedPhone
|
||||||
} ?: phoneNumberPatterns.firstOrNull {
|
)
|
||||||
it.countryCodeISO.equals(
|
)
|
||||||
getDefaultPhoneNumberPatternUseCase.execute()?.countryCodeISO,
|
result.map { it.id }.collectRequest(_profileState)
|
||||||
ignoreCase = true
|
update()
|
||||||
)
|
}
|
||||||
} ?: phoneNumberPatterns.firstOrNull()
|
}
|
||||||
|
|
||||||
selectedPattern?.let { chosenPattern.value = it }
|
fun logout() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
logOutUseCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val digitsWithoutCode = selectedPattern?.let {
|
fun update() {
|
||||||
val codeDigits = it.countryCode.filter { d -> d.isDigit() }
|
viewModelScope.launch {
|
||||||
if (digits.startsWith(codeDigits)) digits.drop(codeDigits.length) else digits
|
loadPhonePatterns()
|
||||||
} ?: digits
|
|
||||||
|
|
||||||
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 {
|
selectedPattern?.let { chosenPattern.value = it }
|
||||||
it.copy(
|
|
||||||
firstName = user.firstName.orEmpty(),
|
|
||||||
lastName = user.lastName.orEmpty(),
|
|
||||||
email = user.email,
|
|
||||||
phone = digitsWithoutCode.take(maxDigits)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadPhonePatterns() {
|
val digitsWithoutCode = selectedPattern?.let {
|
||||||
phoneNumberPatterns.clear()
|
val codeDigits = it.countryCode.filter { d -> d.isDigit() }
|
||||||
phoneNumberPatterns.addAll(
|
if (digits.startsWith(codeDigits)) digits.drop(codeDigits.length) else digits
|
||||||
PhoneNumberPatternsProvider.phoneNumberPatterns.map { it.mapToUI() }
|
} ?: digits
|
||||||
)
|
|
||||||
if (chosenPattern.value == null) {
|
val maxDigits = selectedPattern?.pattern?.count { it == '0' } ?: Int.MAX_VALUE
|
||||||
val defaultPattern = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI()
|
|
||||||
chosenPattern.value = defaultPattern ?: phoneNumberPatterns.firstOrNull()
|
_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