Some checks failed
release / build-release (push) Has been cancelled
- Room-cache: övningar/muskelgrupper/favoritkort + lokal-först-pass - SyncEngine: FIFO-op-kö mot API:t, retry med backoff, synk-status per set (moln-ikon) + global räknare; nätfel loggar inte ut användaren - Passläge: en övning i taget (svep), ±steppers, RPE, förifyllda värden från förra passet, logga set med ett tryck - Vilotimer: helskärm eller banner (inställning), larm/vibration/visuell/ inget vid utgång, ±15s, per-övnings vilotid via API:ts defaultRestSeconds - Övningsväljare: sök + muskelgrupp/muskel-chips med ikoner (API:ts iconKey med namnheuristik som fallback), favoriter först, KG/REPS/TID/M-brickor - Navigation: bottenrad + start-FAB + minispelare, hemskärm med favoritpass - Inställningsskärm under Profil; historik-lista; version 0.2.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP
103 lines
3.4 KiB
Kotlin
103 lines
3.4 KiB
Kotlin
package eu.brassepc.fitnessdroid.data
|
|
|
|
import android.content.Context
|
|
import android.media.RingtoneManager
|
|
import android.os.VibrationEffect
|
|
import android.os.VibratorManager
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.launch
|
|
|
|
data class RestState(
|
|
val totalSeconds: Int,
|
|
val endsAtEpochMs: Long,
|
|
val secondsLeft: Int,
|
|
val finished: Boolean = false,
|
|
)
|
|
|
|
/**
|
|
* Vilotimern. Räknar mot en absolut sluttid (överlever att appen pausas)
|
|
* och larmar enligt inställningarna när tiden är ute.
|
|
*/
|
|
class RestTimerController(
|
|
private val context: Context,
|
|
private val settingsStore: SettingsStore,
|
|
private val scope: CoroutineScope,
|
|
) {
|
|
private val _state = MutableStateFlow<RestState?>(null)
|
|
val state: StateFlow<RestState?> = _state
|
|
|
|
private var ticker: Job? = null
|
|
|
|
fun start(seconds: Int? = null) {
|
|
scope.launch {
|
|
val settings = settingsStore.settings.first()
|
|
val total = seconds ?: settings.defaultRestSeconds
|
|
_state.value = RestState(
|
|
totalSeconds = total,
|
|
endsAtEpochMs = System.currentTimeMillis() + total * 1000L,
|
|
secondsLeft = total,
|
|
)
|
|
startTicker()
|
|
}
|
|
}
|
|
|
|
fun adjust(deltaSeconds: Int) {
|
|
val current = _state.value ?: return
|
|
if (current.finished) return
|
|
_state.value = current.copy(
|
|
endsAtEpochMs = (current.endsAtEpochMs + deltaSeconds * 1000L)
|
|
.coerceAtLeast(System.currentTimeMillis()),
|
|
totalSeconds = (current.totalSeconds + deltaSeconds).coerceAtLeast(5),
|
|
)
|
|
}
|
|
|
|
fun dismiss() {
|
|
ticker?.cancel()
|
|
_state.value = null
|
|
}
|
|
|
|
private fun startTicker() {
|
|
ticker?.cancel()
|
|
ticker = scope.launch {
|
|
while (true) {
|
|
val current = _state.value ?: return@launch
|
|
val left = (((current.endsAtEpochMs - System.currentTimeMillis()) + 999) / 1000)
|
|
.coerceAtLeast(0).toInt()
|
|
if (left != current.secondsLeft) {
|
|
_state.value = current.copy(secondsLeft = left)
|
|
}
|
|
if (left <= 0) {
|
|
_state.value = _state.value?.copy(secondsLeft = 0, finished = true)
|
|
alert()
|
|
// Låt "klart"-läget synas en stund, försvinn sedan självmant.
|
|
delay(8_000)
|
|
if (_state.value?.finished == true) _state.value = null
|
|
return@launch
|
|
}
|
|
delay(250)
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun alert() {
|
|
when (settingsStore.settings.first().restAlert) {
|
|
RestAlert.SOUND -> runCatching {
|
|
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
|
RingtoneManager.getRingtone(context, uri)?.play()
|
|
}
|
|
RestAlert.VIBRATE -> runCatching {
|
|
val vm = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
|
vm.defaultVibrator.vibrate(
|
|
VibrationEffect.createWaveform(longArrayOf(0, 350, 150, 350, 150, 500), -1)
|
|
)
|
|
}
|
|
RestAlert.VISUAL, RestAlert.NONE -> Unit
|
|
}
|
|
}
|
|
}
|