feat: add Training Timer and calculation of optimal Round Times
This commit is contained in:
parent
6d9151c8ec
commit
cfbd2a313b
10 changed files with 271 additions and 149 deletions
|
|
@ -1,5 +1,7 @@
|
|||
package de.patani.kettlebelltracker.viewmodels
|
||||
|
||||
import android.media.AudioManager
|
||||
import android.media.ToneGenerator
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -27,7 +29,11 @@ data class TrainingState(
|
|||
val currentProgram: String = "clean_1.0",
|
||||
val currentBlockDay: Int = 1,
|
||||
val currentReps: Int = 5,
|
||||
val totalTrainingDays: Int = 0
|
||||
val totalTrainingDays: Int = 0,
|
||||
// Neue Rundentimer-Eigenschaften
|
||||
val isRoundActive: Boolean = false,
|
||||
val currentRoundTime: Int = 0,
|
||||
val totalRoundTime: Int = 0
|
||||
)
|
||||
|
||||
class TrainingViewModel(
|
||||
|
|
@ -41,9 +47,9 @@ class TrainingViewModel(
|
|||
val trainingState = _trainingState.asStateFlow()
|
||||
|
||||
private var timerJob: Job? = null
|
||||
private var roundTimerJob: Job? = null
|
||||
|
||||
init {
|
||||
// Load initial state based on past trainings
|
||||
viewModelScope.launch {
|
||||
val trainingCount = dao.getTrainingCount().first()
|
||||
val initialState = calculateStateByDayCount(trainingCount)
|
||||
|
|
@ -59,6 +65,8 @@ class TrainingViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private var recommendedRestSeconds: Int = 90
|
||||
|
||||
fun startTraining() {
|
||||
if (_trainingState.value.isTrainingRunning) return
|
||||
|
||||
|
|
@ -76,7 +84,15 @@ class TrainingViewModel(
|
|||
progress = 0.0f
|
||||
)
|
||||
}
|
||||
val recommendation = apiRepository.getRecommendedRest(
|
||||
uuid = appUUID,
|
||||
repsPerSet = _trainingState.value.repsPerSet,
|
||||
currentSets = 0 // ggf. 0 oder deinen Startwert
|
||||
)
|
||||
recommendedRestSeconds = recommendation?.recommended_rest ?: 90
|
||||
Log.d("Training", "using rest time ${recommendedRestSeconds.toString()}")
|
||||
startTimer()
|
||||
startFirstRound()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,20 +109,87 @@ class TrainingViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun startFirstRound() {
|
||||
startRoundTimer(recommendedRestSeconds)
|
||||
}
|
||||
|
||||
fun completeSet() {
|
||||
if (!_trainingState.value.isTrainingRunning) return
|
||||
|
||||
val currentState = _trainingState.value
|
||||
val newSetsDone = currentState.setsDone + 1
|
||||
val newProgress = if (currentState.goalSets > 0) {
|
||||
min(newSetsDone.toFloat() / currentState.goalSets.toFloat(), 1.0f)
|
||||
} else 0.0f
|
||||
|
||||
_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)
|
||||
it.copy(
|
||||
setsDone = newSetsDone,
|
||||
progress = newProgress
|
||||
)
|
||||
}
|
||||
|
||||
stopRoundTimer()
|
||||
|
||||
if (newSetsDone < currentState.goalSets) {
|
||||
startNextRound()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNextRound() {
|
||||
startRoundTimer(recommendedRestSeconds)
|
||||
}
|
||||
|
||||
private fun startRoundTimer(seconds: Int) {
|
||||
roundTimerJob?.cancel()
|
||||
|
||||
_trainingState.update {
|
||||
it.copy(
|
||||
isRoundActive = true,
|
||||
currentRoundTime = seconds,
|
||||
totalRoundTime = seconds
|
||||
)
|
||||
}
|
||||
|
||||
roundTimerJob = viewModelScope.launch {
|
||||
while (_trainingState.value.currentRoundTime > 0 && _trainingState.value.isRoundActive) {
|
||||
delay(1000)
|
||||
_trainingState.update {
|
||||
it.copy(currentRoundTime = it.currentRoundTime - 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (_trainingState.value.isRoundActive) {
|
||||
playRoundCompleteSound()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopRoundTimer() {
|
||||
roundTimerJob?.cancel()
|
||||
_trainingState.update {
|
||||
it.copy(
|
||||
isRoundActive = false,
|
||||
currentRoundTime = 0,
|
||||
totalRoundTime = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun playRoundCompleteSound() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val toneGenerator = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100)
|
||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 1000)
|
||||
} catch (e: Exception) {
|
||||
Log.e("TrainingViewModel", "Could not play sound", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun finishTraining() {
|
||||
timerJob?.cancel()
|
||||
roundTimerJob?.cancel()
|
||||
if (!_trainingState.value.isTrainingRunning) return
|
||||
|
||||
viewModelScope.launch {
|
||||
|
|
@ -165,5 +248,11 @@ class TrainingViewModel(
|
|||
return ProgramState(program, blockDay, reps)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
timerJob?.cancel()
|
||||
roundTimerJob?.cancel()
|
||||
}
|
||||
|
||||
data class ProgramState(val program: String, val blockDay: Int, val reps: Int)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue