Skip to content

Commit

Permalink
Move ticking logic from ClockScreen to ClockViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
ldeso committed Mar 29, 2024
1 parent ffae1e4 commit 6563b07
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 43 deletions.
2 changes: 1 addition & 1 deletion app/src/main/kotlin/net/leodesouza/blitz/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class MainActivity : ComponentActivity() {
setContent {
ClockScreen(
onClockStart = { window.addFlags(FLAG_KEEP_SCREEN_ON) },
onClockPause = { window.clearFlags(FLAG_KEEP_SCREEN_ON) },
onClockStop = { window.clearFlags(FLAG_KEEP_SCREEN_ON) },
)
}
}
Expand Down
44 changes: 16 additions & 28 deletions app/src/main/kotlin/net/leodesouza/blitz/ui/ClockScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import androidx.activity.BackEventCompat
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
Expand Down Expand Up @@ -49,7 +48,7 @@ import net.leodesouza.blitz.ui.models.ClockState
* @param[tickPeriod] Period between ticks in milliseconds.
* @param[dragSensitivity] How many minutes or seconds to add per dragged pixel.
* @param[onClockStart] Callback called before the clock starts ticking.
* @param[onClockPause] Callback called after the clock stops ticking.
* @param[onClockStop] Callback called after the clock stops ticking.
* @param[clockViewModel] ViewModel holding the state and logic for this screen.
*/
@Composable
Expand All @@ -59,7 +58,7 @@ fun ClockScreen(
tickPeriod: Long = 100L,
dragSensitivity: Float = 0.01F,
onClockStart: () -> Unit = {},
onClockPause: () -> Unit = {},
onClockStop: () -> Unit = {},
clockViewModel: ClockViewModel = viewModel {
ClockViewModel(durationMinutes, incrementSeconds, tickPeriod)
},
Expand All @@ -83,6 +82,12 @@ fun ClockScreen(
}
var backEventAction by remember { mutableStateOf(ClockBackAction.PAUSE) }

CallbackCaller(
clockStateProvider = { clockState },
onClockStart = onClockStart,
onClockStop = onClockStop,
)

OrientationHandler(onOrientationChanged = { orientation = it })

LeaningSideHandler(
Expand All @@ -96,14 +101,6 @@ fun ClockScreen(
},
)

ClockStateHandler(
whiteTimeProvider = { whiteTime },
blackTimeProvider = { blackTime },
clockStateProvider = { clockState },
tick = clockViewModel::tick,
onClockPause = onClockPause,
)

ClockBackHandler(
clockStateProvider = { clockState },
pause = clockViewModel::pause,
Expand Down Expand Up @@ -149,29 +146,20 @@ fun ClockScreen(
}

/**
* Effect taking care of repeatedly waiting until the next tick when the clock is ticking, or
* calling the callback [onClockPause] when the clock has stopped ticking.
*
* @param[whiteTimeProvider] Lambda for the time of the first player.
* @param[blackTimeProvider] Lambda for the time of the second player.
* @param[clockStateProvider] Lambda for the current state of the clock.
* @param[tick] Callback called to wait until next tick.
* @param[onClockPause] Callback called after the clock stops ticking.
* Effect taking care of calling the callbacks [onClockStart] and [onClockStop] depending on the
* state of the clock returned by [clockStateProvider].
*/
@Composable
private fun ClockStateHandler(
whiteTimeProvider: () -> Long,
blackTimeProvider: () -> Long,
private fun CallbackCaller(
clockStateProvider: () -> ClockState,
tick: suspend () -> Unit,
onClockPause: () -> Unit,
onClockStart: () -> Unit,
onClockStop: () -> Unit,
) {
val whiteTime = whiteTimeProvider()
val blackTime = blackTimeProvider()
val clockState = clockStateProvider()

when (clockState) {
ClockState.TICKING -> LaunchedEffect(whiteTime, blackTime) { tick() }
else -> onClockPause()
ClockState.TICKING -> onClockStart()
ClockState.PAUSED, ClockState.FINISHED -> onClockStop()
else -> Unit
}
}
37 changes: 23 additions & 14 deletions app/src/main/kotlin/net/leodesouza/blitz/ui/ClockViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ package net.leodesouza.blitz.ui

import android.os.SystemClock.elapsedRealtime
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import net.leodesouza.blitz.ui.models.ClockState
import net.leodesouza.blitz.ui.models.PlayerState
import kotlin.math.roundToLong
Expand All @@ -45,6 +49,7 @@ class ClockViewModel(
private var duration: Long = defaultDuration
private var increment: Long = defaultIncrement
private var targetRealtime: Long = 0L
private var tickingJob: Job? = null

private val _whiteTime: MutableStateFlow<Long> = MutableStateFlow(duration + increment)
private val _blackTime: MutableStateFlow<Long> = MutableStateFlow(duration + increment)
Expand Down Expand Up @@ -99,6 +104,22 @@ class ClockViewModel(
}
targetRealtime = elapsedRealtime() + currentTime
_clockState.value = ClockState.TICKING

tickingJob = viewModelScope.launch {
while (_clockState.value == ClockState.TICKING) {
val remainingTime = targetRealtime - elapsedRealtime()
val tickDelay = remainingTime % tickPeriod
delay(tickDelay)
val newTime = remainingTime - tickDelay
when (_playerState.value) {
PlayerState.WHITE -> _whiteTime.value = newTime
PlayerState.BLACK -> _blackTime.value = newTime
}
if (newTime <= 0L) {
_clockState.value = ClockState.FINISHED
}
}
}
}

fun play() {
Expand Down Expand Up @@ -129,6 +150,8 @@ class ClockViewModel(
}

fun pause() {
tickingJob?.cancel()

val newTime = targetRealtime - elapsedRealtime()
when (_playerState.value) {
PlayerState.WHITE -> _whiteTime.value = newTime
Expand All @@ -137,20 +160,6 @@ class ClockViewModel(
_clockState.value = ClockState.PAUSED
}

suspend fun tick() {
val remainingTime = targetRealtime - elapsedRealtime()
val correctionDelay = remainingTime % tickPeriod
delay(correctionDelay)
val newTime = remainingTime - correctionDelay
when (_playerState.value) {
PlayerState.WHITE -> _whiteTime.value = newTime
PlayerState.BLACK -> _blackTime.value = newTime
}
if (newTime <= 0L) {
_clockState.value = ClockState.FINISHED
}
}

fun resetTime() {
val newTime = duration + increment
_whiteTime.value = newTime
Expand Down

0 comments on commit 6563b07

Please sign in to comment.