Coverage Summary for Class: TrainingViewModelImpl (com.vsevolodganin.clicktrack.training)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
TrainingViewModelImpl |
0%
(0/17)
|
0%
(0/22)
|
0%
(0/56)
|
0%
(0/407)
|
TrainingViewModelImpl$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/25)
|
TrainingViewModelImpl$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
TrainingViewModelImpl$onAcceptClick$1 |
0%
(0/1)
|
|
0%
(0/6)
|
0%
(0/92)
|
TrainingViewModelImpl$onAcceptClick$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/11)
|
TrainingViewModelImpl$onAcceptClick$1$1$invokeSuspend$$inlined$replaceCurrent$default$1 |
0%
(0/1)
|
|
TrainingViewModelImpl$onAcceptClick$1$1$invokeSuspend$$inlined$replaceCurrent$default$2 |
0%
(0/1)
|
|
TrainingViewModelImpl$onAcceptClick$1$suggestedName$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/14)
|
TrainingViewModelImpl$onBackClick$$inlined$pop$default$1 |
0%
(0/1)
|
|
TrainingViewModelImpl$onBackClick$$inlined$pop$default$2 |
0%
(0/1)
|
|
Total |
0%
(0/26)
|
0%
(0/22)
|
0%
(0/68)
|
0%
(0/555)
|
package com.vsevolodganin.clicktrack.training
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.replaceCurrent
import com.vsevolodganin.clicktrack.ScreenConfiguration
import com.vsevolodganin.clicktrack.ScreenStackNavigation
import com.vsevolodganin.clicktrack.common.NewClickTrackNameSuggester
import com.vsevolodganin.clicktrack.generated.resources.MR
import com.vsevolodganin.clicktrack.model.CueDuration
import com.vsevolodganin.clicktrack.model.DefaultBeatsDuration
import com.vsevolodganin.clicktrack.model.DefaultMeasuresDuration
import com.vsevolodganin.clicktrack.model.DefaultTimeDuration
import com.vsevolodganin.clicktrack.storage.ClickTrackRepository
import com.vsevolodganin.clicktrack.storage.UserPreferencesRepository
import com.vsevolodganin.clicktrack.utils.decompose.coroutineScope
import com.vsevolodganin.clicktrack.utils.optionalCast
import com.vsevolodganin.clicktrack.utils.resources.StringResolver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject
@Inject
class TrainingViewModelImpl(
@Assisted componentContext: ComponentContext,
private val stringResolver: StringResolver,
private val navigation: ScreenStackNavigation,
private val clickTrackRepository: ClickTrackRepository,
private val userPreferences: UserPreferencesRepository,
private val stateValidator: TrainingStateValidator,
private val newClickTrackNameSuggester: NewClickTrackNameSuggester,
private val trainingClickTrackGenerator: TrainingClickTrackGenerator,
) : TrainingViewModel, ComponentContext by componentContext {
private val scope = coroutineScope()
private val _state: MutableStateFlow<TrainingEditState> = MutableStateFlow(
userPreferences.trainingState.value.toEditState(),
)
override val state: StateFlow<TrainingEditState> = _state
init {
scope.launch {
_state
.drop(1)
.collectLatest(::onTrainingStateChange)
}
}
override fun onBackClick() = navigation.pop()
override fun onAcceptClick() {
scope.launch {
val trainingState = userPreferences.trainingState.flow.first()
val suggestedName = newClickTrackNameSuggester.suggest(
withContext(Dispatchers.Main) {
stringResolver.resolve(MR.strings.general_unnamed_training_click_track_template)
},
)
val newClickTrack = trainingClickTrackGenerator.generate(trainingState, suggestedName)
val newClickTrackId = clickTrackRepository.insert(newClickTrack)
withContext(Dispatchers.Main) {
navigation.replaceCurrent(ScreenConfiguration.PlayClickTrack(newClickTrackId))
}
}
}
override fun onStartingTempoChange(startingTempo: Int) {
reduceState { copy(startingTempo = startingTempo) }
}
override fun onModeSelect(mode: TrainingEditState.TrainingMode) {
reduceState { copy(mode = mode) }
}
override fun onSegmentLengthChange(segmentLength: CueDuration) {
reduceState {
when (segmentLength) {
is CueDuration.Beats -> copy(segmentLengthBeats = segmentLength)
is CueDuration.Measures -> copy(segmentLengthMeasures = segmentLength)
is CueDuration.Time -> copy(segmentLengthTime = segmentLength)
}
}
}
override fun onSegmentLengthTypeChange(segmentLengthType: CueDuration.Type) {
reduceState { copy(activeSegmentLengthType = segmentLengthType) }
}
override fun onTempoChangeChange(tempoChange: Int) {
reduceState { copy(tempoChange = tempoChange) }
}
override fun onEndingChange(ending: TrainingEditState.Ending) {
reduceState {
when (ending) {
is TrainingEditState.Ending.ByTempo -> copy(endingByTempo = ending)
is TrainingEditState.Ending.ByTime -> copy(endingByTime = ending)
}
}
}
override fun onEndingKindChange(endingKind: TrainingEndingKind) {
reduceState { copy(activeEndingKind = endingKind) }
}
private fun onTrainingStateChange(trainingState: TrainingEditState?) {
trainingState ?: return
val stateValidationResult = stateValidator.validate(trainingState)
stateValidationResult.persistableState?.let { persistableState ->
userPreferences.trainingState.value = persistableState
}
reduceState {
copy(errors = stateValidationResult.errors)
}
}
private fun reduceState(reduce: TrainingEditState.() -> TrainingEditState) {
_state.update { it.reduce() }
}
private fun TrainingValidState.toEditState(): TrainingEditState {
val endingEditState = ending.toEditState()
return TrainingEditState(
startingTempo = startingTempo.value,
mode = mode,
activeSegmentLengthType = segmentLength.type,
segmentLengthBeats = segmentLength.optionalCast<CueDuration.Beats>() ?: DefaultBeatsDuration,
segmentLengthMeasures = segmentLength.optionalCast<CueDuration.Measures>() ?: DefaultMeasuresDuration,
segmentLengthTime = segmentLength.optionalCast<CueDuration.Time>() ?: DefaultTimeDuration,
tempoChange = tempoChange.value,
activeEndingKind = endingEditState.kind,
endingByTempo = endingEditState.optionalCast<TrainingEditState.Ending.ByTempo>() ?: DefaultEndingByTempo,
endingByTime = endingEditState.optionalCast<TrainingEditState.Ending.ByTime>() ?: DefaultEndingByTime,
errors = emptySet(),
)
}
private fun TrainingValidState.Ending.toEditState(): TrainingEditState.Ending = when (this) {
is TrainingValidState.Ending.ByTempo -> TrainingEditState.Ending.ByTempo(endingTempo.value)
is TrainingValidState.Ending.ByTime -> TrainingEditState.Ending.ByTime(duration)
}
}