some fixes

This commit is contained in:
MaximOksiuta
2025-11-23 14:38:44 +03:00
parent afea49db37
commit 6fa0d11162
14 changed files with 416 additions and 139 deletions
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "153c4dcbf8d785dbfcd495eee39ae220",
"identityHash": "aac4b458e39f7bddd2a666a7b0645eb7",
"entities": [
{
"tableName": "users",
@@ -55,7 +55,7 @@
},
{
"tableName": "resumes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `experience_type` TEXT NOT NULL, `about_me` TEXT NOT NULL, `key_skills` TEXT NOT NULL, `position` TEXT NOT NULL, `from_salary` INTEGER, `to_salary` INTEGER, `recommended_skills` TEXT NOT NULL, `city` TEXT NOT NULL, `experience` TEXT NOT NULL, `education` TEXT NOT NULL, `projects` TEXT NOT NULL, PRIMARY KEY(`id`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `experience_type` TEXT NOT NULL, `about_me` TEXT NOT NULL, `key_skills` TEXT NOT NULL, `position` TEXT NOT NULL, `from_salary` REAL, `to_salary` REAL, `recommended_skills` TEXT NOT NULL, `city` TEXT NOT NULL, `experience` TEXT NOT NULL, `education` TEXT NOT NULL, `projects` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
@@ -90,12 +90,12 @@
{
"fieldPath": "fromSalary",
"columnName": "from_salary",
"affinity": "INTEGER"
"affinity": "REAL"
},
{
"fieldPath": "toSalary",
"columnName": "to_salary",
"affinity": "INTEGER"
"affinity": "REAL"
},
{
"fieldPath": "recommendedSkills",
@@ -137,14 +137,8 @@
},
{
"tableName": "resume_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `resume_id` TEXT NOT NULL, `experience_type` TEXT NOT NULL, `about_me` TEXT NOT NULL, `key_skills` TEXT NOT NULL, `position` TEXT NOT NULL, `from_salary` INTEGER, `to_salary` INTEGER, `recommended_skills` TEXT NOT NULL, `city` TEXT NOT NULL, `experience` TEXT NOT NULL, `education` TEXT NOT NULL, `projects` TEXT NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`resume_id` TEXT NOT NULL, `experience_type` TEXT NOT NULL, `about_me` TEXT NOT NULL, `key_skills` TEXT NOT NULL, `position` TEXT NOT NULL, `from_salary` REAL, `to_salary` REAL, `recommended_skills` TEXT NOT NULL, `city` TEXT NOT NULL, `experience` TEXT NOT NULL, `education` TEXT NOT NULL, `projects` TEXT NOT NULL, PRIMARY KEY(`resume_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "resumeId",
"columnName": "resume_id",
@@ -178,12 +172,12 @@
{
"fieldPath": "fromSalary",
"columnName": "from_salary",
"affinity": "INTEGER"
"affinity": "REAL"
},
{
"fieldPath": "toSalary",
"columnName": "to_salary",
"affinity": "INTEGER"
"affinity": "REAL"
},
{
"fieldPath": "recommendedSkills",
@@ -217,16 +211,16 @@
}
],
"primaryKey": {
"autoGenerate": true,
"autoGenerate": false,
"columnNames": [
"id"
"resume_id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '153c4dcbf8d785dbfcd495eee39ae220')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'aac4b458e39f7bddd2a666a7b0645eb7')"
]
}
}
@@ -18,9 +18,9 @@ data class ResumeEntity(
val keySkills: String,
val position: String,
@ColumnInfo("from_salary")
val fromSalary: Int?,
val fromSalary: Float?,
@ColumnInfo("to_salary")
val toSalary: Int?,
val toSalary: Float?,
@ColumnInfo("recommended_skills")
val recommendedSkills: String,
val city: String,
@@ -8,8 +8,7 @@ import com.prodhack.moscow2025.domain.models.ResumeModel
@Entity(tableName = "resume_history")
data class ResumeHistoryEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@PrimaryKey(autoGenerate = false)
@ColumnInfo("resume_id")
val resumeId: String,
@ColumnInfo("experience_type")
@@ -20,9 +19,9 @@ data class ResumeHistoryEntity(
val keySkills: String,
val position: String,
@ColumnInfo("from_salary")
val fromSalary: Int?,
val fromSalary: Float?,
@ColumnInfo("to_salary")
val toSalary: Int?,
val toSalary: Float?,
@ColumnInfo("recommended_skills")
val recommendedSkills: String,
val city: String,
@@ -60,6 +60,7 @@ data class ResumeDTO(
val city: String,
val experience: List<ExperienceDTO> = emptyList(),
val education: List<EducationDTO> = emptyList(),
@SerialName("projects")
val project: List<ProjectDTO> = emptyList(),
val prediction: PredictionDTO? = null
) {
@@ -71,8 +72,8 @@ data class ResumeDTO(
experienceType = experienceType.mapToDomain(),
prediction = prediction?.let {
Pair(
it.fromSalary.toIntOrNull(),
it.toSalary.toIntOrNull()
it.fromSalary.toFloatOrNull(),
it.toSalary.toFloatOrNull()
)
},
recommendedSkills = prediction?.recommendedSkills,
@@ -87,8 +88,8 @@ data class ResumeDTO(
aboutMe = aboutMe,
keySkills = keySkills.joinToString("|"),
position = position,
fromSalary = prediction?.fromSalary?.toIntOrNull(),
toSalary = prediction?.toSalary?.toIntOrNull(),
fromSalary = prediction?.fromSalary?.toFloatOrNull(),
toSalary = prediction?.toSalary?.toFloatOrNull(),
recommendedSkills = prediction?.recommendedSkills?.joinToString("|") ?: "",
experienceType = experienceType.mapToDomain().name,
city = city,
@@ -219,6 +220,7 @@ data class ResumeCreateDTO(
val city: String,
val experience: List<ExperienceDTO>? = null,
val education: List<EducationDTO>? = null,
@SerialName("projects")
val project: List<ProjectDTO>? = null,
)
@@ -10,7 +10,7 @@ data class ResumeModel(
val experience: List<WorkExperience>,
val education: List<Education>,
val projects: List<Project>,
val prediction: Pair<Int?, Int?>?,
val prediction: Pair<Float?, Float?>?,
val recommendedSkills: List<String>?
)
@@ -30,4 +30,9 @@ sealed class AppDestination(val route: String) {
data object ResumeEdit : AppDestination("resume/edit") {
const val ARG_ID = "id"
}
data object ResumeDiff : AppDestination("resume/diff") {
const val ARG_FIRST = "first_version"
const val ARG_SECOND = "second_version"
}
}
@@ -18,6 +18,7 @@ import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
import com.prodhack.moscow2025.presentation.screens.resumeDetails.ResumeDetailsScreen
import com.prodhack.moscow2025.presentation.screens.editResume.EditResumeScreen
import com.prodhack.moscow2025.presentation.screens.resumeHistory.ResumeHistoryScreen
import com.prodhack.moscow2025.presentation.screens.diffScreen.ResumeDiffScreen
import com.prodhack.moscow2025.presentation.utils.ErrorCallbacks
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
@@ -140,6 +141,10 @@ fun TTasksNavHost(
navController.popBackStack()
}
}
composable(AppDestination.ResumeDiff.route) {
ResumeDiffScreen(navBackStackEntry = it)
}
}
}
}
@@ -1,8 +1,170 @@
package com.prodhack.moscow2025.presentation.screens.diffScreen
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.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavBackStackEntry
import com.google.gson.Gson
import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.domain.models.ResumeModel
import com.prodhack.moscow2025.domain.usecase.resumes.CalculateResumeDiffUseCase
import com.prodhack.moscow2025.presentation.navigation.AppDestination
import com.prodhack.moscow2025.presentation.theme.Paddings
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
import com.prodhack.moscow2025.presentation.utils.toSalaryRangeString
import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
@Composable
fun ResumeDiffScreen(){
fun ErrorCollectorScope.ResumeDiffScreen(
navBackStackEntry: NavBackStackEntry,
calculateResumeDiffUseCase: CalculateResumeDiffUseCase = CalculateResumeDiffUseCase(),
onBack: () -> Unit = { navController.popBackStack() }
) {
val gson = remember { Gson() }
val firstJson = navBackStackEntry.arguments?.getString(AppDestination.ResumeDiff.ARG_FIRST)
val secondJson = navBackStackEntry.arguments?.getString(AppDestination.ResumeDiff.ARG_SECOND)
val first = remember(firstJson) { firstJson?.let { gson.fromJson(it, ResumeModel::class.java) } }
val second =
remember(secondJson) { secondJson?.let { gson.fromJson(it, ResumeModel::class.java) } }
if (first == null || second == null) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(Paddings.large),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Не удалось загрузить данные для сравнения")
}
return
}
val diff = remember(first, second) { calculateResumeDiffUseCase(first, second) }
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
.size(24.dp)
.rotate(180f)
.noRippleClickable(onBack),
painter = painterResource(R.drawable.ic_arr_details),
tint = MaterialTheme.colorScheme.onBackground,
contentDescription = "go back"
)
Text(
text = "Сравнение версий",
style = MaterialTheme.typography.titleLarge,
fontSize = 22.sp
)
Spacer(modifier = Modifier.size(24.dp))
}
Spacer(modifier = Modifier.height(Paddings.large))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(Paddings.medium)
) {
VersionSummaryCard(title = "Версия 1", resume = first)
VersionSummaryCard(title = "Версия 2", resume = second)
}
Spacer(modifier = Modifier.height(Paddings.large))
Text(
text = "Изменено:",
style = MaterialTheme.typography.titleMedium,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(Paddings.small))
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(Paddings.small),
verticalArrangement = Arrangement.spacedBy(Paddings.small)
) {
if (diff.changedFields.isEmpty()) {
Text("Изменений не найдено", style = MaterialTheme.typography.labelLarge)
} else {
diff.changedFields.forEach { field ->
Card(shape = MaterialTheme.shapes.small) {
Text(
modifier = Modifier.padding(horizontal = Paddings.medium, vertical = Paddings.small),
text = field,
style = MaterialTheme.typography.labelLarge
)
}
}
}
}
Spacer(modifier = Modifier.height(Paddings.large))
diff.changes.forEach { change ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = Paddings.small),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier.padding(Paddings.medium),
verticalArrangement = Arrangement.spacedBy(Paddings.small)
) {
Text(change.title, style = MaterialTheme.typography.titleMedium)
Text(change.body, style = MaterialTheme.typography.labelLarge)
}
}
}
}
}
@Composable
private fun VersionSummaryCard(title: String, resume: ResumeModel) {
Card(
modifier = Modifier,
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(Paddings.medium),
verticalArrangement = Arrangement.spacedBy(Paddings.small)
) {
Text(title, style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Bold)
Text(resume.position, style = MaterialTheme.typography.titleMedium)
Text(resume.prediction.toSalaryRangeString(), style = MaterialTheme.typography.labelLarge)
Text(resume.city, style = MaterialTheme.typography.labelMedium)
}
}
}
@@ -1,15 +1,17 @@
package com.prodhack.moscow2025.presentation.screens.diffScreen
import com.prodhack.moscow2025.domain.usecase.resumes.LoadHistoryUseCase
import androidx.paging.PagingData
import com.prodhack.moscow2025.domain.models.ResumeModel
import com.prodhack.moscow2025.domain.usecase.resumes.LoadResumeHistoryUseCase
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
import kotlinx.coroutines.flow.Flow
import org.koin.android.annotation.KoinViewModel
import org.koin.core.annotation.Provided
@KoinViewModel
class ResumeDiffViewModel(
@Provided resumeId: String,
private val loadHistoryUseCase: LoadHistoryUseCase
loadResumeHistoryUseCase: LoadResumeHistoryUseCase
) : BaseViewModel() {
val history: Flow<PagingData<ResumeModel>> = loadResumeHistoryUseCase(resumeId)
}
@@ -82,7 +82,7 @@ fun ErrorCollectorScope.ResumeDetailsScreen(
Box {
ResumeDetailsContent(
resume = resume,
onBack = { navController.popBackStack() },
onBack = { navController.navigate(AppDestination.Main.route) },
onHistory = {
navController.navigate(
AppDestination.ResumeHistory.route,
@@ -45,7 +45,7 @@ class ResumeDetailsViewModel(
resumeState.collect {
val data = (it as? UIState.Success)?.data
if (data?.prediction == null) {
// startPredictionPolling()
startPredictionPolling()
}
}
}
@@ -1,7 +1,9 @@
package com.prodhack.moscow2025.presentation.screens.resumeHistory
import android.os.Bundle
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
@@ -14,33 +16,32 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavBackStackEntry
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.gson.Gson
import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.domain.models.ResumeModel
import com.prodhack.moscow2025.domain.usecase.resumes.CalculateResumeDiffUseCase
import com.prodhack.moscow2025.presentation.components.standart.TBubble
import com.prodhack.moscow2025.presentation.navigation.AppDestination
import com.prodhack.moscow2025.presentation.navigation.navigate
import com.prodhack.moscow2025.presentation.theme.Paddings
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
import com.prodhack.moscow2025.presentation.utils.toReadableText
import com.prodhack.moscow2025.presentation.utils.toSalaryRangeString
import com.prodhack.moscow2025.presentation.utils.ui.noRippleClickable
import org.koin.androidx.compose.koinViewModel
@@ -62,11 +63,17 @@ fun ErrorCollectorScope.ResumeHistoryScreen(
val typography = MaterialTheme.typography
val colorScheme = MaterialTheme.colorScheme
val expandedState = remember { mutableStateMapOf<Int, Boolean>() }
val selected = remember { mutableStateMapOf<String, ResumeModel>() }
val selectedOrder = remember { mutableListOf<String>() }
Box(
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = Paddings.large)
.padding(horizontal = Paddings.large),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(Paddings.large))
Row(
@@ -93,6 +100,16 @@ fun ErrorCollectorScope.ResumeHistoryScreen(
Spacer(modifier = Modifier.height(Paddings.large))
viewModel.resumePosition.FoldUIStateWithGlobalCallbacks {
Text(
text = it,
style = typography.titleLarge,
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.height(Paddings.large))
LazyColumn(
verticalArrangement = Arrangement.spacedBy(Paddings.medium)
) {
@@ -100,14 +117,57 @@ fun ErrorCollectorScope.ResumeHistoryScreen(
val version = items[index] ?: return@items
val previous = if ((index + 1) < items.itemCount) items[index + 1] else null
val expanded = expandedState[index] ?: false
val isSelected = selected.contains(version.id)
val canSelectMore = isSelected || selected.size < 2
HistoryCard(
current = version,
previous = previous,
expanded = expanded,
onToggle = { expandedState[index] = !expanded },
calculateResumeDiffUseCase = calculateResumeDiffUseCase
calculateResumeDiffUseCase = calculateResumeDiffUseCase,
isSelected = isSelected,
enabled = canSelectMore,
onSelectToggle = {
if (isSelected) {
selected.remove(version.id)
selectedOrder.remove(version.id)
} else if (selected.size < 2) {
selected[version.id] = version
selectedOrder.add(version.id)
}
}
)
}
item { Spacer(modifier = Modifier.height(Paddings.large * 3)) }
}
}
if (selected.size == 2) {
ExtendedFloatingActionButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = Paddings.large),
onClick = {
val first = selected[selectedOrder.getOrNull(0)]
val second = selected[selectedOrder.getOrNull(1)]
val gson = Gson()
navController.navigate(
AppDestination.ResumeDiff.route,
Bundle().apply {
putString(AppDestination.ResumeDiff.ARG_FIRST, gson.toJson(first))
putString(AppDestination.ResumeDiff.ARG_SECOND, gson.toJson(second))
}
)
},
icon = {
Icon(
painter = painterResource(R.drawable.ic_checkmark),
contentDescription = "compare"
)
},
text = { Text(text = "Сравнить") },
)
}
}
}
@@ -119,7 +179,10 @@ private fun HistoryCard(
previous: ResumeModel?,
expanded: Boolean,
onToggle: () -> Unit,
calculateResumeDiffUseCase: CalculateResumeDiffUseCase
calculateResumeDiffUseCase: CalculateResumeDiffUseCase,
isSelected: Boolean,
enabled: Boolean,
onSelectToggle: () -> Unit
) {
val typography = MaterialTheme.typography
val colorScheme = MaterialTheme.colorScheme
@@ -128,15 +191,22 @@ private fun HistoryCard(
Card(
onClick = onToggle,
shape = MaterialTheme.shapes.medium
shape = MaterialTheme.shapes.medium,
enabled = enabled || isSelected
) {
Column(
Row(
modifier = Modifier
.fillMaxWidth()
.padding(Paddings.medium),
horizontalArrangement = Arrangement.spacedBy(Paddings.medium),
verticalAlignment = Alignment.Top
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(Paddings.small)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (previous != null) {
Text(
salaryDiff,
style = typography.titleMedium,
@@ -144,12 +214,14 @@ private fun HistoryCard(
fontSize = 20.sp
)
Spacer(modifier = Modifier.width(Paddings.small))
}
Text(
current.prediction.toSalaryRangeString(),
style = typography.labelLarge,
fontSize = 18.sp
)
}
if (previous != null) {
Text(
"Изменено:",
style = typography.titleMedium,
@@ -187,20 +259,40 @@ private fun HistoryCard(
}
}
}
} else {
Text(
"Первая версия",
style = typography.titleMedium,
color = colorScheme.primary,
fontSize = 20.sp
)
}
}
Checkbox(
checked = isSelected,
onCheckedChange = {
if (enabled || isSelected) onSelectToggle()
},
enabled = enabled || isSelected
)
}
}
}
private fun calculateSalaryDiff(prev: Pair<Int?, Int?>?, current: Pair<Int?, Int?>?): String {
private fun calculateSalaryDiff(
prev: Pair<Float?, Float?>?,
current: Pair<Float?, Float?>?
): String {
val prevAvg = prev?.let { listOfNotNull(it.first, it.second).averageOrNull() }
val currAvg = current?.let { listOfNotNull(it.first, it.second).averageOrNull() }
return if (prevAvg != null && currAvg != null) {
val diff = currAvg - prevAvg
val sign = if (diff >= 0) "+" else "-"
"${sign}${kotlin.math.abs(diff).toInt()}"
"${sign}${(kotlin.math.abs(diff).toInt() / 1000) * 1000}"
} else {
"н/д"
}
}
private fun List<Int>.averageOrNull(): Double? = if (isEmpty()) null else average()
private fun List<Float>.averageOrNull(): Double? = if (isEmpty()) null else average()
@@ -1,17 +1,32 @@
package com.prodhack.moscow2025.presentation.screens.resumeHistory
import androidx.compose.animation.core.updateTransition
import androidx.paging.PagingData
import com.prodhack.moscow2025.domain.models.ResumeModel
import com.prodhack.moscow2025.domain.usecase.resumes.GetResumeInfoUseCase
import com.prodhack.moscow2025.domain.usecase.resumes.LoadResumeHistoryUseCase
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.koin.android.annotation.KoinViewModel
import org.koin.core.annotation.Provided
@KoinViewModel
class ResumeHistoryViewModel(
@Provided resumeId: String,
loadResumeHistoryUseCase: LoadResumeHistoryUseCase
@Provided private val resumeId: String,
loadResumeHistoryUseCase: LoadResumeHistoryUseCase,
private val getResumeInfoUseCase: GetResumeInfoUseCase
) : BaseViewModel() {
val history: Flow<PagingData<ResumeModel>> = loadResumeHistoryUseCase(resumeId)
val resumePosition = MutableUIStateFlow<String>()
fun update() {
getResumeInfoUseCase(resumeId = resumeId)
.map { it -> it.map { it.position } }.collectRequest(resumePosition)
}
init {
update()
}
}
@@ -2,6 +2,7 @@ package com.prodhack.moscow2025.presentation.utils
import com.prodhack.moscow2025.domain.models.EducationGrades
import com.prodhack.moscow2025.domain.models.ExperienceType
import kotlin.math.roundToInt
fun ExperienceType.toReadableText(): String = when (this) {
ExperienceType.NoExperience -> "Нет опыта"
@@ -22,10 +23,10 @@ fun EducationGrades.toReadableText(): String = when (this) {
EducationGrades.Other -> "Другое"
}
fun Pair<Int?, Int?>?.toSalaryRangeString(): String = when {
fun Pair<Float?, Float?>?.toSalaryRangeString(): String = when {
this == null -> "Загрузка..."
first != null && second != null -> "${first}₽ - ${second}"
first != null -> "от ${first}"
second != null -> "до ${second}"
first != null && second != null -> "${(first!!.roundToInt() / 1000) * 1000}₽ - ${(second!!.roundToInt() / 1000) * 1000}"
first != null -> "от ${(first!!.roundToInt() / 1000) * 1000}"
second != null -> "до ${(second!!.roundToInt() / 1000) * 1000}"
else -> "Ошибка"
}