You've already forked RekomenciMobile
feat: add simple version of form
This commit is contained in:
@@ -85,6 +85,7 @@ dependencies {
|
|||||||
implementation(libs.compose.animation.graphics)
|
implementation(libs.compose.animation.graphics)
|
||||||
implementation(libs.material.icons.extended)
|
implementation(libs.material.icons.extended)
|
||||||
implementation(libs.androidx.foundation)
|
implementation(libs.androidx.foundation)
|
||||||
|
implementation(libs.androidx.runtime)
|
||||||
androidTestImplementation(platform(libs.compose.bom))
|
androidTestImplementation(platform(libs.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
debugImplementation(libs.ui.tooling)
|
debugImplementation(libs.ui.tooling)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.prodhack.moscow2025.data.dto
|
|||||||
|
|
||||||
import com.prodhack.moscow2025.data.data_providers.local_db.entities.ResumeEntity
|
import com.prodhack.moscow2025.data.data_providers.local_db.entities.ResumeEntity
|
||||||
import com.prodhack.moscow2025.domain.models.ExperienceType
|
import com.prodhack.moscow2025.domain.models.ExperienceType
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||||
import com.prodhack.moscow2025.domain.models.ResumeModel
|
import com.prodhack.moscow2025.domain.models.ResumeModel
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -32,6 +33,14 @@ enum class ExperienceTypeDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ExperienceType.mapToData(): ExperienceTypeDTO = when (this) {
|
||||||
|
ExperienceType.NoExperience -> ExperienceTypeDTO.NoExperience
|
||||||
|
ExperienceType.LessThan1 -> ExperienceTypeDTO.LessThan1
|
||||||
|
ExperienceType.Between1And3 -> ExperienceTypeDTO.Between1And3
|
||||||
|
ExperienceType.Between3And6 -> ExperienceTypeDTO.Between3And6
|
||||||
|
ExperienceType.MoreThan6 -> ExperienceTypeDTO.MoreThan6
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ResumeDTO(
|
data class ResumeDTO(
|
||||||
val id: String,
|
val id: String,
|
||||||
@@ -69,6 +78,23 @@ data class ResumeDTO(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResumeCreateDTO(
|
||||||
|
@SerialName("about_me")
|
||||||
|
val aboutMe: String,
|
||||||
|
@SerialName("experience_type")
|
||||||
|
val experienceType: ExperienceTypeDTO,
|
||||||
|
val keySkills: List<String>,
|
||||||
|
val position: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ResumeCreationModel.mapToData(): ResumeCreateDTO = ResumeCreateDTO(
|
||||||
|
aboutMe = about,
|
||||||
|
experienceType = experienceType.mapToData(),
|
||||||
|
keySkills = skills,
|
||||||
|
position = position
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PredictionDTO(
|
data class PredictionDTO(
|
||||||
@SerialName("from_salary")
|
@SerialName("from_salary")
|
||||||
@@ -83,3 +109,14 @@ data class PredictionDTO(
|
|||||||
data class ResumeListDTO(
|
data class ResumeListDTO(
|
||||||
val resumes: List<ResumeDTO>
|
val resumes: List<ResumeDTO>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResumeIdDTO(
|
||||||
|
@SerialName("resume_id")
|
||||||
|
val resumeId: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ResumeSkillDTO(
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
+30
@@ -4,12 +4,21 @@ import androidx.paging.map
|
|||||||
import com.prodhack.moscow2025.data.base.BaseRepository
|
import com.prodhack.moscow2025.data.base.BaseRepository
|
||||||
import com.prodhack.moscow2025.data.data_providers.api.ApiKtorClient
|
import com.prodhack.moscow2025.data.data_providers.api.ApiKtorClient
|
||||||
import com.prodhack.moscow2025.data.data_providers.local_db.AppDatabase
|
import com.prodhack.moscow2025.data.data_providers.local_db.AppDatabase
|
||||||
|
import com.prodhack.moscow2025.data.dto.ResumeCreateDTO
|
||||||
|
import com.prodhack.moscow2025.data.dto.ResumeIdDTO
|
||||||
import com.prodhack.moscow2025.data.dto.ResumeListDTO
|
import com.prodhack.moscow2025.data.dto.ResumeListDTO
|
||||||
|
import com.prodhack.moscow2025.data.dto.ResumeSkillDTO
|
||||||
import com.prodhack.moscow2025.domain.interfaces.resumes.ResumeRepository
|
import com.prodhack.moscow2025.domain.interfaces.resumes.ResumeRepository
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||||
import com.prodhack.moscow2025.domain.models.ResumeModel
|
import com.prodhack.moscow2025.domain.models.ResumeModel
|
||||||
import com.prodhack.moscow2025.domain.utils.RemotePagingWrapper
|
import com.prodhack.moscow2025.domain.utils.RemotePagingWrapper
|
||||||
|
import io.ktor.client.request.setBody
|
||||||
import io.ktor.client.request.url
|
import io.ktor.client.request.url
|
||||||
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.HttpMethod
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.contentType
|
||||||
|
import io.ktor.http.parameters
|
||||||
|
import io.ktor.http.path
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
@@ -37,4 +46,25 @@ class ResumeRepositoryImpl(
|
|||||||
}.map { it -> it.resumes.map { it.mapToDB() } }
|
}.map { it -> it.resumes.map { it.mapToDB() } }
|
||||||
}
|
}
|
||||||
).map { it -> it.map { it.mapToDomain() } }
|
).map { it -> it.map { it.mapToDomain() } }
|
||||||
|
|
||||||
|
override suspend fun suggestSkills(query: String): Result<List<String>> =
|
||||||
|
networkRequest<List<ResumeSkillDTO>> {
|
||||||
|
method = HttpMethod.Get
|
||||||
|
url {
|
||||||
|
path("key_skills")
|
||||||
|
parameters.append("query", query)
|
||||||
|
}
|
||||||
|
}.map { it.map { it.name } }
|
||||||
|
|
||||||
|
override suspend fun createResume(resumeForm: ResumeCreationModel): Result<String> =
|
||||||
|
networkRequest<ResumeIdDTO> {
|
||||||
|
method = HttpMethod.Post
|
||||||
|
|
||||||
|
url {
|
||||||
|
url("/resume")
|
||||||
|
}
|
||||||
|
|
||||||
|
setBody(ResumeCreateDTO)
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
}.map { it.resumeId }
|
||||||
}
|
}
|
||||||
+4
@@ -1,8 +1,12 @@
|
|||||||
package com.prodhack.moscow2025.domain.interfaces.resumes
|
package com.prodhack.moscow2025.domain.interfaces.resumes
|
||||||
|
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||||
import com.prodhack.moscow2025.domain.models.ResumeModel
|
import com.prodhack.moscow2025.domain.models.ResumeModel
|
||||||
import com.prodhack.moscow2025.domain.utils.RemotePagingWrapper
|
import com.prodhack.moscow2025.domain.utils.RemotePagingWrapper
|
||||||
|
|
||||||
interface ResumeRepository {
|
interface ResumeRepository {
|
||||||
fun loadResumeList(): RemotePagingWrapper<ResumeModel>
|
fun loadResumeList(): RemotePagingWrapper<ResumeModel>
|
||||||
|
|
||||||
|
suspend fun suggestSkills(query: String): Result<List<String>>
|
||||||
|
suspend fun createResume(resumeForm: ResumeCreationModel): Result<String>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,12 @@ data class LoginData(
|
|||||||
val email: String,
|
val email: String,
|
||||||
val password: String
|
val password: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum class AuthField {
|
||||||
|
FirstName,
|
||||||
|
LastName,
|
||||||
|
Email,
|
||||||
|
Password,
|
||||||
|
ConfirmPassword,
|
||||||
|
Phone
|
||||||
|
}
|
||||||
@@ -10,6 +10,13 @@ data class ResumeModel(
|
|||||||
val recommendedSkills: List<String>
|
val recommendedSkills: List<String>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ResumeCreationModel(
|
||||||
|
val position: String,
|
||||||
|
val about: String,
|
||||||
|
val skills: List<String>,
|
||||||
|
val experienceType: ExperienceType
|
||||||
|
)
|
||||||
|
|
||||||
enum class ExperienceType {
|
enum class ExperienceType {
|
||||||
NoExperience,
|
NoExperience,
|
||||||
LessThan1,
|
LessThan1,
|
||||||
@@ -17,3 +24,10 @@ enum class ExperienceType {
|
|||||||
Between3And6,
|
Between3And6,
|
||||||
MoreThan6
|
MoreThan6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class ResumeField {
|
||||||
|
About,
|
||||||
|
Position,
|
||||||
|
Experience,
|
||||||
|
KeySkills
|
||||||
|
}
|
||||||
+44
-40
@@ -1,36 +1,29 @@
|
|||||||
package com.prodhack.moscow2025.domain.usecase.auth
|
package com.prodhack.moscow2025.domain.usecase.auth
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
|
import com.prodhack.moscow2025.domain.models.ExperienceType
|
||||||
import com.prodhack.moscow2025.domain.models.PhoneNumberPattern
|
import com.prodhack.moscow2025.domain.models.PhoneNumberPattern
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeField
|
||||||
|
import com.prodhack.moscow2025.presentation.screens.createResume.UIExperience
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
enum class AuthField {
|
data class ValidationResult<T>(
|
||||||
FirstName,
|
val errors: Map<T, String> = emptyMap()
|
||||||
LastName,
|
|
||||||
Email,
|
|
||||||
Password,
|
|
||||||
ConfirmPassword,
|
|
||||||
Phone
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class ValidationResult(
|
|
||||||
val errors: Map<AuthField, String> = emptyMap()
|
|
||||||
) {
|
) {
|
||||||
val isValid: Boolean
|
val isValid: Boolean
|
||||||
get() = errors.isEmpty()
|
get() = errors.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
class ValidateAuthFieldsUseCase {
|
class ValidateFieldsUseCase {
|
||||||
fun validateProfile(
|
fun validateProfile(
|
||||||
chosenPattern: PhoneNumberPattern?,
|
chosenPattern: PhoneNumberPattern?,
|
||||||
firstName: String,
|
firstName: String,
|
||||||
lastName: String,
|
lastName: String,
|
||||||
email: String,
|
email: String,
|
||||||
phone: String
|
phone: String
|
||||||
): ValidationResult {
|
): ValidationResult<AuthField> {
|
||||||
val errors = buildMap {
|
val errors = buildMap {
|
||||||
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
||||||
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
||||||
@@ -49,7 +42,7 @@ class ValidateAuthFieldsUseCase {
|
|||||||
firstName: String,
|
firstName: String,
|
||||||
lastName: String,
|
lastName: String,
|
||||||
phone: String
|
phone: String
|
||||||
): ValidationResult {
|
): ValidationResult<AuthField> {
|
||||||
val errors = buildMap {
|
val errors = buildMap {
|
||||||
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
||||||
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
if (lastName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
||||||
@@ -66,7 +59,7 @@ class ValidateAuthFieldsUseCase {
|
|||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
confirmPassword: String
|
confirmPassword: String
|
||||||
): ValidationResult {
|
): ValidationResult<AuthField> {
|
||||||
val errors = buildMap {
|
val errors = buildMap {
|
||||||
if (!isEmailValid(email)) put(AuthField.Email, "Некорректный email")
|
if (!isEmailValid(email)) put(AuthField.Email, "Некорректный email")
|
||||||
validatePassword(password)?.let { put(AuthField.Password, it) }
|
validatePassword(password)?.let { put(AuthField.Password, it) }
|
||||||
@@ -79,6 +72,40 @@ class ValidateAuthFieldsUseCase {
|
|||||||
return ValidationResult(errors)
|
return ValidationResult(errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun validateLogin(
|
||||||
|
email: String,
|
||||||
|
password: String
|
||||||
|
): ValidationResult<AuthField> {
|
||||||
|
val errors = buildMap {
|
||||||
|
if (!isEmailValid(email)) put(AuthField.Email, "Некорректный email")
|
||||||
|
validatePassword(password)?.let { put(AuthField.Password, it) }
|
||||||
|
}
|
||||||
|
return ValidationResult(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun validateResume(
|
||||||
|
about: String,
|
||||||
|
position: String,
|
||||||
|
experience: ExperienceType?,
|
||||||
|
keySkills: List<String>
|
||||||
|
): ValidationResult<ResumeField> {
|
||||||
|
val errors = buildMap {
|
||||||
|
if (about.isBlank()) put(ResumeField.About, "Без этого мы не сможем рассчитать вашу ЗП")
|
||||||
|
if (position.isBlank()) put(
|
||||||
|
ResumeField.Position,
|
||||||
|
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||||
|
)
|
||||||
|
if (experience == null) put(
|
||||||
|
ResumeField.Experience,
|
||||||
|
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||||
|
)
|
||||||
|
if (keySkills.isEmpty()) put(ResumeField.KeySkills, "Укажите хотя бы один навык")
|
||||||
|
|
||||||
|
}
|
||||||
|
return ValidationResult(errors)
|
||||||
|
}
|
||||||
|
|
||||||
fun validatePassword(password: String): String? {
|
fun validatePassword(password: String): String? {
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
return "Пароль должен быть не менее 8 символов"
|
return "Пароль должен быть не менее 8 символов"
|
||||||
@@ -95,29 +122,6 @@ class ValidateAuthFieldsUseCase {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun validateLogin(
|
|
||||||
email: String,
|
|
||||||
password: String
|
|
||||||
): ValidationResult {
|
|
||||||
val errors = buildMap {
|
|
||||||
if (!isEmailValid(email)) put(AuthField.Email, "Некорректный email")
|
|
||||||
validatePassword(password)?.let { put(AuthField.Password, it) }
|
|
||||||
}
|
|
||||||
return ValidationResult(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateProfile(
|
|
||||||
firstName: String,
|
|
||||||
secondName: String,
|
|
||||||
): ValidationResult {
|
|
||||||
val errors = buildMap {
|
|
||||||
if (firstName.isBlank()) put(AuthField.FirstName, "Введите имя")
|
|
||||||
if (secondName.isBlank()) put(AuthField.LastName, "Введите фамилию")
|
|
||||||
}
|
|
||||||
return ValidationResult(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isEmailValid(email: String): Boolean =
|
private fun isEmailValid(email: String): Boolean =
|
||||||
email.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(email).matches()
|
email.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(email).matches()
|
||||||
|
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.prodhack.moscow2025.domain.usecase.resumes
|
||||||
|
|
||||||
|
import com.prodhack.moscow2025.domain.interfaces.resumes.ResumeRepository
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class CreateResumeUseCase(
|
||||||
|
private val resumeRepository: ResumeRepository
|
||||||
|
){
|
||||||
|
suspend operator fun invoke(resumeForm: ResumeCreationModel): Result<String> = resumeRepository.createResume(resumeForm)
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.prodhack.moscow2025.domain.usecase.resumes
|
||||||
|
|
||||||
|
import com.prodhack.moscow2025.domain.interfaces.resumes.ResumeRepository
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class SuggestSkillsUseCase(
|
||||||
|
private val resumeRepository: ResumeRepository
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(query: String): Result<List<String>> =
|
||||||
|
resumeRepository.suggestSkills(query = query)
|
||||||
|
|
||||||
|
// mock
|
||||||
|
// suspend operator fun invoke(query: String): Result<List<String>> =
|
||||||
|
// Result.success(listOf(
|
||||||
|
// "Python", "Kotlin", "Java", "C#", "JavaScript",
|
||||||
|
// "TypeScript", "Go", "Rust", "Swift", "PHP",
|
||||||
|
// "Ruby", "C++", "Dart", "HTML", "CSS",
|
||||||
|
// "SQL", "NoSQL", "MongoDB", "PostgreSQL", "MySQL",
|
||||||
|
// "Docker", "Kubernetes", "AWS", "Azure", "Google Cloud Platform",
|
||||||
|
// "React", "Angular", "Vue.js", "Node.js", "Spring Boot",
|
||||||
|
// "Django", "Flask", "ASP.NET", "Ruby on Rails", "Laravel",
|
||||||
|
// "Android", "iOS", "Flutter", "React Native", "Xamarin",
|
||||||
|
// "Git", "Jira", "Confluence", "Jenkins", "Travis CI",
|
||||||
|
// "Agile", "Scrum", "Kanban", "DevOps", "GraphQL"
|
||||||
|
// ).filter { it.contains(query, ignoreCase = true) }.take(10))
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.prodhack.moscow2025.presentation.components.standart
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.prodhack.moscow2025.R
|
||||||
|
import com.prodhack.moscow2025.presentation.theme.Paddings
|
||||||
|
import com.prodhack.moscow2025.presentation.theme.Shapes
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TBubble(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
onDelete: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val shapes = MaterialTheme.shapes
|
||||||
|
val typography = MaterialTheme.typography
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
val bubbleColor =
|
||||||
|
animateColorAsState(
|
||||||
|
if (isSelected) colorScheme.primary else colorScheme.secondary
|
||||||
|
)
|
||||||
|
val bubbleBorderColor =
|
||||||
|
animateColorAsState(
|
||||||
|
if (isSelected) colorScheme.primaryFixed else colorScheme.secondaryFixed
|
||||||
|
)
|
||||||
|
val contentColor =
|
||||||
|
animateColorAsState(
|
||||||
|
if (isSelected) colorScheme.onPrimary else colorScheme.onSecondary
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.background(
|
||||||
|
color = bubbleColor.value,
|
||||||
|
shape = shapes.small
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = bubbleBorderColor.value,
|
||||||
|
shape = shapes.small
|
||||||
|
)
|
||||||
|
.clip(Shapes.smallRoundedBox)
|
||||||
|
.padding(horizontal = Paddings.small, vertical = Paddings.verySmall),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = typography.labelLarge,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = contentColor.value
|
||||||
|
)
|
||||||
|
onDelete?.let {
|
||||||
|
Spacer(modifier = Modifier.width(Paddings.verySmall))
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.noRippleClickable(it),
|
||||||
|
painter = painterResource(R.drawable.ic_remove),
|
||||||
|
tint = contentColor.value,
|
||||||
|
contentDescription = "Remove"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -266,7 +266,7 @@ fun <T> TTTextFieldWithSearch(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
value: String,
|
value: String,
|
||||||
onValueChange: (String) -> Unit = {},
|
onValueChange: (String) -> Unit = {},
|
||||||
readOnly: Boolean = true,
|
readOnly: Boolean = false,
|
||||||
label: String,
|
label: String,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
singleLine: Boolean = true,
|
singleLine: Boolean = true,
|
||||||
|
|||||||
@@ -20,5 +20,7 @@ sealed class AppDestination(val route: String) {
|
|||||||
data object ResumeDetails : AppDestination("resume/details") {
|
data object ResumeDetails : AppDestination("resume/details") {
|
||||||
const val ARG_ID = "id"
|
const val ARG_ID = "id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data object ResumeCreation: AppDestination("resume/creation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import com.prodhack.moscow2025.presentation.screens.main.MainScreen
|
import com.prodhack.moscow2025.presentation.screens.main.MainScreen
|
||||||
import com.prodhack.moscow2025.domain.utils.NetworkError
|
import com.prodhack.moscow2025.domain.utils.NetworkError
|
||||||
|
import com.prodhack.moscow2025.presentation.screens.createResume.CreateResumeScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.fillProfile.FillProfileScreen
|
import com.prodhack.moscow2025.presentation.screens.fillProfile.FillProfileScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
|
import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
||||||
@@ -91,11 +92,15 @@ fun TTasksNavHost(
|
|||||||
}
|
}
|
||||||
|
|
||||||
composable(AppDestination.Main.route) {
|
composable(AppDestination.Main.route) {
|
||||||
MainScreen(openResumeDetails = { id ->
|
MainScreen(
|
||||||
|
openResumeDetails = { id ->
|
||||||
navController.navigate(AppDestination.ResumeDetails.route, Bundle().apply {
|
navController.navigate(AppDestination.ResumeDetails.route, Bundle().apply {
|
||||||
putString(AppDestination.ResumeDetails.ARG_ID, id)
|
putString(AppDestination.ResumeDetails.ARG_ID, id)
|
||||||
})
|
})
|
||||||
})
|
}, openCreateResume = {
|
||||||
|
navController.navigate(AppDestination.ResumeCreation.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(AppDestination.Profile.route)
|
composable(AppDestination.Profile.route)
|
||||||
@@ -111,6 +116,10 @@ fun TTasksNavHost(
|
|||||||
composable(AppDestination.ResumeDetails.route) {
|
composable(AppDestination.ResumeDetails.route) {
|
||||||
ResumeDetailsScreen(navBackStackEntry = it)
|
ResumeDetailsScreen(navBackStackEntry = it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(AppDestination.ResumeCreation.route) {
|
||||||
|
CreateResumeScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
package com.prodhack.moscow2025.presentation.screens.createResume
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.prodhack.moscow2025.R
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeField
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.BigButton
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.TBubble
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextFieldWithDropdown
|
||||||
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextFieldWithSearch
|
||||||
|
import com.prodhack.moscow2025.presentation.theme.Paddings
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CreateResumeScreen(
|
||||||
|
viewModel: CreateResumeViewModel = koinViewModel()
|
||||||
|
) {
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
val typography = MaterialTheme.typography
|
||||||
|
|
||||||
|
val formState = viewModel.formStateFillResume.collectAsState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = Paddings.large)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(Paddings.large))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.rotate(180f)
|
||||||
|
.size(24.dp),
|
||||||
|
painter = painterResource(R.drawable.ic_arr_details),
|
||||||
|
tint = colorScheme.onBackground,
|
||||||
|
contentDescription = "go back"
|
||||||
|
)
|
||||||
|
Text(text = "Новое резюме", style = typography.titleLarge, fontSize = 24.sp)
|
||||||
|
Spacer(modifier = Modifier.size(24.dp))
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(Paddings.large))
|
||||||
|
TTTextField(
|
||||||
|
value = formState.value.position,
|
||||||
|
onValueChange = viewModel::onPositionChange,
|
||||||
|
label = "Какая должность вас интересует?",
|
||||||
|
error = formState.value.errors[ResumeField.Position]
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||||
|
TTTextField(
|
||||||
|
value = formState.value.about,
|
||||||
|
onValueChange = viewModel::onAboutChange,
|
||||||
|
singleLine = false,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
label = "Расскажите о себе",
|
||||||
|
error = formState.value.errors[ResumeField.Position]
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||||
|
TTTextFieldWithDropdown(
|
||||||
|
value = formState.value.experience?.friendlyName ?: "",
|
||||||
|
onValueChange = {},
|
||||||
|
singleLine = false,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
label = "Какой у вас опыт в данной сфере?",
|
||||||
|
error = formState.value.errors[ResumeField.Experience],
|
||||||
|
dropdownItems = viewModel.experienceOptions,
|
||||||
|
dropDownItem = {
|
||||||
|
Text(text = it.friendlyName, style = typography.titleMedium, fontSize = 16.sp)
|
||||||
|
},
|
||||||
|
onDropdownItemSelected = viewModel::onExperienceSelect
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||||
|
TTTextFieldWithSearch(
|
||||||
|
value = viewModel.skillSearchQuery.value,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.skillSearchQuery.value = it
|
||||||
|
},
|
||||||
|
label = "Ваши навыки",
|
||||||
|
error = formState.value.errors[ResumeField.Experience],
|
||||||
|
dropdownItems = viewModel.suggestedSkills.collectAsState(emptyList()).value,
|
||||||
|
dropDownItem = {
|
||||||
|
Text(text = it, style = typography.titleMedium, fontSize = 16.sp)
|
||||||
|
},
|
||||||
|
onDropdownItemSelected = viewModel::onAddSkill,
|
||||||
|
trailingIcon = {
|
||||||
|
if (viewModel.skillSearchQuery.value.isNotBlank()) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
viewModel.onAddSkill(viewModel.skillSearchQuery.value)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Добавить",
|
||||||
|
style = typography.labelLarge,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(Paddings.verySmall))
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
painter = painterResource(R.drawable.ic_plus),
|
||||||
|
tint = colorScheme.onPrimary,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(Paddings.medium))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
Paddings.small
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(
|
||||||
|
Paddings.small
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
formState.value.keySkills.forEach { skillName ->
|
||||||
|
TBubble(text = skillName) {
|
||||||
|
viewModel.onRemoveSkill(skillName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(Paddings.large))
|
||||||
|
|
||||||
|
BigButton(
|
||||||
|
onClick = {},
|
||||||
|
buttonText = "Узнать свою ЗП",
|
||||||
|
isLoading = viewModel.resumeFillState.collectAsState().value.isLoading
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
package com.prodhack.moscow2025.presentation.screens.createResume
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.prodhack.moscow2025.domain.models.ExperienceType
|
||||||
|
import com.prodhack.moscow2025.domain.models.RegisterData
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeField
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.resumes.CreateResumeUseCase
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.resumes.SuggestSkillsUseCase
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
|
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
||||||
|
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
|
||||||
|
import kotlin.math.exp
|
||||||
|
|
||||||
|
data class ResumeFormState(
|
||||||
|
val about: String = "",
|
||||||
|
val position: String = "",
|
||||||
|
val experience: UIExperience? = null,
|
||||||
|
val keySkills: Set<String> = emptySet(),
|
||||||
|
val errors: Map<ResumeField, String> = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class UIExperience(val friendlyName: String) {
|
||||||
|
data object NoExperience : UIExperience("Без опыта")
|
||||||
|
data object LessThan1 : UIExperience("Меньше года")
|
||||||
|
data object Between1And3 : UIExperience("От 1 до 3 лет")
|
||||||
|
data object Between3And6 : UIExperience("От 3 до 6 лет")
|
||||||
|
data object MoreThan6 : UIExperience("Более 6 лет")
|
||||||
|
|
||||||
|
fun mapToDomain(): ExperienceType =
|
||||||
|
when (this) {
|
||||||
|
is NoExperience -> ExperienceType.NoExperience
|
||||||
|
is LessThan1 -> ExperienceType.LessThan1
|
||||||
|
is Between1And3 -> ExperienceType.Between1And3
|
||||||
|
is Between3And6 -> ExperienceType.Between3And6
|
||||||
|
is MoreThan6 -> ExperienceType.MoreThan6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@KoinViewModel
|
||||||
|
class CreateResumeViewModel(
|
||||||
|
private val suggestSkillsUseCase: SuggestSkillsUseCase,
|
||||||
|
private val validateDataUseCase: ValidateFieldsUseCase,
|
||||||
|
private val createResumeUseCase: CreateResumeUseCase
|
||||||
|
) : BaseViewModel() {
|
||||||
|
private val _formStateFillResume = MutableStateFlow(ResumeFormState())
|
||||||
|
val formStateFillResume: StateFlow<ResumeFormState> = _formStateFillResume
|
||||||
|
|
||||||
|
private val _resumeFillState = MutableUIStateFlow<String>()
|
||||||
|
val resumeFillState: StateFlow<UIState<String>> = _resumeFillState
|
||||||
|
|
||||||
|
fun onAboutChange(value: String) {
|
||||||
|
_formStateFillResume.update {
|
||||||
|
it.copy(
|
||||||
|
about = value,
|
||||||
|
errors = it.errors - ResumeField.About
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPositionChange(value: String) {
|
||||||
|
_formStateFillResume.update {
|
||||||
|
it.copy(
|
||||||
|
position = value,
|
||||||
|
errors = it.errors - ResumeField.Position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onExperienceSelect(value: UIExperience) {
|
||||||
|
_formStateFillResume.update {
|
||||||
|
it.copy(
|
||||||
|
experience = value,
|
||||||
|
errors = it.errors - ResumeField.Experience
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAddSkill(value: String) {
|
||||||
|
_formStateFillResume.update {
|
||||||
|
it.copy(
|
||||||
|
keySkills = it.keySkills + value,
|
||||||
|
errors = it.errors - ResumeField.KeySkills
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRemoveSkill(value: String) {
|
||||||
|
_formStateFillResume.update {
|
||||||
|
it.copy(
|
||||||
|
keySkills = it.keySkills - value,
|
||||||
|
errors = it.errors - ResumeField.KeySkills
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val skillSearchQuery = mutableStateOf("")
|
||||||
|
|
||||||
|
val suggestedSkills = snapshotFlow { skillSearchQuery.value }.map {
|
||||||
|
suggestSkillsUseCase(it).getOrNull() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val experienceOptions = listOf(
|
||||||
|
UIExperience.NoExperience,
|
||||||
|
UIExperience.LessThan1,
|
||||||
|
UIExperience.Between1And3,
|
||||||
|
UIExperience.Between3And6,
|
||||||
|
UIExperience.MoreThan6
|
||||||
|
)
|
||||||
|
|
||||||
|
fun submit() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val validation = validateDataUseCase.validateResume(
|
||||||
|
about = _formStateFillResume.value.about,
|
||||||
|
position = _formStateFillResume.value.position,
|
||||||
|
experience = _formStateFillResume.value.experience?.mapToDomain(),
|
||||||
|
keySkills = _formStateFillResume.value.keySkills.toList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!validation.isValid) {
|
||||||
|
_formStateFillResume.update { it.copy(errors = validation.errors) }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
_resumeFillState.emit(UIState.Loading())
|
||||||
|
|
||||||
|
val result = createResumeUseCase(
|
||||||
|
with(_formStateFillResume.value) {
|
||||||
|
ResumeCreationModel(
|
||||||
|
position = position,
|
||||||
|
about = about,
|
||||||
|
skills = keySkills.toList(),
|
||||||
|
experienceType = experience!!.mapToDomain()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
result.collectRequest(_resumeFillState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-20
@@ -1,35 +1,24 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.fillProfile
|
package com.prodhack.moscow2025.presentation.screens.fillProfile
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -43,25 +32,17 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
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.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.input.KeyboardType
|
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.prodhack.moscow2025.R
|
import com.prodhack.moscow2025.R
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
import com.prodhack.moscow2025.domain.models.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.TPhoneCountryList
|
import com.prodhack.moscow2025.presentation.components.standart.TPhoneCountryList
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.TPhoneField
|
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.Shapes
|
|
||||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||||
import com.prodhack.moscow2025.presentation.utils.PhoneVisualTransformation
|
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -14,11 +14,11 @@ import coil.ImageLoader
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.prodhack.moscow2025.data.data_providers.PhoneNumberPatternsProvider
|
import com.prodhack.moscow2025.data.data_providers.PhoneNumberPatternsProvider
|
||||||
import com.prodhack.moscow2025.domain.interfaces.GalleryRepository
|
import com.prodhack.moscow2025.domain.interfaces.GalleryRepository
|
||||||
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
import com.prodhack.moscow2025.domain.models.UpdateUserData
|
import com.prodhack.moscow2025.domain.models.UpdateUserData
|
||||||
import com.prodhack.moscow2025.domain.usecase.GetDefaultPhoneNumberPatternUseCase
|
import com.prodhack.moscow2025.domain.usecase.GetDefaultPhoneNumberPatternUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.UpdateUserUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.UpdateUserUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
||||||
import com.prodhack.moscow2025.presentation.utils.convertNumberToPattern
|
import com.prodhack.moscow2025.presentation.utils.convertNumberToPattern
|
||||||
@@ -65,7 +65,7 @@ data class FillProfileFormState(
|
|||||||
@KoinViewModel
|
@KoinViewModel
|
||||||
class FillProfileViewModel(
|
class FillProfileViewModel(
|
||||||
private val updateUserUseCase: UpdateUserUseCase,
|
private val updateUserUseCase: UpdateUserUseCase,
|
||||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
|
private val validateFieldsUseCase: ValidateFieldsUseCase,
|
||||||
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
||||||
private val galleryRepository: GalleryRepository
|
private val galleryRepository: GalleryRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
@@ -168,7 +168,7 @@ class FillProfileViewModel(
|
|||||||
|
|
||||||
fun submit() {
|
fun submit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val validation = validateAuthFieldsUseCase.validateFillProfile(
|
val validation = validateFieldsUseCase.validateFillProfile(
|
||||||
firstName = _formStateFillProfile.value.firstName,
|
firstName = _formStateFillProfile.value.firstName,
|
||||||
lastName = _formStateFillProfile.value.lastName,
|
lastName = _formStateFillProfile.value.lastName,
|
||||||
phone = _formStateFillProfile.value.phone,
|
phone = _formStateFillProfile.value.phone,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import com.prodhack.moscow2025.R
|
import com.prodhack.moscow2025.R
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.BigButton
|
import com.prodhack.moscow2025.presentation.components.standart.BigButton
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
|
import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
||||||
|
|||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.login
|
package com.prodhack.moscow2025.presentation.screens.login
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
import com.prodhack.moscow2025.domain.models.LoginData
|
import com.prodhack.moscow2025.domain.models.LoginData
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.LoginUserUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.LoginUserUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -22,7 +22,7 @@ data class LoginFormState(
|
|||||||
@KoinViewModel
|
@KoinViewModel
|
||||||
class LoginViewModel(
|
class LoginViewModel(
|
||||||
private val loginUserUseCase: LoginUserUseCase,
|
private val loginUserUseCase: LoginUserUseCase,
|
||||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase
|
private val validateFieldsUseCase: ValidateFieldsUseCase
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val _formState = MutableStateFlow(LoginFormState())
|
private val _formState = MutableStateFlow(LoginFormState())
|
||||||
@@ -41,7 +41,7 @@ class LoginViewModel(
|
|||||||
|
|
||||||
fun submit() {
|
fun submit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val validation = validateAuthFieldsUseCase.validateLogin(
|
val validation = validateFieldsUseCase.validateLogin(
|
||||||
email = _formState.value.email,
|
email = _formState.value.email,
|
||||||
password = _formState.value.password
|
password = _formState.value.password
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import org.koin.androidx.compose.koinViewModel
|
|||||||
fun ErrorCollectorScope.MainScreen(
|
fun ErrorCollectorScope.MainScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
openResumeDetails: (String) -> Unit,
|
openResumeDetails: (String) -> Unit,
|
||||||
|
openCreateResume: () -> Unit,
|
||||||
viewModel: MainScreenViewModel = koinViewModel()
|
viewModel: MainScreenViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
@@ -53,7 +54,7 @@ fun ErrorCollectorScope.MainScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 20.dp),
|
.padding(horizontal = Paddings.large),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
TopLogo()
|
TopLogo()
|
||||||
@@ -78,9 +79,13 @@ fun ErrorCollectorScope.MainScreen(
|
|||||||
color = colorScheme.onBackground
|
color = colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
|
||||||
BigButton(onClick = {
|
BigButton(
|
||||||
|
onClick = {
|
||||||
TODO()
|
TODO()
|
||||||
}, buttonText = "Создать резюме", isLoading = false)
|
},
|
||||||
|
buttonText = "Создать резюме",
|
||||||
|
isLoading = false
|
||||||
|
)
|
||||||
} else if (items.loadState.hasError) {
|
} else if (items.loadState.hasError) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -123,7 +128,7 @@ fun ErrorCollectorScope.MainScreen(
|
|||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.padding(bottom = Paddings.medium),
|
.padding(bottom = Paddings.medium),
|
||||||
onClick = {
|
onClick = {
|
||||||
Toast.makeText(context, "Will be soon...", Toast.LENGTH_SHORT).show()
|
openCreateResume()
|
||||||
},
|
},
|
||||||
text = "Добавить резюме"
|
text = "Добавить резюме"
|
||||||
)
|
)
|
||||||
|
|||||||
-1
@@ -2,7 +2,6 @@ package com.prodhack.moscow2025.presentation.screens.main
|
|||||||
|
|
||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
import com.prodhack.moscow2025.domain.usecase.resumes.LoadResumeListUseCase
|
import com.prodhack.moscow2025.domain.usecase.resumes.LoadResumeListUseCase
|
||||||
import com.prodhack.moscow2025.presentation.dataModels.UIResumeBaseInfo
|
|
||||||
import com.prodhack.moscow2025.presentation.dataModels.mapToBaseUIInfo
|
import com.prodhack.moscow2025.presentation.dataModels.mapToBaseUIInfo
|
||||||
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|||||||
+1
-15
@@ -1,14 +1,9 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.profile
|
package com.prodhack.moscow2025.presentation.screens.profile
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -17,16 +12,12 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.width
|
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.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonColors
|
import androidx.compose.material3.ButtonColors
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -40,24 +31,19 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
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.res.painterResource
|
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.KeyboardType
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.prodhack.moscow2025.R
|
import com.prodhack.moscow2025.R
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
import com.prodhack.moscow2025.domain.models.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.TPhoneCountryList
|
import com.prodhack.moscow2025.presentation.components.standart.TPhoneCountryList
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.TPhoneField
|
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
|
||||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||||
import com.prodhack.moscow2025.presentation.utils.PhoneVisualTransformation
|
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle
|
import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle
|
||||||
import com.prodhack.moscow2025.presentation.utils.ui.showSnackbar
|
import com.prodhack.moscow2025.presentation.utils.ui.showSnackbar
|
||||||
|
|||||||
+9
-13
@@ -6,8 +6,6 @@ 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
|
||||||
@@ -16,13 +14,13 @@ import coil.ImageLoader
|
|||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.prodhack.moscow2025.data.data_providers.PhoneNumberPatternsProvider
|
import com.prodhack.moscow2025.data.data_providers.PhoneNumberPatternsProvider
|
||||||
import com.prodhack.moscow2025.domain.interfaces.GalleryRepository
|
import com.prodhack.moscow2025.domain.interfaces.GalleryRepository
|
||||||
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
import com.prodhack.moscow2025.domain.models.UpdateUserData
|
import com.prodhack.moscow2025.domain.models.UpdateUserData
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
import com.prodhack.moscow2025.domain.usecase.GetDefaultPhoneNumberPatternUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.GetUserUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.GetUserUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.LogOutUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.LogOutUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.UpdateUserUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.UpdateUserUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.GetDefaultPhoneNumberPatternUseCase
|
|
||||||
import com.prodhack.moscow2025.presentation.screens.fillProfile.UIPhoneNumberPattern
|
import com.prodhack.moscow2025.presentation.screens.fillProfile.UIPhoneNumberPattern
|
||||||
import com.prodhack.moscow2025.presentation.screens.fillProfile.mapToUI
|
import com.prodhack.moscow2025.presentation.screens.fillProfile.mapToUI
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
@@ -39,7 +37,7 @@ 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 ProfileFormState(
|
||||||
val email: String = "",
|
val email: String = "",
|
||||||
val firstName: String = "",
|
val firstName: String = "",
|
||||||
val lastName: String = "",
|
val lastName: String = "",
|
||||||
@@ -51,7 +49,7 @@ data class ProfileState(
|
|||||||
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 ProfileFormState
|
||||||
|
|
||||||
if (email != other.email) return false
|
if (email != other.email) return false
|
||||||
if (firstName != other.firstName) return false
|
if (firstName != other.firstName) return false
|
||||||
@@ -72,21 +70,19 @@ data class ProfileState(
|
|||||||
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 validateFieldsUseCase: ValidateFieldsUseCase,
|
||||||
private val logOutUseCase: LogOutUseCase,
|
private val logOutUseCase: LogOutUseCase,
|
||||||
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
|
||||||
galleryRepository: GalleryRepository
|
galleryRepository: GalleryRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
private val _formStateProfile = MutableStateFlow(ProfileState())
|
private val _formStateProfile = MutableStateFlow(ProfileFormState())
|
||||||
val formStateFillProfile: StateFlow<ProfileState> = _formStateProfile
|
val formStateFillProfile: StateFlow<ProfileFormState> = _formStateProfile
|
||||||
|
|
||||||
private val _profileState = MutableUIStateFlow<String>()
|
private val _profileState = MutableUIStateFlow<String>()
|
||||||
val profileState: StateFlow<UIState<String>> = _profileState
|
val profileState: StateFlow<UIState<String>> = _profileState
|
||||||
@@ -203,7 +199,7 @@ class ProfileScreenViewModel(
|
|||||||
fun submit() {
|
fun submit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val pattern = chosenPattern.value
|
val pattern = chosenPattern.value
|
||||||
val validation = validateAuthFieldsUseCase.validateProfile(
|
val validation = validateFieldsUseCase.validateProfile(
|
||||||
chosenPattern = pattern?.mapToDomain(),
|
chosenPattern = pattern?.mapToDomain(),
|
||||||
firstName = _formStateProfile.value.firstName,
|
firstName = _formStateProfile.value.firstName,
|
||||||
lastName = _formStateProfile.value.lastName,
|
lastName = _formStateProfile.value.lastName,
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.prodhack.moscow2025.R
|
import com.prodhack.moscow2025.R
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.BigButton
|
import com.prodhack.moscow2025.presentation.components.standart.BigButton
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
|
import com.prodhack.moscow2025.presentation.components.standart.TTPasswordField
|
||||||
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
import com.prodhack.moscow2025.presentation.components.standart.TTTextField
|
||||||
|
|||||||
+4
-31
@@ -1,10 +1,10 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.register
|
package com.prodhack.moscow2025.presentation.screens.register
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.prodhack.moscow2025.domain.models.AuthField
|
||||||
import com.prodhack.moscow2025.domain.models.RegisterData
|
import com.prodhack.moscow2025.domain.models.RegisterData
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.AuthField
|
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.RegisterUserUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.RegisterUserUseCase
|
||||||
import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase
|
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||||
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -23,7 +23,7 @@ data class RegisterFormState(
|
|||||||
@KoinViewModel
|
@KoinViewModel
|
||||||
class RegisterViewModel(
|
class RegisterViewModel(
|
||||||
private val registerUserUseCase: RegisterUserUseCase,
|
private val registerUserUseCase: RegisterUserUseCase,
|
||||||
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase
|
private val validateFieldsUseCase: ValidateFieldsUseCase
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val _formStateSignUp = MutableStateFlow(RegisterFormState())
|
private val _formStateSignUp = MutableStateFlow(RegisterFormState())
|
||||||
@@ -58,8 +58,7 @@ class RegisterViewModel(
|
|||||||
|
|
||||||
fun submit() {
|
fun submit() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
val validation = validateFieldsUseCase.validateSignUp(
|
||||||
val validation = validateAuthFieldsUseCase.validateSignUp(
|
|
||||||
email = _formStateSignUp.value.email,
|
email = _formStateSignUp.value.email,
|
||||||
password = _formStateSignUp.value.password,
|
password = _formStateSignUp.value.password,
|
||||||
confirmPassword = _formStateSignUp.value.confirmPassword
|
confirmPassword = _formStateSignUp.value.confirmPassword
|
||||||
@@ -79,32 +78,6 @@ class RegisterViewModel(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
result.collectRequest(_registerState)
|
result.collectRequest(_registerState)
|
||||||
|
|
||||||
// val validation = validateAuthFieldsUseCase.validateRegister(
|
|
||||||
// firstName = _formStateSignUp.value.firstName,
|
|
||||||
// lastName = _formStateSignUp.value.lastName,
|
|
||||||
// email = _formStateSignUp.value.email,
|
|
||||||
// password = _formStateSignUp.value.password,
|
|
||||||
// confirmPassword = _formStateSignUp.value.confirmPassword,
|
|
||||||
// phone = _formStateSignUp.value.ph
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// if (!validation.isValid) {
|
|
||||||
// _formStateSignUp.update { it.copy(errors = validation.errors) }
|
|
||||||
// return@launch
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// _registerState.emit(UIState.Loading())
|
|
||||||
//
|
|
||||||
// val result = registerUserUseCase(
|
|
||||||
// RegisterData(
|
|
||||||
// firstName = _formStateSignUp.value.firstName,
|
|
||||||
// secondName = _formStateSignUp.value.lastName,
|
|
||||||
// email = _formStateSignUp.value.email,
|
|
||||||
// password = _formStateSignUp.value.password
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// result.collectRequest(_registerState)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
sealed class UIState<T> {
|
sealed class UIState<T>() {
|
||||||
class Idle<T> : UIState<T>()
|
class Idle<T> : UIState<T>()
|
||||||
class Loading<T> : UIState<T>()
|
class Loading<T> : UIState<T>()
|
||||||
class Error<T>(val error: NetworkError) : UIState<T>()
|
class Error<T>(val error: NetworkError) : UIState<T>()
|
||||||
@@ -43,6 +43,9 @@ sealed class UIState<T> {
|
|||||||
|
|
||||||
val isSuccess: Boolean
|
val isSuccess: Boolean
|
||||||
get() = this is Success
|
get() = this is Success
|
||||||
|
|
||||||
|
val isLoading: Boolean
|
||||||
|
get() = this is Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ErrorCallbacks {
|
interface ErrorCallbacks {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M10.31,2.25H13.69C13.907,2.25 14.096,2.25 14.274,2.278C14.621,2.334 14.95,2.469 15.234,2.675C15.519,2.88 15.752,3.15 15.914,3.461C15.998,3.621 16.057,3.8 16.126,4.005L16.237,4.34L16.267,4.425C16.358,4.676 16.526,4.892 16.748,5.04C16.97,5.189 17.233,5.262 17.5,5.25H20.5C20.699,5.25 20.89,5.329 21.03,5.47C21.171,5.61 21.25,5.801 21.25,6C21.25,6.199 21.171,6.39 21.03,6.53C20.89,6.671 20.699,6.75 20.5,6.75H3.5C3.301,6.75 3.11,6.671 2.97,6.53C2.829,6.39 2.75,6.199 2.75,6C2.75,5.801 2.829,5.61 2.97,5.47C3.11,5.329 3.301,5.25 3.5,5.25H6.59C6.857,5.244 7.115,5.152 7.326,4.988C7.537,4.824 7.69,4.597 7.763,4.34L7.875,4.005C7.943,3.8 8.002,3.621 8.085,3.461C8.247,3.149 8.48,2.88 8.765,2.675C9.05,2.469 9.379,2.333 9.726,2.278C9.904,2.25 10.093,2.25 10.309,2.25M9.007,5.25C9.076,5.112 9.135,4.969 9.182,4.822L9.282,4.522C9.373,4.249 9.394,4.194 9.415,4.154C9.469,4.05 9.547,3.96 9.642,3.892C9.737,3.823 9.846,3.778 9.962,3.759C10.092,3.747 10.223,3.744 10.354,3.75H13.644C13.932,3.75 13.992,3.752 14.036,3.76C14.152,3.778 14.261,3.824 14.356,3.892C14.451,3.961 14.529,4.05 14.583,4.154C14.604,4.194 14.625,4.249 14.716,4.523L14.816,4.823L14.855,4.935C14.894,5.044 14.94,5.149 14.991,5.25H9.007Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M5.915,8.45C5.902,8.251 5.81,8.066 5.66,7.935C5.511,7.804 5.315,7.738 5.116,7.751C4.918,7.765 4.733,7.856 4.602,8.006C4.471,8.156 4.405,8.351 4.418,8.55L4.882,15.502C4.967,16.784 5.036,17.82 5.198,18.634C5.367,19.479 5.653,20.185 6.245,20.738C6.837,21.291 7.56,21.531 8.415,21.642C9.237,21.75 10.275,21.75 11.561,21.75H12.44C13.725,21.75 14.764,21.75 15.586,21.642C16.44,21.531 17.164,21.292 17.756,20.738C18.347,20.185 18.633,19.478 18.802,18.634C18.964,17.821 19.032,16.784 19.118,15.502L19.582,8.55C19.595,8.351 19.529,8.156 19.398,8.006C19.267,7.856 19.082,7.765 18.883,7.751C18.685,7.738 18.489,7.804 18.34,7.935C18.19,8.066 18.098,8.251 18.085,8.45L17.625,15.35C17.535,16.697 17.471,17.635 17.331,18.34C17.194,19.025 17.004,19.387 16.731,19.643C16.457,19.899 16.083,20.065 15.391,20.155C14.678,20.248 13.738,20.25 12.387,20.25H11.613C10.263,20.25 9.323,20.248 8.609,20.155C7.917,20.065 7.543,19.899 7.269,19.643C6.996,19.387 6.806,19.025 6.669,18.341C6.529,17.635 6.465,16.697 6.375,15.349L5.915,8.45Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M9.425,10.254C9.623,10.234 9.82,10.294 9.974,10.42C10.128,10.545 10.226,10.727 10.246,10.925L10.746,15.925C10.761,16.12 10.698,16.313 10.573,16.463C10.447,16.613 10.268,16.708 10.073,16.727C9.878,16.747 9.684,16.69 9.531,16.568C9.378,16.446 9.278,16.269 9.254,16.075L8.754,11.075C8.734,10.877 8.794,10.679 8.92,10.526C9.045,10.372 9.227,10.274 9.425,10.254ZM14.575,10.254C14.773,10.274 14.954,10.372 15.08,10.525C15.206,10.679 15.266,10.876 15.246,11.074L14.746,16.074C14.721,16.268 14.622,16.444 14.469,16.566C14.316,16.687 14.122,16.744 13.927,16.725C13.733,16.706 13.554,16.611 13.428,16.462C13.302,16.312 13.24,16.12 13.254,15.925L13.754,10.925C13.774,10.727 13.872,10.546 14.025,10.42C14.179,10.294 14.377,10.234 14.575,10.254Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@@ -39,6 +39,7 @@ googleServicesGMC = "4.4.4"
|
|||||||
crashlytics = "3.0.6"
|
crashlytics = "3.0.6"
|
||||||
foundation = "1.9.4"
|
foundation = "1.9.4"
|
||||||
kotzilla = "1.4.0"
|
kotzilla = "1.4.0"
|
||||||
|
runtime = "1.9.5"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -116,6 +117,7 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
|
|||||||
compose-animation-graphics = { module = "androidx.compose.animation:animation-graphics" }
|
compose-animation-graphics = { module = "androidx.compose.animation:animation-graphics" }
|
||||||
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
|
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
|
||||||
kotzilla-sdk = { group = "io.kotzilla", name = "kotzilla-sdk", version.ref = "kotzilla" }
|
kotzilla-sdk = { group = "io.kotzilla", name = "kotzilla-sdk", version.ref = "kotzilla" }
|
||||||
|
androidx-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
|
||||||
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|||||||
Reference in New Issue
Block a user