Coverage Summary for Class: TrainingScreenViewKt (com.vsevolodganin.clicktrack.ui.screen)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
TrainingScreenViewKt |
0%
(0/18)
|
2.6%
(1/38)
|
0%
(0/90)
|
1.4%
(16/1114)
|
TrainingScreenViewKt$WhenMappings |
|
Total |
0%
(0/18)
|
2.6%
(1/38)
|
0%
(0/90)
|
1.4%
(16/1114)
|
package com.vsevolodganin.clicktrack.ui.screen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.FabPosition
import androidx.compose.material.Icon
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
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.training.TrainingEditState
import com.vsevolodganin.clicktrack.training.TrainingEditState.TrainingMode
import com.vsevolodganin.clicktrack.training.TrainingEndingKind
import com.vsevolodganin.clicktrack.training.TrainingViewModel
import com.vsevolodganin.clicktrack.ui.piece.BpmInputField
import com.vsevolodganin.clicktrack.ui.piece.CueDurationView
import com.vsevolodganin.clicktrack.ui.piece.DropdownSelector
import com.vsevolodganin.clicktrack.ui.piece.DurationPicker
import com.vsevolodganin.clicktrack.ui.piece.FloatingActionButton
import com.vsevolodganin.clicktrack.ui.piece.TopAppBarWithBack
import com.vsevolodganin.clicktrack.ui.theme.ClickTrackTheme
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration.Companion.minutes
@Composable
fun TrainingScreenView(viewModel: TrainingViewModel, modifier: Modifier = Modifier) {
Scaffold(
topBar = {
TopAppBarWithBack(
onBackClick = viewModel::onBackClick,
title = { Text(stringResource(MR.strings.training_screen_title)) },
)
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
val state by viewModel.state.collectAsState()
if (state.errors.isEmpty()) {
FloatingActionButton(onClick = viewModel::onAcceptClick) {
Icon(Icons.Default.Check, contentDescription = null)
}
}
},
modifier = modifier,
) {
Content(viewModel)
}
}
@Composable
private fun Content(viewModel: TrainingViewModel) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
val state by viewModel.state.collectAsState()
@Composable
fun FormRow(content: @Composable RowScope.() -> Unit) = Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
content = content,
)
val rightColumnModifier = Modifier.width(140.dp)
FormRow {
Text(
text = stringResource(MR.strings.training_starting_tempo),
modifier = Modifier.weight(1f),
)
BpmInputField(
value = state.startingTempo,
onValueChange = viewModel::onStartingTempoChange,
modifier = rightColumnModifier,
isError = TrainingEditState.Error.STARTING_TEMPO in state.errors,
)
}
DropdownSelector(
items = TrainingMode.entries,
selectedValue = state.mode,
onSelect = viewModel::onModeSelect,
toString = { it.stringResource() },
modifier = Modifier.fillMaxWidth(),
)
CueDurationView(
value = state.segmentLength,
onValueChange = viewModel::onSegmentLengthChange,
onTypeChange = viewModel::onSegmentLengthTypeChange,
modifier = Modifier.fillMaxWidth(),
)
FormRow {
Text(
text = stringResource(
when (state.mode) {
TrainingMode.INCREASE_TEMPO -> MR.strings.training_increase_by
TrainingMode.DECREASE_TEMPO -> MR.strings.training_decrease_by
},
),
modifier = Modifier.weight(1f),
)
BpmInputField(
value = state.tempoChange,
onValueChange = viewModel::onTempoChangeChange,
modifier = rightColumnModifier,
isError = TrainingEditState.Error.TEMPO_CHANGE in state.errors,
)
}
FormRow {
DropdownSelector(
items = TrainingEndingKind.entries,
selectedValue = state.activeEndingKind,
onSelect = viewModel::onEndingKindChange,
toString = { it.stringResource(state.mode) },
modifier = Modifier.weight(1f),
)
when (val ending = state.ending) {
is TrainingEditState.Ending.ByTempo -> {
BpmInputField(
value = ending.endingTempo,
onValueChange = { viewModel.onEndingChange(TrainingEditState.Ending.ByTempo(it)) },
modifier = rightColumnModifier,
isError = TrainingEditState.Error.ENDING_TEMPO in state.errors,
)
}
is TrainingEditState.Ending.ByTime -> {
DurationPicker(
value = ending.duration,
onValueChange = { viewModel.onEndingChange(TrainingEditState.Ending.ByTime(it)) },
modifier = rightColumnModifier,
)
}
}
}
}
}
@Composable
private fun TrainingMode.stringResource(): String {
return stringResource(
when (this) {
TrainingMode.INCREASE_TEMPO -> MR.strings.training_increase_every
TrainingMode.DECREASE_TEMPO -> MR.strings.training_decrease_every
},
)
}
@Composable
private fun TrainingEndingKind.stringResource(mode: TrainingMode): String {
return stringResource(
when (this) {
TrainingEndingKind.BY_TEMPO -> when (mode) {
TrainingMode.INCREASE_TEMPO -> MR.strings.training_max_tempo
TrainingMode.DECREASE_TEMPO -> MR.strings.training_min_tempo
}
TrainingEndingKind.BY_TIME -> MR.strings.training_play_for
},
)
}
@ScreenPreview
@Composable
private fun Preview() = ClickTrackTheme {
TrainingScreenView(
viewModel = object : TrainingViewModel {
override val state: StateFlow<TrainingEditState> = MutableStateFlow(
TrainingEditState(
startingTempo = 120,
mode = TrainingMode.INCREASE_TEMPO,
activeSegmentLengthType = CueDuration.Type.MEASURES,
segmentLengthBeats = DefaultBeatsDuration,
segmentLengthMeasures = DefaultMeasuresDuration,
segmentLengthTime = DefaultTimeDuration,
tempoChange = 5,
activeEndingKind = TrainingEndingKind.BY_TEMPO,
endingByTempo = TrainingEditState.Ending.ByTempo(160),
endingByTime = TrainingEditState.Ending.ByTime(5.minutes),
errors = emptySet(),
),
)
override fun onBackClick() = Unit
override fun onAcceptClick() = Unit
override fun onStartingTempoChange(startingTempo: Int) = Unit
override fun onModeSelect(mode: TrainingMode) = Unit
override fun onSegmentLengthChange(segmentLength: CueDuration) = Unit
override fun onSegmentLengthTypeChange(segmentLengthType: CueDuration.Type) = Unit
override fun onTempoChangeChange(tempoChange: Int) = Unit
override fun onEndingChange(ending: TrainingEditState.Ending) = Unit
override fun onEndingKindChange(endingKind: TrainingEndingKind) = Unit
},
)
}