diff --git a/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt b/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt index 636daeb..bb6ab1b 100644 --- a/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt +++ b/app/src/main/java/com/prodhack/moscow2025/domain/usecase/auth/ValidateAuthFieldsUseCase.kt @@ -24,6 +24,20 @@ data class ValidationResult( @Single class ValidateAuthFieldsUseCase { + fun validateProfile( + firstName: String, + lastName: String, + email: String, + phone: String + ): ValidationResult { + val errors = buildMap { + 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, "Некорректный номер телефона") + } + return ValidationResult(errors) + } fun validateFillProfile( chosenPattern: PhoneNumberPattern?, diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt index 21ae1ed..0201697 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt @@ -2,8 +2,13 @@ package com.prodhack.moscow2025.presentation.screens.profile import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel +import org.koin.androidx.compose.koinViewModel @Composable -fun ProfileScreen(modifier: Modifier = Modifier) { +fun ProfileScreen( + viewModel: ProfileScreenViewModel = koinViewModel() +) : BaseViewModel { + } \ No newline at end of file diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt new file mode 100644 index 0000000..60669ed --- /dev/null +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt @@ -0,0 +1,204 @@ +package com.prodhack.moscow2025.presentation.screens.profile + +import android.content.ContentUris +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.provider.MediaStore +import androidx.lifecycle.viewModelScope +import androidx.paging.map +import coil.ImageLoader +import coil.request.ImageRequest +import com.prodhack.moscow2025.domain.interfaces.GalleryRepository +import com.prodhack.moscow2025.domain.models.UpdateUserData +import com.prodhack.moscow2025.domain.usecase.auth.AuthField +import com.prodhack.moscow2025.domain.usecase.auth.GetUserUseCase +import com.prodhack.moscow2025.domain.usecase.auth.UpdateUserUseCase +import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase +import com.prodhack.moscow2025.presentation.utils.UIState +import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel +import com.prodhack.moscow2025.presentation.utils.toByteArray +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +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 = emptyMap() +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + 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 + + 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 + } + + +} + +@KoinViewModel +class ProfileScreenViewModel( + private val getUserUseCase: GetUserUseCase, + private val updateUserUseCase: UpdateUserUseCase, + private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase, + private val galleryRepository: GalleryRepository +): BaseViewModel() { + private val _formStateProfile = MutableStateFlow(ProfileState()) + val formStateFillProfile: StateFlow = _formStateProfile + + private val _profileState = MutableUIStateFlow() + val profileState: StateFlow> = _profileState + + fun onEmailChange(value: String) { + _formStateProfile.update { + it.copy( + email = value, + errors = it.errors - AuthField.Email + ) + } + } + + fun onFirstNameChange(value: String) { + _formStateProfile.update { + it.copy( + firstName = value, + errors = it.errors - AuthField.FirstName + ) + } + } + + fun onLastNameChange(value: String) { + _formStateProfile.update { + it.copy( + lastName = value, + errors = it.errors - AuthField.LastName + ) + } + } + + fun onPhoneChange(value: String) { + _formStateProfile.update { + it.copy( + phone = value, + errors = it.errors - AuthField.Phone + ) + } + } + + val galleryItems = galleryRepository.getImagesIds().map { + it.map { id -> + ContentUris.withAppendedId( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + id + ) + } + } + + fun post(context: Context) { + viewModelScope.launch { + post( + (ImageLoader(context).execute( + ImageRequest.Builder(context) + .data(currentPhoto).build() + ).drawable as BitmapDrawable).bitmap + ) + } + } + + fun post(bitmap: Bitmap) { + viewModelScope.launch { + _formStateProfile.update { + it.copy( + avatar = bitmap.toByteArray() + ) + } + } + } + + fun clearAvatar() { + viewModelScope.launch { + _formStateProfile.update { + it.copy( + avatar = null + ) + } + } + } + + var currentPhoto: Uri? = null + + fun selectImage(photo: Uri) { + currentPhoto = photo + } + + fun submit() { + viewModelScope.launch { + val validation = validateAuthFieldsUseCase.validateProfile( + firstName = _formStateProfile.value.firstName, + lastName = _formStateProfile.value.lastName, + email = _formStateProfile.value.email, + phone = _formStateProfile.value.phone + ) + + if (!validation.isValid) { + _formStateProfile.update { it.copy(errors = validation.errors) } + return@launch + } + + _profileState.emit(UIState.Loading()) + + val result = updateUserUseCase( + UpdateUserData( + firstName = _formStateProfile.value.firstName, + lastName = _formStateProfile.value.lastName, + email = _formStateProfile.value.email, + phone = _formStateProfile.value.phone + ) + ) + result.map { it.id }.collectRequest(_profileState) + } + } + + init { + viewModelScope.launch { + val user = getUserUseCase().getOrNull() + if (user != null) { + _formStateProfile.update { + it.copy( + firstName = user.firstName.orEmpty(), + lastName = user.lastName.orEmpty(), + email = user.email, + phone = user.phone.orEmpty() + ) + } + } + } + } +} \ No newline at end of file