You've already forked RekomenciMobile
feat: edit resume
This commit is contained in:
@@ -217,9 +217,9 @@ data class ResumeCreateDTO(
|
||||
val position: String,
|
||||
@SerialName("location")
|
||||
val city: String,
|
||||
val experience: List<ExperienceDTO>,
|
||||
val education: List<EducationDTO>,
|
||||
val project: List<ProjectDTO>,
|
||||
val experience: List<ExperienceDTO>? = null,
|
||||
val education: List<EducationDTO>? = null,
|
||||
val project: List<ProjectDTO>? = null,
|
||||
)
|
||||
|
||||
fun ResumeCreationModel.mapToData(): ResumeCreateDTO = ResumeCreateDTO(
|
||||
|
||||
+16
@@ -74,6 +74,22 @@ class ResumeRepositoryImpl(
|
||||
contentType(ContentType.Application.Json)
|
||||
}.map { it.resumeId }
|
||||
|
||||
override suspend fun updateResume(
|
||||
resumeId: String,
|
||||
resumeForm: ResumeCreationModel
|
||||
): Result<String> = networkRequest<ResumeCreateDTO> {
|
||||
method = HttpMethod.Patch
|
||||
|
||||
url {
|
||||
path("resume", resumeId)
|
||||
}
|
||||
|
||||
setBody(resumeForm.mapToData())
|
||||
contentType(ContentType.Application.Json)
|
||||
}.map {
|
||||
resumeId
|
||||
}
|
||||
|
||||
override fun getResume(resumeId: String): Flow<Result<ResumeModel>> =
|
||||
merge(
|
||||
resumeDao.getById(resumeId = resumeId).map { entity ->
|
||||
|
||||
+1
-1
@@ -10,6 +10,6 @@ interface ResumeRepository {
|
||||
|
||||
suspend fun suggestSkills(query: String): Result<List<String>>
|
||||
suspend fun createResume(resumeForm: ResumeCreationModel): Result<String>
|
||||
|
||||
suspend fun updateResume(resumeId: String, resumeForm: ResumeCreationModel): Result<String>
|
||||
fun getResume(resumeId: String): Flow<Result<ResumeModel>>
|
||||
}
|
||||
|
||||
+10
-3
@@ -5,9 +5,16 @@ import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
@Single
|
||||
class CreateResumeUseCase(
|
||||
class PostResumeUseCase(
|
||||
private val resumeRepository: ResumeRepository
|
||||
) {
|
||||
suspend operator fun invoke(resumeForm: ResumeCreationModel): Result<String> =
|
||||
resumeRepository.createResume(resumeForm)
|
||||
suspend operator fun invoke(
|
||||
resumeForm: ResumeCreationModel,
|
||||
isNew: Boolean,
|
||||
resumeId: String?
|
||||
): Result<String> =
|
||||
if (isNew) resumeRepository.createResume(resumeForm) else resumeRepository.updateResume(
|
||||
resumeId!!,
|
||||
resumeForm
|
||||
)
|
||||
}
|
||||
@@ -22,5 +22,8 @@ sealed class AppDestination(val route: String) {
|
||||
}
|
||||
|
||||
data object ResumeCreation: AppDestination("resume/creation")
|
||||
}
|
||||
|
||||
data object ResumeEdit : AppDestination("resume/edit") {
|
||||
const val ARG_ID = "id"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
|
||||
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
||||
import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
|
||||
import com.prodhack.moscow2025.presentation.screens.resumeDetails.ResumeDetailsScreen
|
||||
import com.prodhack.moscow2025.presentation.screens.resumeDetails.EditResumeScreen
|
||||
import com.prodhack.moscow2025.presentation.utils.ErrorCallbacks
|
||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||
import org.koin.compose.viewmodel.koinActivityViewModel
|
||||
@@ -124,6 +125,18 @@ fun TTasksNavHost(
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
composable(AppDestination.ResumeEdit.route) {
|
||||
EditResumeScreen(navBackStackEntry = it, goBack = {
|
||||
navController.popBackStack()
|
||||
}, openResumeDetails = { id ->
|
||||
navController.navigate(AppDestination.ResumeDetails.route, Bundle().apply {
|
||||
putString(AppDestination.ResumeDetails.ARG_ID, id)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -52,7 +52,9 @@ import org.koin.androidx.compose.koinViewModel
|
||||
fun ErrorCollectorScope.CreateResumeScreen(
|
||||
goBack: () -> Unit,
|
||||
openResumeDetails: (String) -> Unit,
|
||||
viewModel: CreateResumeViewModel = koinViewModel()
|
||||
viewModel: CreateResumeViewModel = koinViewModel(),
|
||||
title: String = "Новое резюме",
|
||||
submitButtonText: String = "Узнать свою ЗП"
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val typography = MaterialTheme.typography
|
||||
@@ -79,7 +81,7 @@ fun ErrorCollectorScope.CreateResumeScreen(
|
||||
tint = colorScheme.onBackground,
|
||||
contentDescription = "go back"
|
||||
)
|
||||
Text(text = "Новое резюме", style = typography.titleLarge, fontSize = 24.sp)
|
||||
Text(text = title, style = typography.titleLarge, fontSize = 24.sp)
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
Column(
|
||||
@@ -295,14 +297,14 @@ fun ErrorCollectorScope.CreateResumeScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
|
||||
val resumeFillState = viewModel.resumeFillState.collectAsStateWithCallbacks {
|
||||
openResumeDetails(it)
|
||||
}
|
||||
BigButton(
|
||||
onClick = viewModel::submit,
|
||||
buttonText = "Узнать свою зарплату",
|
||||
isLoading = resumeFillState.value.isLoading
|
||||
)
|
||||
val resumeFillState = viewModel.resumeFillState.collectAsStateWithCallbacks {
|
||||
openResumeDetails(it)
|
||||
}
|
||||
BigButton(
|
||||
onClick = viewModel::submit,
|
||||
buttonText = submitButtonText,
|
||||
isLoading = resumeFillState.value.isLoading
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
}
|
||||
}
|
||||
|
||||
+28
-6
@@ -9,9 +9,10 @@ import com.prodhack.moscow2025.domain.models.ExperienceType
|
||||
import com.prodhack.moscow2025.domain.models.Project
|
||||
import com.prodhack.moscow2025.domain.models.ResumeCreationModel
|
||||
import com.prodhack.moscow2025.domain.models.ResumeField
|
||||
import com.prodhack.moscow2025.domain.models.ResumeModel
|
||||
import com.prodhack.moscow2025.domain.models.WorkExperience
|
||||
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.CreateResumeUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.PostResumeUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.SuggestSkillsUseCase
|
||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
|
||||
@@ -35,16 +36,18 @@ data class ResumeFormState(
|
||||
)
|
||||
|
||||
@KoinViewModel
|
||||
class CreateResumeViewModel(
|
||||
open class CreateResumeViewModel(
|
||||
private val suggestSkillsUseCase: SuggestSkillsUseCase,
|
||||
private val validateDataUseCase: ValidateFieldsUseCase,
|
||||
private val createResumeUseCase: CreateResumeUseCase
|
||||
private val postResumeUseCase: PostResumeUseCase
|
||||
) : BaseViewModel() {
|
||||
private val _formStateFillResume = MutableStateFlow(ResumeFormState())
|
||||
val formStateFillResume: StateFlow<ResumeFormState> = _formStateFillResume
|
||||
|
||||
private val _resumeFillState = MutableUIStateFlow<String>()
|
||||
val resumeFillState: StateFlow<UIState<String>> = _resumeFillState
|
||||
private var prefilled = false
|
||||
private var currId: String? = null
|
||||
|
||||
// Simple fields
|
||||
fun onAboutChange(value: String) {
|
||||
@@ -295,6 +298,24 @@ class CreateResumeViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun prefill(resume: ResumeModel) {
|
||||
if (prefilled) return
|
||||
prefilled = true
|
||||
currId = resume.id
|
||||
_formStateFillResume.update {
|
||||
it.copy(
|
||||
about = resume.about,
|
||||
position = resume.position,
|
||||
experience = resume.experienceType,
|
||||
keySkills = resume.skills.toSet(),
|
||||
city = resume.city,
|
||||
workExperience = resume.experience,
|
||||
education = resume.education,
|
||||
projects = resume.projects
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
viewModelScope.launch {
|
||||
val validation = validateDataUseCase.validateResume(
|
||||
@@ -315,7 +336,7 @@ class CreateResumeViewModel(
|
||||
|
||||
_resumeFillState.emit(UIState.Loading())
|
||||
|
||||
val result = createResumeUseCase(
|
||||
val result = postResumeUseCase(
|
||||
with(_formStateFillResume.value) {
|
||||
ResumeCreationModel(
|
||||
position = position,
|
||||
@@ -327,8 +348,9 @@ class CreateResumeViewModel(
|
||||
education = education,
|
||||
projects = projects
|
||||
)
|
||||
}
|
||||
|
||||
},
|
||||
isNew = prefilled.not(),
|
||||
resumeId = currId
|
||||
)
|
||||
result.collectRequest(_resumeFillState)
|
||||
}
|
||||
|
||||
@@ -16,10 +16,8 @@ import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -219,5 +217,3 @@ fun ResumeShortInfoCard(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.prodhack.moscow2025.presentation.screens.resumeDetails
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.prodhack.moscow2025.presentation.navigation.AppDestination
|
||||
import com.prodhack.moscow2025.presentation.screens.createResume.CreateResumeScreen
|
||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Composable
|
||||
fun ErrorCollectorScope.EditResumeScreen(
|
||||
navBackStackEntry: NavBackStackEntry,
|
||||
viewModel: EditResumeViewModel = koinViewModel {
|
||||
parametersOf(
|
||||
navBackStackEntry.arguments?.getString(AppDestination.ResumeEdit.ARG_ID, "") ?: ""
|
||||
)
|
||||
},
|
||||
openResumeDetails: (String) -> Unit,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
CreateResumeScreen(
|
||||
goBack = goBack,
|
||||
openResumeDetails = openResumeDetails,
|
||||
viewModel = viewModel,
|
||||
title = "Изменить резюме",
|
||||
submitButtonText = "Пересчитать"
|
||||
)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.prodhack.moscow2025.presentation.screens.resumeDetails
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.PostResumeUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.GetResumeInfoUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.SuggestSkillsUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.auth.ValidateFieldsUseCase
|
||||
import com.prodhack.moscow2025.presentation.screens.createResume.CreateResumeViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.annotation.KoinViewModel
|
||||
import org.koin.core.annotation.Provided
|
||||
|
||||
@KoinViewModel
|
||||
class EditResumeViewModel(
|
||||
private val getResumeInfoUseCase: GetResumeInfoUseCase,
|
||||
suggestSkillsUseCase: SuggestSkillsUseCase,
|
||||
validateDataUseCase: ValidateFieldsUseCase,
|
||||
postResumeUseCase: PostResumeUseCase,
|
||||
@Provided private val resumeId: String
|
||||
) : CreateResumeViewModel(
|
||||
suggestSkillsUseCase = suggestSkillsUseCase,
|
||||
validateDataUseCase = validateDataUseCase,
|
||||
postResumeUseCase = postResumeUseCase
|
||||
) {
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
getResumeInfoUseCase(resumeId).collect { result ->
|
||||
result.getOrNull()?.let { prefill(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-11
@@ -1,6 +1,5 @@
|
||||
package com.prodhack.moscow2025.presentation.screens.resumeDetails
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -19,10 +18,8 @@ import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -35,6 +32,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import android.os.Bundle
|
||||
import com.prodhack.moscow2025.R
|
||||
import com.prodhack.moscow2025.domain.models.Education
|
||||
import com.prodhack.moscow2025.domain.models.Project
|
||||
@@ -49,14 +47,14 @@ import com.prodhack.moscow2025.presentation.utils.toSalaryRangeString
|
||||
import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
|
||||
import com.prodhack.moscow2025.presentation.utils.ui.placeholders.ErrorPlaceholder
|
||||
import com.prodhack.moscow2025.presentation.utils.ui.placeholders.LoadingPlaceholder
|
||||
import com.prodhack.moscow2025.presentation.navigation.navigate
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Composable
|
||||
fun ErrorCollectorScope.ResumeDetailsScreen(
|
||||
navBackStackEntry: NavBackStackEntry,
|
||||
onEditResume: (String) -> Unit,
|
||||
onHistory: () -> Unit,
|
||||
// onHistory: () -> Unit,
|
||||
viewModel: ResumeDetailsViewModel = koinViewModel {
|
||||
parametersOf(
|
||||
navBackStackEntry.arguments?.getString(AppDestination.ResumeDetails.ARG_ID, "") ?: ""
|
||||
@@ -89,18 +87,18 @@ fun ErrorCollectorScope.ResumeDetailsScreen(
|
||||
ResumeDetailsContent(
|
||||
resume = resume,
|
||||
onBack = { navController.popBackStack() },
|
||||
onHistory = onHistory
|
||||
onHistory = {}
|
||||
)
|
||||
ExtendedFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = Paddings.large),
|
||||
onClick = {
|
||||
onEditResume(
|
||||
navBackStackEntry.arguments?.getString(
|
||||
AppDestination.ResumeDetails.ARG_ID,
|
||||
""
|
||||
) ?: ""
|
||||
navController.navigate(
|
||||
AppDestination.ResumeEdit.route,
|
||||
Bundle().apply {
|
||||
putString(AppDestination.ResumeEdit.ARG_ID, resume.id)
|
||||
}
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
|
||||
Reference in New Issue
Block a user