You've already forked RekomenciMobile
fixes
This commit is contained in:
+13
@@ -91,6 +91,19 @@ class ResumeRepositoryImpl(
|
|||||||
resumeId
|
resumeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun refreshResume(resumeId: String): Result<ResumeModel> =
|
||||||
|
networkRequest<ResumeDTO> {
|
||||||
|
method = HttpMethod.Get
|
||||||
|
url {
|
||||||
|
path("resume", resumeId)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
it.mapToDomain().also { model ->
|
||||||
|
resumeDao.upsertAll(listOf(it.mapToDB()))
|
||||||
|
resumeHistoryDao.upsertAll(listOf(ResumeHistoryEntity.fromDomain(model)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getResume(resumeId: String): Flow<Result<ResumeModel>> =
|
override fun getResume(resumeId: String): Flow<Result<ResumeModel>> =
|
||||||
merge(
|
merge(
|
||||||
resumeDao.getById(resumeId = resumeId).map { entity ->
|
resumeDao.getById(resumeId = resumeId).map { entity ->
|
||||||
|
|||||||
+1
@@ -12,5 +12,6 @@ interface ResumeRepository {
|
|||||||
suspend fun suggestSkills(query: String): Result<List<String>>
|
suspend fun suggestSkills(query: String): Result<List<String>>
|
||||||
suspend fun createResume(resumeForm: ResumeCreationModel): Result<String>
|
suspend fun createResume(resumeForm: ResumeCreationModel): Result<String>
|
||||||
suspend fun updateResume(resumeId: String, resumeForm: ResumeCreationModel): Result<String>
|
suspend fun updateResume(resumeId: String, resumeForm: ResumeCreationModel): Result<String>
|
||||||
|
suspend fun refreshResume(resumeId: String): Result<ResumeModel>
|
||||||
fun getResume(resumeId: String): Flow<Result<ResumeModel>>
|
fun getResume(resumeId: String): Flow<Result<ResumeModel>>
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.prodhack.moscow2025.domain.usecase.resumes
|
||||||
|
|
||||||
|
import com.prodhack.moscow2025.domain.interfaces.resumes.ResumeRepository
|
||||||
|
import com.prodhack.moscow2025.domain.models.ResumeModel
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class RefreshResumeUseCase(
|
||||||
|
private val resumeRepository: ResumeRepository
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(resumeId: String): Result<ResumeModel> =
|
||||||
|
resumeRepository.refreshResume(resumeId)
|
||||||
|
}
|
||||||
+4
-2
@@ -6,11 +6,13 @@ import com.prodhack.moscow2025.presentation.utils.toSalaryRangeString
|
|||||||
data class UIResumeBaseInfo(
|
data class UIResumeBaseInfo(
|
||||||
val id: String,
|
val id: String,
|
||||||
val positionName: String,
|
val positionName: String,
|
||||||
val salary: String
|
val salary: String,
|
||||||
|
val isPredictionLoading: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ResumeModel.mapToBaseUIInfo(): UIResumeBaseInfo = UIResumeBaseInfo(
|
fun ResumeModel.mapToBaseUIInfo(): UIResumeBaseInfo = UIResumeBaseInfo(
|
||||||
id = id,
|
id = id,
|
||||||
positionName = position,
|
positionName = position,
|
||||||
salary = prediction.toSalaryRangeString()
|
salary = prediction.toSalaryRangeString(),
|
||||||
|
isPredictionLoading = prediction == null
|
||||||
)
|
)
|
||||||
@@ -5,7 +5,6 @@ import android.os.Bundle
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
@@ -17,11 +16,10 @@ import com.prodhack.moscow2025.presentation.screens.login.LoginScreen
|
|||||||
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
import com.prodhack.moscow2025.presentation.screens.profile.ProfileScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
|
import com.prodhack.moscow2025.presentation.screens.register.RegisterScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.resumeDetails.ResumeDetailsScreen
|
import com.prodhack.moscow2025.presentation.screens.resumeDetails.ResumeDetailsScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.resumeDetails.EditResumeScreen
|
import com.prodhack.moscow2025.presentation.screens.editResume.EditResumeScreen
|
||||||
import com.prodhack.moscow2025.presentation.screens.resumeHistory.ResumeHistoryScreen
|
import com.prodhack.moscow2025.presentation.screens.resumeHistory.ResumeHistoryScreen
|
||||||
import com.prodhack.moscow2025.presentation.utils.ErrorCallbacks
|
import com.prodhack.moscow2025.presentation.utils.ErrorCallbacks
|
||||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||||
import org.koin.compose.viewmodel.koinActivityViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TTasksNavHost(
|
fun TTasksNavHost(
|
||||||
|
|||||||
+2
-6
@@ -1,4 +1,4 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.resumeDetails
|
package com.prodhack.moscow2025.presentation.screens.editResume
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
@@ -11,11 +11,7 @@ import org.koin.core.parameter.parametersOf
|
|||||||
@Composable
|
@Composable
|
||||||
fun ErrorCollectorScope.EditResumeScreen(
|
fun ErrorCollectorScope.EditResumeScreen(
|
||||||
navBackStackEntry: NavBackStackEntry,
|
navBackStackEntry: NavBackStackEntry,
|
||||||
viewModel: EditResumeViewModel = koinViewModel {
|
viewModel: EditResumeViewModel = koinViewModel(),
|
||||||
parametersOf(
|
|
||||||
navBackStackEntry.arguments?.getString(AppDestination.ResumeEdit.ARG_ID, "") ?: ""
|
|
||||||
)
|
|
||||||
},
|
|
||||||
openResumeDetails: (String) -> Unit,
|
openResumeDetails: (String) -> Unit,
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
+11
-6
@@ -1,14 +1,15 @@
|
|||||||
package com.prodhack.moscow2025.presentation.screens.resumeDetails
|
package com.prodhack.moscow2025.presentation.screens.editResume
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
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.domain.usecase.auth.ValidateFieldsUseCase
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.resumes.GetResumeInfoUseCase
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.resumes.PostResumeUseCase
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.resumes.SuggestSkillsUseCase
|
||||||
|
import com.prodhack.moscow2025.presentation.navigation.AppDestination
|
||||||
import com.prodhack.moscow2025.presentation.screens.createResume.CreateResumeViewModel
|
import com.prodhack.moscow2025.presentation.screens.createResume.CreateResumeViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.annotation.KoinViewModel
|
import org.koin.android.annotation.KoinViewModel
|
||||||
import org.koin.core.annotation.Provided
|
|
||||||
|
|
||||||
@KoinViewModel
|
@KoinViewModel
|
||||||
class EditResumeViewModel(
|
class EditResumeViewModel(
|
||||||
@@ -16,12 +17,16 @@ class EditResumeViewModel(
|
|||||||
suggestSkillsUseCase: SuggestSkillsUseCase,
|
suggestSkillsUseCase: SuggestSkillsUseCase,
|
||||||
validateDataUseCase: ValidateFieldsUseCase,
|
validateDataUseCase: ValidateFieldsUseCase,
|
||||||
postResumeUseCase: PostResumeUseCase,
|
postResumeUseCase: PostResumeUseCase,
|
||||||
@Provided private val resumeId: String
|
savedStateHandle: SavedStateHandle
|
||||||
) : CreateResumeViewModel(
|
) : CreateResumeViewModel(
|
||||||
suggestSkillsUseCase = suggestSkillsUseCase,
|
suggestSkillsUseCase = suggestSkillsUseCase,
|
||||||
validateDataUseCase = validateDataUseCase,
|
validateDataUseCase = validateDataUseCase,
|
||||||
postResumeUseCase = postResumeUseCase
|
postResumeUseCase = postResumeUseCase
|
||||||
) {
|
) {
|
||||||
|
private val resumeId: String =
|
||||||
|
savedStateHandle.get<String>(AppDestination.ResumeEdit.ARG_ID)
|
||||||
|
?: savedStateHandle.get<String>("id") ?: ""
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
getResumeInfoUseCase(resumeId).collect { result ->
|
getResumeInfoUseCase(resumeId).collect { result ->
|
||||||
@@ -199,12 +199,16 @@ fun ResumeShortInfoCard(
|
|||||||
style = typography.labelLarge,
|
style = typography.labelLarge,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
Text(
|
if (info.isPredictionLoading) {
|
||||||
info.salary,
|
CircularProgressIndicator(modifier = Modifier.size(18.dp))
|
||||||
style = typography.titleMedium,
|
} else {
|
||||||
color = MaterialTheme.colorScheme.primary,
|
Text(
|
||||||
fontSize = 18.sp
|
info.salary,
|
||||||
)
|
style = typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-7
@@ -21,6 +21,7 @@ import androidx.compose.material3.ExtendedFloatingActionButton
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -54,14 +55,12 @@ import org.koin.core.parameter.parametersOf
|
|||||||
@Composable
|
@Composable
|
||||||
fun ErrorCollectorScope.ResumeDetailsScreen(
|
fun ErrorCollectorScope.ResumeDetailsScreen(
|
||||||
navBackStackEntry: NavBackStackEntry,
|
navBackStackEntry: NavBackStackEntry,
|
||||||
// onHistory: () -> Unit,
|
|
||||||
viewModel: ResumeDetailsViewModel = koinViewModel {
|
viewModel: ResumeDetailsViewModel = koinViewModel {
|
||||||
parametersOf(
|
parametersOf(
|
||||||
navBackStackEntry.arguments?.getString(AppDestination.ResumeDetails.ARG_ID, "") ?: ""
|
navBackStackEntry.arguments?.getString(AppDestination.ResumeDetails.ARG_ID, "") ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val resumeState by viewModel.resumeState.collectAsStateWithCallbacks()
|
val resumeState by viewModel.resumeState.collectAsStateWithCallbacks()
|
||||||
|
|
||||||
@@ -172,11 +171,25 @@ private fun ResumeDetailsContent(
|
|||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(Paddings.large))
|
Spacer(modifier = Modifier.height(Paddings.large))
|
||||||
SectionContainer {
|
SectionContainer {
|
||||||
Text(
|
Row(
|
||||||
"${resume.position} • ${resume.prediction.toSalaryRangeString()}",
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
style = typography.titleLarge,
|
horizontalArrangement = Arrangement.spacedBy(Paddings.small)
|
||||||
fontSize = 28.sp
|
) {
|
||||||
)
|
Text(
|
||||||
|
resume.position,
|
||||||
|
style = typography.titleLarge,
|
||||||
|
fontSize = 28.sp
|
||||||
|
)
|
||||||
|
if (resume.prediction == null) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.size(18.dp))
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
resume.prediction.toSalaryRangeString(),
|
||||||
|
style = typography.titleMedium,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = resume.city,
|
text = resume.city,
|
||||||
style = typography.labelLarge,
|
style = typography.labelLarge,
|
||||||
|
|||||||
+32
-1
@@ -2,8 +2,12 @@ package com.prodhack.moscow2025.presentation.screens.resumeDetails
|
|||||||
|
|
||||||
import com.prodhack.moscow2025.domain.models.ResumeModel
|
import com.prodhack.moscow2025.domain.models.ResumeModel
|
||||||
import com.prodhack.moscow2025.domain.usecase.resumes.GetResumeInfoUseCase
|
import com.prodhack.moscow2025.domain.usecase.resumes.GetResumeInfoUseCase
|
||||||
|
import com.prodhack.moscow2025.domain.usecase.resumes.RefreshResumeUseCase
|
||||||
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 androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.koin.android.annotation.KoinViewModel
|
import org.koin.android.annotation.KoinViewModel
|
||||||
import org.koin.core.annotation.Provided
|
import org.koin.core.annotation.Provided
|
||||||
@@ -11,13 +15,40 @@ import org.koin.core.annotation.Provided
|
|||||||
@KoinViewModel
|
@KoinViewModel
|
||||||
class ResumeDetailsViewModel(
|
class ResumeDetailsViewModel(
|
||||||
@Provided resumeId: String,
|
@Provided resumeId: String,
|
||||||
private val getResumeInfoUseCase: GetResumeInfoUseCase
|
private val getResumeInfoUseCase: GetResumeInfoUseCase,
|
||||||
|
private val refreshResumeUseCase: RefreshResumeUseCase
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
private val _resumeState = MutableUIStateFlow<ResumeModel>()
|
private val _resumeState = MutableUIStateFlow<ResumeModel>()
|
||||||
val resumeState: StateFlow<UIState<ResumeModel>> = _resumeState
|
val resumeState: StateFlow<UIState<ResumeModel>> = _resumeState
|
||||||
|
private val id = resumeId
|
||||||
|
private var pollingStarted = false
|
||||||
|
|
||||||
|
private fun startPredictionPolling() {
|
||||||
|
if (pollingStarted) return
|
||||||
|
pollingStarted = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
while (true) {
|
||||||
|
val current = (_resumeState.value as? UIState.Success)?.data
|
||||||
|
if (current?.prediction?.first != null || current?.prediction?.second != null) break
|
||||||
|
delay(2000)
|
||||||
|
val refreshed = refreshResumeUseCase(id)
|
||||||
|
if (refreshed.isSuccess) {
|
||||||
|
_resumeState.value = UIState.Success(refreshed.getOrNull()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun loadResume(resumeId: String) {
|
fun loadResume(resumeId: String) {
|
||||||
getResumeInfoUseCase(resumeId).collectRequest(_resumeState)
|
getResumeInfoUseCase(resumeId).collectRequest(_resumeState)
|
||||||
|
viewModelScope.launch {
|
||||||
|
resumeState.collect {
|
||||||
|
val data = (it as? UIState.Success)?.data
|
||||||
|
if (data?.prediction == null) {
|
||||||
|
startPredictionPolling()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
Reference in New Issue
Block a user