feat: added view model for profile screen

# Conflicts:
#	.idea/deploymentTargetSelector.xml
#	app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileScreen.kt
This commit is contained in:
dany
2025-11-21 20:02:55 +03:00
parent 75de13980f
commit 15f8fe1d85
3 changed files with 224 additions and 1 deletions
@@ -24,6 +24,20 @@ data class ValidationResult(
@Single @Single
class ValidateAuthFieldsUseCase { 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( fun validateFillProfile(
chosenPattern: PhoneNumberPattern?, chosenPattern: PhoneNumberPattern?,
@@ -2,8 +2,13 @@ package com.prodhack.moscow2025.presentation.screens.profile
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
import org.koin.androidx.compose.koinViewModel
@Composable @Composable
fun ProfileScreen(modifier: Modifier = Modifier) { fun ProfileScreen(
viewModel: ProfileScreenViewModel = koinViewModel()
) : BaseViewModel {
} }
@@ -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<AuthField, String> = 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<ProfileState> = _formStateProfile
private val _profileState = MutableUIStateFlow<String>()
val profileState: StateFlow<UIState<String>> = _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()
)
}
}
}
}
}