You've already forked RekomenciMobile
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:
+14
@@ -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?,
|
||||
|
||||
+6
-1
@@ -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 {
|
||||
|
||||
|
||||
}
|
||||
+204
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user