diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt index 1be49fc..d618dc6 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeScreen.kt @@ -199,21 +199,21 @@ fun CreateResumeScreen( TTTextField( value = workExp.description, onValueChange = { - viewModel.changeWorkExperiencePlace(index, it) + viewModel.changeWorkExperienceDescription(index, it) }, singleLine = false, maxLines = 10, label = "Расскажите подробнее", - error = formState.value.errors[ResumeField.WorkExperiencePlace(index)] + error = formState.value.errors[ResumeField.WorkExperienceDescription(index)] ) TTTextField( - value = workExp.place, + value = workExp.monthDuration?.toString() ?: "", onValueChange = { - viewModel.changeWorkExperiencePlace(index, it) + viewModel.changeWorkExperienceMonthDuration(index, it) }, - label = "Место работы", - error = formState.value.errors[ResumeField.WorkExperiencePlace(index)] + label = "Продолжительность (в месяцах)", + error = formState.value.errors[ResumeField.WorkExperienceMonthDuration(index)] ) } @@ -246,6 +246,161 @@ fun CreateResumeScreen( fontSize = 18.sp, ) } + Spacer(modifier = Modifier.height(Paddings.large)) + + Text( + modifier = Modifier.fillMaxWidth(), + text = "Образование:", + style = typography.titleMedium, + fontSize = 20.sp, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(Paddings.large)) + + formState.value.education.forEachIndexed { index, education -> + Text( + text = "№${index + 1}:", + style = typography.labelLarge, + fontSize = 18.sp + ) + Spacer(modifier = Modifier.height(Paddings.medium)) + + TTTextField( + value = education.place, + onValueChange = { viewModel.changeEducationPlace(index, it) }, + label = "Учебное заведение", + error = formState.value.errors[ResumeField.EducationPlace(index)] + ) + + TTTextFieldWithDropdown( + value = education.grade.friendlyName, + onValueChange = {}, + singleLine = false, + maxLines = Int.MAX_VALUE, + label = "Уровень образования", + error = formState.value.errors[ResumeField.EducationGrade(index)], + dropdownItems = viewModel.educationGradeOptions, + dropDownItem = { + Text(text = it.friendlyName, style = typography.titleMedium, fontSize = 16.sp) + }, + onDropdownItemSelected = { viewModel.changeEducationGrade(index, it) } + ) + + TTTextField( + value = education.specialization, + onValueChange = { viewModel.changeEducationSpecialization(index, it) }, + label = "Специализация", + error = formState.value.errors[ResumeField.EducationSpecialization(index)] + ) + + TTTextField( + value = education.description, + onValueChange = { viewModel.changeEducationDescription(index, it) }, + singleLine = false, + maxLines = 10, + label = "Расскажите подробнее", + error = formState.value.errors[ResumeField.EducationDescription(index)] + ) + } + + if (formState.value.education.isEmpty()){ + Text( + modifier = Modifier.fillMaxWidth(), + text = "Пока ничего нет", + style = typography.labelLarge, + fontSize = 18.sp, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(Paddings.medium)) + } + + Button( + modifier = Modifier + .fillMaxWidth(), + shape = Shapes.smallRoundedBox, + onClick = viewModel::addNewEducation, + colors = ButtonColors( + containerColor = colorScheme.onSecondary, + contentColor = colorScheme.secondary, + disabledContainerColor = colorScheme.onSecondary, + disabledContentColor = colorScheme.secondary + ) + ) { + Text( + text = "Добавить", + style = typography.labelLarge, + fontSize = 18.sp, + ) + } + + Spacer(modifier = Modifier.height(Paddings.large)) + + Text( + modifier = Modifier.fillMaxWidth(), + text = "Проекты:", + style = typography.titleMedium, + fontSize = 20.sp, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(Paddings.large)) + + formState.value.projects.forEachIndexed { index, project -> + Text( + text = "№${index + 1}:", + style = typography.labelLarge, + fontSize = 18.sp + ) + Spacer(modifier = Modifier.height(Paddings.medium)) + + TTTextField( + value = project.name, + onValueChange = { viewModel.changeProjectName(index, it) }, + label = "Название проекта", + error = formState.value.errors[ResumeField.ProjectName(index)] + ) + + TTTextField( + value = project.description, + onValueChange = { viewModel.changeProjectDescription(index, it) }, + singleLine = false, + maxLines = 10, + label = "Расскажите подробнее", + error = formState.value.errors[ResumeField.ProjectDescription(index)] + ) + } + + if (formState.value.projects.isEmpty()){ + Text( + modifier = Modifier.fillMaxWidth(), + text = "Пока ничего нет", + style = typography.labelLarge, + fontSize = 18.sp, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(Paddings.medium)) + } + + Button( + modifier = Modifier + .fillMaxWidth(), + shape = Shapes.smallRoundedBox, + onClick = viewModel::addNewProject, + colors = ButtonColors( + containerColor = colorScheme.onSecondary, + contentColor = colorScheme.secondary, + disabledContainerColor = colorScheme.onSecondary, + disabledContentColor = colorScheme.secondary + ) + ) { + Text( + text = "Добавить", + style = typography.labelLarge, + fontSize = 18.sp, + ) + } + Spacer(modifier = Modifier.height(Paddings.large)) BigButton( onClick = viewModel::submit, @@ -254,4 +409,4 @@ fun CreateResumeScreen( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeViewModel.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeViewModel.kt index 0c4ac38..c2bf15e 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeViewModel.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/createResume/CreateResumeViewModel.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.koin.android.annotation.KoinViewModel import kotlin.collections.minus -import kotlin.math.exp data class ResumeFormState( val about: String = "", @@ -67,6 +66,15 @@ sealed class UIEducationGrade(val friendlyName: String) { data object HighNotFinished : UIEducationGrade("Неоконченное высшее") data object High : UIEducationGrade("Высшее") data object Additional : UIEducationGrade("Другое") + + fun mapToDomain(): EducationGrades = when (this) { + Common -> EducationGrades.Common + Middle -> EducationGrades.Middle + MiddleSpec -> EducationGrades.MiddleSpec + HighNotFinished -> EducationGrades.HighNotFinished + High -> EducationGrades.High + Additional -> EducationGrades.Additional + } } @KoinViewModel @@ -219,6 +227,104 @@ class CreateResumeViewModel( } } + // Education + val educationGradeOptions = listOf( + UIEducationGrade.Common, + UIEducationGrade.Middle, + UIEducationGrade.MiddleSpec, + UIEducationGrade.HighNotFinished, + UIEducationGrade.High, + UIEducationGrade.Additional + ) + + fun addNewEducation() { + _formStateFillResume.update { + it.copy( + education = it.education + UIEducation( + place = "", + grade = UIEducationGrade.High, + specialization = "", + description = "" + ) + ) + } + } + + fun changeEducationPlace(index: Int, value: String) { + _formStateFillResume.update { + it.copy( + education = it.education.mapIndexed { ind, education -> + if (ind == index) education.copy(place = value) else education + }, + errors = it.errors - ResumeField.EducationPlace(index) + ) + } + } + + fun changeEducationGrade(index: Int, value: UIEducationGrade) { + _formStateFillResume.update { + it.copy( + education = it.education.mapIndexed { ind, education -> + if (ind == index) education.copy(grade = value) else education + }, + errors = it.errors - ResumeField.EducationGrade(index) + ) + } + } + + fun changeEducationSpecialization(index: Int, value: String) { + _formStateFillResume.update { + it.copy( + education = it.education.mapIndexed { ind, education -> + if (ind == index) education.copy(specialization = value) else education + }, + errors = it.errors - ResumeField.EducationSpecialization(index) + ) + } + } + + fun changeEducationDescription(index: Int, value: String) { + _formStateFillResume.update { + it.copy( + education = it.education.mapIndexed { ind, education -> + if (ind == index) education.copy(description = value) else education + }, + errors = it.errors - ResumeField.EducationDescription(index) + ) + } + } + + // Projects + fun addNewProject() { + _formStateFillResume.update { + it.copy( + projects = it.projects + Project("", "") + ) + } + } + + fun changeProjectName(index: Int, value: String) { + _formStateFillResume.update { + it.copy( + projects = it.projects.mapIndexed { ind, project -> + if (ind == index) project.copy(name = value) else project + }, + errors = it.errors - ResumeField.ProjectName(index) + ) + } + } + + fun changeProjectDescription(index: Int, value: String) { + _formStateFillResume.update { + it.copy( + projects = it.projects.mapIndexed { ind, project -> + if (ind == index) project.copy(description = value) else project + }, + errors = it.errors - ResumeField.ProjectDescription(index) + ) + } + } + fun submit() { viewModelScope.launch { val validation = validateDataUseCase.validateResume( @@ -242,10 +348,17 @@ class CreateResumeViewModel( about = about, skills = keySkills.toList(), experienceType = experience!!.mapToDomain(), - city = null, - experience = emptyList(), - education = emptyList(), - projects = emptyList() + city = city.ifBlank { null }, + experience = workExperience, + education = education.map { + Education( + place = it.place, + grade = it.grade.mapToDomain(), + specialization = it.specialization, + description = it.description + ) + }, + projects = projects ) } @@ -253,4 +366,4 @@ class CreateResumeViewModel( result.collectRequest(_resumeFillState) } } -} \ No newline at end of file +}