Commit 46b4ce28 by Aleksandr

open details screen after creating job

parent c39a2157
...@@ -130,7 +130,7 @@ class RenderUseCase @Inject constructor( ...@@ -130,7 +130,7 @@ class RenderUseCase @Inject constructor(
) )
SendJobWorker.create(context) SendJobWorker.create(context)
emit(FlowResult.Success(null)) emit(FlowResult.Success(job.id))
} }
} }
...@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable ...@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.isidroid.c23.ui._component.BackCloseComponent import com.isidroid.c23.ui._component.BackCloseComponent
import com.isidroid.c23.ui.navigation.routeJobDetails
import com.isidroid.c23.ui.navigation.routeMap import com.isidroid.c23.ui.navigation.routeMap
import com.isidroid.c23.ui.navigation.routeSelectContent import com.isidroid.c23.ui.navigation.routeSelectContent
import com.isidroid.c23.ui.screen.render_preview.RenderContract import com.isidroid.c23.ui.screen.render_preview.RenderContract
...@@ -32,6 +33,7 @@ fun RenderScreenDestination(navController: NavController) { ...@@ -32,6 +33,7 @@ fun RenderScreenDestination(navController: NavController) {
RenderContract.Effect.Navigation.ToMap -> navController.navigate(routeMap()) RenderContract.Effect.Navigation.ToMap -> navController.navigate(routeMap())
RenderContract.Effect.Navigation.ToSelectContent -> navController.navigateSingleTopTo(routeSelectContent()) RenderContract.Effect.Navigation.ToSelectContent -> navController.navigateSingleTopTo(routeSelectContent())
is RenderContract.Effect.Navigation.ToDetails -> navController.navigateSingleTopTo(routeJobDetails(effect.jobId))
} }
} }
) )
......
...@@ -28,6 +28,8 @@ class JobDetailsContract { ...@@ -28,6 +28,8 @@ class JobDetailsContract {
val lng: Double? = null, val lng: Double? = null,
val routeConfirmationVisible: Boolean = false, val routeConfirmationVisible: Boolean = false,
val requestLocationPermission: Boolean = false, val requestLocationPermission: Boolean = false,
val distance: String? = null val distance: String? = null,
val renderProgress: Pair<Int, Int>? = null,
val uploadProgress: Pair<Int, Int>? = null,
) : ViewState ) : ViewState
} }
\ No newline at end of file
...@@ -19,7 +19,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel ...@@ -19,7 +19,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
...@@ -36,7 +35,7 @@ class JobDetailsViewModel @Inject constructor( ...@@ -36,7 +35,7 @@ class JobDetailsViewModel @Inject constructor(
private val _spotResultStateFlow = MutableStateFlow<List<MapMarker>>(emptyList()) private val _spotResultStateFlow = MutableStateFlow<List<MapMarker>>(emptyList())
val spotResultStateFlow = _spotResultStateFlow.asStateFlow() val spotResultStateFlow = _spotResultStateFlow.asStateFlow()
private lateinit var jobId: String private lateinit var _jobId: String
init { init {
viewModelScope.launch { create(savedStateHandle) } viewModelScope.launch { create(savedStateHandle) }
...@@ -61,7 +60,7 @@ class JobDetailsViewModel @Inject constructor( ...@@ -61,7 +60,7 @@ class JobDetailsViewModel @Inject constructor(
savedStateHandle.getStateFlow<String?>(Argument.ID, null) savedStateHandle.getStateFlow<String?>(Argument.ID, null)
.filterNotNull() .filterNotNull()
.onEach { .onEach {
jobId = it _jobId = it
loadDetails() loadDetails()
} }
.singleOrNull() .singleOrNull()
...@@ -69,12 +68,18 @@ class JobDetailsViewModel @Inject constructor( ...@@ -69,12 +68,18 @@ class JobDetailsViewModel @Inject constructor(
private suspend fun listenSendJobEvents() { private suspend fun listenSendJobEvents() {
sendJobEventCollectorFlow.eventsFlow sendJobEventCollectorFlow.eventsFlow
.filterIsInstance<JobSenderResult.Statuses>() .collect { st ->
.collect { st -> updateStatuses(st) } when (st) {
is JobSenderResult.Error -> {}
is JobSenderResult.RenderProgress -> onRenderProgress(st.jobId, st.position, st.total)
is JobSenderResult.Statuses -> updateStatuses(st)
is JobSenderResult.UploadProgress -> onUploadProgress(st.jobId, st.position, st.total)
}
}
} }
private suspend fun loadDetails() { private suspend fun loadDetails() {
useCase.loadDetails(jobId) useCase.loadDetails(_jobId)
.flowOn(Dispatchers.IO) .flowOn(Dispatchers.IO)
.catchTimber { .catchTimber {
when (it) { when (it) {
...@@ -116,4 +121,14 @@ class JobDetailsViewModel @Inject constructor( ...@@ -116,4 +121,14 @@ class JobDetailsViewModel @Inject constructor(
val updated = useCase.updateStatuses(viewState.value.printJob, info = statuses) ?: return val updated = useCase.updateStatuses(viewState.value.printJob, info = statuses) ?: return
setState { copy(printJob = updated) } setState { copy(printJob = updated) }
} }
private fun onRenderProgress(jobId: String, position: Int, total: Int) {
if (jobId != _jobId) return
setState { copy(renderProgress = Pair(position, total), uploadProgress = null) }
}
private fun onUploadProgress(jobId: String, position: Int, total: Int) {
if (jobId != _jobId) return
setState { copy(uploadProgress = Pair(position, total), renderProgress = null) }
}
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ package com.isidroid.c23.ui.screen.details.component ...@@ -3,6 +3,7 @@ package com.isidroid.c23.ui.screen.details.component
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
...@@ -10,21 +11,27 @@ import androidx.compose.foundation.layout.fillMaxWidth ...@@ -10,21 +11,27 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
...@@ -38,6 +45,7 @@ import com.isidroid.ui.maps.model.MapMarker ...@@ -38,6 +45,7 @@ import com.isidroid.ui.maps.model.MapMarker
import com.isidroid.utils.asCost import com.isidroid.utils.asCost
import com.isidroid.utils.spaceInCenter import com.isidroid.utils.spaceInCenter
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
...@@ -67,6 +75,9 @@ fun DetailsV2( ...@@ -67,6 +75,9 @@ fun DetailsV2(
) { ) {
PrintInfoComponent(printJob) PrintInfoComponent(printJob)
ProgressComponentState(state)
HorizontalDivider(Modifier.padding(vertical = 16.dp)) HorizontalDivider(Modifier.padding(vertical = 16.dp))
SpotInfoComponent( SpotInfoComponent(
lat = state.value.lat, lat = state.value.lat,
...@@ -84,6 +95,65 @@ fun DetailsV2( ...@@ -84,6 +95,65 @@ fun DetailsV2(
} }
@Composable @Composable
private fun ProgressComponentState(
state: State<JobDetailsContract.State>,
modifier: Modifier = Modifier
) {
val renderProgress = state.value.renderProgress
val uploadProgress = state.value.uploadProgress
val index = renderProgress?.first ?: uploadProgress?.first ?: return
val total = renderProgress?.second ?: uploadProgress?.second ?: return
ProgressComponent(index, total, isRender = renderProgress != null, modifier)
}
@Composable
private fun ProgressComponent(
index: Int,
total: Int,
isRender: Boolean,
modifier: Modifier = Modifier
) {
val progress = index.toFloat() / total.toFloat()
val title = if (isRender) R.string.progress_rendering else R.string.progress_uploading
Timber.i("===> progress=$index/$total, progress=$progress")
Column(
modifier = modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.End
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, bottom = 2.dp)
) {
Text(
text = stringResource(id = title),
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.labelLarge
)
Text(
text = "$index/$total",
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Medium
)
}
LinearProgressIndicator(
progress = progress,
modifier = Modifier.fillMaxWidth()
)
}
}
@Composable
private fun PrintInfoComponent(printJob: PrintJobListItem) { private fun PrintInfoComponent(printJob: PrintJobListItem) {
val textStrings = listOfNotNull( val textStrings = listOfNotNull(
"PIN code: ${printJob.accessCode.spaceInCenter()}", "PIN code: ${printJob.accessCode.spaceInCenter()}",
......
...@@ -34,11 +34,11 @@ class RenderContract { ...@@ -34,11 +34,11 @@ class RenderContract {
} }
sealed interface Effect : ViewSideEffect { sealed interface Effect : ViewSideEffect {
data object PrintJobCreated: Effect
sealed interface Navigation : Effect { sealed interface Navigation : Effect {
data object ToSelectContent: Navigation data object ToSelectContent: Navigation
data object ToMap : Navigation data object ToMap : Navigation
data object ToBack : Navigation data object ToBack : Navigation
data class ToDetails(val jobId: String): Navigation
} }
} }
......
...@@ -80,7 +80,6 @@ fun RenderPreviewScreen( ...@@ -80,7 +80,6 @@ fun RenderPreviewScreen(
effectFlow?.collect { effect -> effectFlow?.collect { effect ->
when (effect) { when (effect) {
is RenderContract.Effect.Navigation -> onNavigationRequested(effect) is RenderContract.Effect.Navigation -> onNavigationRequested(effect)
RenderContract.Effect.PrintJobCreated -> Toast.makeText(context, R.string.print_job_sending, Toast.LENGTH_SHORT).show()
} }
} }
} }
......
...@@ -186,7 +186,7 @@ class RenderViewModel @Inject constructor( ...@@ -186,7 +186,7 @@ class RenderViewModel @Inject constructor(
.collect { res -> .collect { res ->
when (res) { when (res) {
FlowResult.Loading -> setState { copy(disablePrintButton = true) } FlowResult.Loading -> setState { copy(disablePrintButton = true) }
is FlowResult.Success -> onPrintJobCreated() is FlowResult.Success -> onPrintJobCreated(res.result)
} }
} }
} }
...@@ -234,8 +234,7 @@ class RenderViewModel @Inject constructor( ...@@ -234,8 +234,7 @@ class RenderViewModel @Inject constructor(
_renderResults.clear() _renderResults.clear()
} }
private fun onPrintJobCreated(){ private fun onPrintJobCreated(jobId: String) {
setEffect { RenderContract.Effect.PrintJobCreated } setEffect { RenderContract.Effect.Navigation.ToDetails(jobId) }
setEffect { RenderContract.Effect.Navigation.ToSelectContent }
} }
} }
\ No newline at end of file
...@@ -44,4 +44,6 @@ ...@@ -44,4 +44,6 @@
<string name="print_job_status_sending">Sending</string> <string name="print_job_status_sending">Sending</string>
<string name="pin_code">Pin code</string> <string name="pin_code">Pin code</string>
<string name="price">Price</string> <string name="price">Price</string>
<string name="progress_rendering">Rendering progress</string>
<string name="progress_uploading">Uploading progress</string>
</resources> </resources>
\ No newline at end of file
...@@ -16,8 +16,8 @@ class SendJobEventCollectorFlow @Inject constructor() { ...@@ -16,8 +16,8 @@ class SendJobEventCollectorFlow @Inject constructor() {
private suspend fun emit(result: JobSenderResult) = _eventsFlow.emit(result) private suspend fun emit(result: JobSenderResult) = _eventsFlow.emit(result)
.also { Timber.i("call: $result") } .also { Timber.i("call: $result") }
suspend fun renderProgress(index: Int, total: Int) = emit(JobSenderResult.RenderProgress(index, total)) suspend fun renderProgress(jobId: String, index: Int, total: Int) = emit(JobSenderResult.RenderProgress(jobId, index, total))
suspend fun updateProgress(index: Int, total: Int) = emit(JobSenderResult.UploadProgress(index, total)) suspend fun uploadProgress(jobId: String, index: Int, total: Int) = emit(JobSenderResult.UploadProgress(jobId, index, total))
suspend fun jobError(jobId: String, t: Throwable) = emit(JobSenderResult.Error(jobId, t)) suspend fun jobError(jobId: String, t: Throwable) = emit(JobSenderResult.Error(jobId, t))
suspend fun updateStatus(@JobStatus status: Int, vararg jobIds: String?) = emit(JobSenderResult.Statuses(status = status, jobIds = jobIds.toSet())) suspend fun updateStatus(@JobStatus status: Int, vararg jobIds: String?) = emit(JobSenderResult.Statuses(status = status, jobIds = jobIds.toSet()))
......
...@@ -3,8 +3,8 @@ package com.isidroid.job_sender.domain.dto ...@@ -3,8 +3,8 @@ package com.isidroid.job_sender.domain.dto
import com.isidroid.job.constant.JobStatus import com.isidroid.job.constant.JobStatus
sealed interface JobSenderResult { sealed interface JobSenderResult {
data class RenderProgress(val position: Int, val total: Int) : JobSenderResult data class RenderProgress(val jobId: String, val position: Int, val total: Int) : JobSenderResult
data class UploadProgress(val position: Int, val total: Int) : JobSenderResult data class UploadProgress(val jobId: String, val position: Int, val total: Int) : JobSenderResult
data class Error(val jobId: String, val t: Throwable) : JobSenderResult data class Error(val jobId: String, val t: Throwable) : JobSenderResult
data class Statuses(@JobStatus val status: Int, val jobIds: Collection<String?>): JobSenderResult data class Statuses(@JobStatus val status: Int, val jobIds: Collection<String?>) : JobSenderResult
} }
\ No newline at end of file
...@@ -93,13 +93,14 @@ internal suspend fun createRenderItems( ...@@ -93,13 +93,14 @@ internal suspend fun createRenderItems(
jobIdleList: Collection<PrintJob> jobIdleList: Collection<PrintJob>
): Collection<PrintJobSender> { ): Collection<PrintJobSender> {
val richSpots = spotRepository.findLocalRichSpots(ids = jobIdleList.map { it.spotId }) val richSpots = spotRepository.findLocalRichSpots(ids = jobIdleList.map { it.spotId })
var index = 0
val total = jobIdleList.size
val result = jobIdleList.mapNotNull { printJob -> val result = jobIdleList.mapNotNull { printJob ->
val jobTotal = printJob.sourceFiles.orEmpty().size
var jobIndex = 0
printJob.sourceFiles?.mapNotNull { source -> printJob.sourceFiles?.mapNotNull { source ->
createSingleRender(context, renderRepository, richSpots, printJob, source) createSingleRender(context, renderRepository, richSpots, printJob, source)
.also { eventCollector.renderProgress(++index, total) } .also { eventCollector.renderProgress(printJob.id, ++jobIndex, jobTotal) }
} }
}.flatten() }.flatten()
......
...@@ -59,7 +59,7 @@ internal class JobSendRepositoryImpl( ...@@ -59,7 +59,7 @@ internal class JobSendRepositoryImpl(
val sendJobResults = items.groupBy { it.printJobId }.mapValues { it.value.size }.toMutableMap() val sendJobResults = items.groupBy { it.printJobId }.mapValues { it.value.size }.toMutableMap()
for ((index, item) in items.withIndex()) { for ((index, item) in items.withIndex()) {
eventCollector.updateProgress(index, total) eventCollector.uploadProgress(item.printJobId, index, total)
try { try {
updateStatus(item, SendJobStatus.SENDING) updateStatus(item, SendJobStatus.SENDING)
...@@ -82,8 +82,6 @@ internal class JobSendRepositoryImpl( ...@@ -82,8 +82,6 @@ internal class JobSendRepositoryImpl(
} }
} }
eventCollector.updateProgress(total, total)
// update jobs // update jobs
val successJobIds = sendJobResults.filter { it.value == 0 }.keys val successJobIds = sendJobResults.filter { it.value == 0 }.keys
printJobRepository.updateJobStatus(status = JobStatus.RENDER_UPLOAD, *successJobIds.toTypedArray()) printJobRepository.updateJobStatus(status = JobStatus.RENDER_UPLOAD, *successJobIds.toTypedArray())
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment