You've already forked RekomenciMobile
fixes
This commit is contained in:
+13
@@ -91,6 +91,19 @@ class ResumeRepositoryImpl(
|
||||
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>> =
|
||||
merge(
|
||||
resumeDao.getById(resumeId = resumeId).map { entity ->
|
||||
|
||||
+1
@@ -12,5 +12,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>
|
||||
suspend fun refreshResume(resumeId: String): 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(
|
||||
val id: String,
|
||||
val positionName: String,
|
||||
val salary: String
|
||||
val salary: String,
|
||||
val isPredictionLoading: Boolean
|
||||
)
|
||||
|
||||
fun ResumeModel.mapToBaseUIInfo(): UIResumeBaseInfo = UIResumeBaseInfo(
|
||||
id = id,
|
||||
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.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
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.register.RegisterScreen
|
||||
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.utils.ErrorCallbacks
|
||||
import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope
|
||||
import org.koin.compose.viewmodel.koinActivityViewModel
|
||||
|
||||
@Composable
|
||||
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.navigation.NavBackStackEntry
|
||||
@@ -11,11 +11,7 @@ import org.koin.core.parameter.parametersOf
|
||||
@Composable
|
||||
fun ErrorCollectorScope.EditResumeScreen(
|
||||
navBackStackEntry: NavBackStackEntry,
|
||||
viewModel: EditResumeViewModel = koinViewModel {
|
||||
parametersOf(
|
||||
navBackStackEntry.arguments?.getString(AppDestination.ResumeEdit.ARG_ID, "") ?: ""
|
||||
)
|
||||
},
|
||||
viewModel: EditResumeViewModel = koinViewModel(),
|
||||
openResumeDetails: (String) -> 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 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.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 kotlinx.coroutines.launch
|
||||
import org.koin.android.annotation.KoinViewModel
|
||||
import org.koin.core.annotation.Provided
|
||||
|
||||
@KoinViewModel
|
||||
class EditResumeViewModel(
|
||||
@@ -16,12 +17,16 @@ class EditResumeViewModel(
|
||||
suggestSkillsUseCase: SuggestSkillsUseCase,
|
||||
validateDataUseCase: ValidateFieldsUseCase,
|
||||
postResumeUseCase: PostResumeUseCase,
|
||||
@Provided private val resumeId: String
|
||||
savedStateHandle: SavedStateHandle
|
||||
) : CreateResumeViewModel(
|
||||
suggestSkillsUseCase = suggestSkillsUseCase,
|
||||
validateDataUseCase = validateDataUseCase,
|
||||
postResumeUseCase = postResumeUseCase
|
||||
) {
|
||||
private val resumeId: String =
|
||||
savedStateHandle.get<String>(AppDestination.ResumeEdit.ARG_ID)
|
||||
?: savedStateHandle.get<String>("id") ?: ""
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
getResumeInfoUseCase(resumeId).collect { result ->
|
||||
@@ -199,12 +199,16 @@ fun ResumeShortInfoCard(
|
||||
style = typography.labelLarge,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
Text(
|
||||
info.salary,
|
||||
style = typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
if (info.isPredictionLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(18.dp))
|
||||
} else {
|
||||
Text(
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -54,14 +55,12 @@ import org.koin.core.parameter.parametersOf
|
||||
@Composable
|
||||
fun ErrorCollectorScope.ResumeDetailsScreen(
|
||||
navBackStackEntry: NavBackStackEntry,
|
||||
// onHistory: () -> Unit,
|
||||
viewModel: ResumeDetailsViewModel = koinViewModel {
|
||||
parametersOf(
|
||||
navBackStackEntry.arguments?.getString(AppDestination.ResumeDetails.ARG_ID, "") ?: ""
|
||||
)
|
||||
}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val resumeState by viewModel.resumeState.collectAsStateWithCallbacks()
|
||||
|
||||
@@ -172,11 +171,25 @@ private fun ResumeDetailsContent(
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
SectionContainer {
|
||||
Text(
|
||||
"${resume.position} • ${resume.prediction.toSalaryRangeString()}",
|
||||
style = typography.titleLarge,
|
||||
fontSize = 28.sp
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(Paddings.small)
|
||||
) {
|
||||
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 = resume.city,
|
||||
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.usecase.resumes.GetResumeInfoUseCase
|
||||
import com.prodhack.moscow2025.domain.usecase.resumes.RefreshResumeUseCase
|
||||
import com.prodhack.moscow2025.presentation.utils.UIState
|
||||
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 org.koin.android.annotation.KoinViewModel
|
||||
import org.koin.core.annotation.Provided
|
||||
@@ -11,13 +15,40 @@ import org.koin.core.annotation.Provided
|
||||
@KoinViewModel
|
||||
class ResumeDetailsViewModel(
|
||||
@Provided resumeId: String,
|
||||
private val getResumeInfoUseCase: GetResumeInfoUseCase
|
||||
private val getResumeInfoUseCase: GetResumeInfoUseCase,
|
||||
private val refreshResumeUseCase: RefreshResumeUseCase
|
||||
) : BaseViewModel() {
|
||||
private val _resumeState = MutableUIStateFlow<ResumeModel>()
|
||||
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) {
|
||||
getResumeInfoUseCase(resumeId).collectRequest(_resumeState)
|
||||
viewModelScope.launch {
|
||||
resumeState.collect {
|
||||
val data = (it as? UIState.Success)?.data
|
||||
if (data?.prediction == null) {
|
||||
startPredictionPolling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
Reference in New Issue
Block a user