169 lines
No EOL
5.7 KiB
Kotlin
169 lines
No EOL
5.7 KiB
Kotlin
package de.patani.kettlebelltracker.viewmodels
|
|
|
|
import android.util.Log
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import de.patani.kettlebelltracker.data.local.TrainingSessionDao
|
|
import de.patani.kettlebelltracker.data.datastore.SettingsDataStore
|
|
import de.patani.kettlebelltracker.repositories.ApiRepository
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.flow.update
|
|
import kotlinx.coroutines.launch
|
|
import java.util.Date
|
|
import kotlin.math.min
|
|
|
|
data class TrainingState(
|
|
val isTrainingRunning: Boolean = false,
|
|
val remainingSeconds: Int = 0,
|
|
val initialDurationSeconds: Int = 0,
|
|
val setsDone: Int = 0,
|
|
val goalSets: Int = 5,
|
|
val repsPerSet: Int = 5,
|
|
val progress: Float = 0.0f,
|
|
val currentProgram: String = "clean_1.0",
|
|
val currentBlockDay: Int = 1,
|
|
val currentReps: Int = 5,
|
|
val totalTrainingDays: Int = 0
|
|
)
|
|
|
|
class TrainingViewModel(
|
|
private val dao: TrainingSessionDao,
|
|
private val settingsDataStore: SettingsDataStore,
|
|
private val apiRepository: ApiRepository,
|
|
private val appUUID: String
|
|
) : ViewModel() {
|
|
|
|
private val _trainingState = MutableStateFlow(TrainingState())
|
|
val trainingState = _trainingState.asStateFlow()
|
|
|
|
private var timerJob: Job? = null
|
|
|
|
init {
|
|
// Load initial state based on past trainings
|
|
viewModelScope.launch {
|
|
val trainingCount = dao.getTrainingCount().first()
|
|
val initialState = calculateStateByDayCount(trainingCount)
|
|
_trainingState.update {
|
|
it.copy(
|
|
totalTrainingDays = trainingCount,
|
|
currentProgram = initialState.program,
|
|
currentBlockDay = initialState.blockDay,
|
|
currentReps = initialState.reps,
|
|
repsPerSet = initialState.reps
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun startTraining() {
|
|
if (_trainingState.value.isTrainingRunning) return
|
|
|
|
viewModelScope.launch {
|
|
val settings = settingsDataStore.settingsFlow.first()
|
|
val durationSeconds = settings.trainingTimeMinutes * 60
|
|
|
|
_trainingState.update {
|
|
it.copy(
|
|
isTrainingRunning = true,
|
|
initialDurationSeconds = durationSeconds,
|
|
remainingSeconds = durationSeconds,
|
|
goalSets = settings.goalSets,
|
|
setsDone = 0,
|
|
progress = 0.0f
|
|
)
|
|
}
|
|
startTimer()
|
|
}
|
|
}
|
|
|
|
private fun startTimer() {
|
|
timerJob?.cancel()
|
|
timerJob = viewModelScope.launch {
|
|
while (_trainingState.value.remainingSeconds > 0 && _trainingState.value.isTrainingRunning) {
|
|
delay(1000)
|
|
_trainingState.update { it.copy(remainingSeconds = it.remainingSeconds - 1) }
|
|
}
|
|
if (_trainingState.value.isTrainingRunning) {
|
|
finishTraining()
|
|
}
|
|
}
|
|
}
|
|
|
|
fun completeSet() {
|
|
if (!_trainingState.value.isTrainingRunning) return
|
|
|
|
_trainingState.update {
|
|
val newSetsDone = it.setsDone + 1
|
|
val newProgress = if (it.goalSets > 0) {
|
|
min(newSetsDone.toFloat() / it.goalSets.toFloat(), 1.0f)
|
|
} else 0.0f
|
|
it.copy(setsDone = newSetsDone, progress = newProgress)
|
|
}
|
|
}
|
|
|
|
fun finishTraining() {
|
|
timerJob?.cancel()
|
|
if (!_trainingState.value.isTrainingRunning) return
|
|
|
|
viewModelScope.launch {
|
|
val state = _trainingState.value
|
|
val settings = settingsDataStore.settingsFlow.first()
|
|
|
|
val session = de.patani.kettlebelltracker.data.local.TrainingSession(
|
|
date = Date(),
|
|
sets = state.setsDone,
|
|
weightLeft = settings.weightLeft,
|
|
weightRight = settings.weightRight,
|
|
repsPerSet = state.repsPerSet,
|
|
duration = (state.initialDurationSeconds - state.remainingSeconds).toLong(),
|
|
program = state.currentProgram,
|
|
blockDay = state.currentBlockDay
|
|
)
|
|
|
|
dao.insert(session)
|
|
apiRepository.sendTrainingData(session, appUUID)
|
|
resetTraining()
|
|
}
|
|
}
|
|
|
|
private suspend fun resetTraining() {
|
|
val trainingCount = dao.getTrainingCount().first()
|
|
val nextState = calculateStateByDayCount(trainingCount)
|
|
_trainingState.value = TrainingState(
|
|
totalTrainingDays = trainingCount,
|
|
currentProgram = nextState.program,
|
|
currentBlockDay = nextState.blockDay,
|
|
currentReps = nextState.reps,
|
|
repsPerSet = nextState.reps
|
|
)
|
|
}
|
|
|
|
private fun calculateStateByDayCount(totalDays: Int): ProgramState {
|
|
if (totalDays <= 0) {
|
|
return ProgramState("clean_1.0", 1, 5)
|
|
}
|
|
|
|
val cycleIndex = (totalDays / 12) % 6
|
|
val programs = listOf("clean_1.0", "snatch_1.0", "clean_1.1", "snatch_1.1", "clean_1.2", "snatch_1.2")
|
|
val program = programs[cycleIndex]
|
|
val blockDay = (totalDays % 3) + 1
|
|
|
|
val repsMap = mapOf(
|
|
"clean_1.0" to listOf(5, 6, 4),
|
|
"clean_1.1" to listOf(6, 8, 7),
|
|
"clean_1.2" to listOf(7, 9, 8),
|
|
"snatch_1.0" to listOf(5, 6, 4),
|
|
"snatch_1.1" to listOf(6, 8, 7),
|
|
"snatch_1.2" to listOf(7, 9, 8)
|
|
)
|
|
|
|
val reps = repsMap[program]?.getOrNull(blockDay - 1) ?: 5
|
|
return ProgramState(program, blockDay, reps)
|
|
}
|
|
|
|
data class ProgramState(val program: String, val blockDay: Int, val reps: Int)
|
|
} |