Add timer
This commit is contained in:
@@ -1 +1 @@
|
|||||||
java temurin-17.0.12+7
|
java temurin-21
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
topBar = {
|
topBar = {
|
||||||
|
|
||||||
}
|
}
|
||||||
) {insets ->
|
) { insets ->
|
||||||
App(modifier = Modifier.padding(insets))
|
App(modifier = Modifier.padding(insets))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,4 +30,4 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun AppAndroidPreview() {
|
fun AppAndroidPreview() {
|
||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.material3.OutlinedCard
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -58,12 +59,26 @@ class DisplayClue<C : Clue>(val clue: C) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun App(modifier: Modifier = Modifier, game: Game = remember { generateGame() }) {
|
fun App(modifier: Modifier = Modifier, game: Game = remember { generateGame() }) {
|
||||||
val horizontalClues = remember { game.horizontalClues.map { DisplayClue(it) } }
|
val horizontalClues = remember(game) { game.horizontalClues.map { DisplayClue(it) } }
|
||||||
val verticalClues = remember { game.verticalClues.map { DisplayClue(it) } }
|
val verticalClues = remember(game) { game.verticalClues.map { DisplayClue(it) } }
|
||||||
val time = "00:00:00" // TODO
|
val timer = remember(game) { GameTimer() }
|
||||||
|
val time by timer.elapsedTime.collectAsState("00:00")
|
||||||
|
LaunchedEffect(game) {
|
||||||
|
game.onStart {
|
||||||
|
timer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
game.onSolved {
|
||||||
|
timer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(modifier = modifier) {
|
Row(modifier = modifier) {
|
||||||
PuzzleGrid(
|
PuzzleGrid(
|
||||||
modifier = Modifier.aspectRatio(1f).weight(0.6f).fillMaxHeight(),
|
modifier = Modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.weight(0.6f)
|
||||||
|
.fillMaxHeight(),
|
||||||
grid = game.grid,
|
grid = game.grid,
|
||||||
onUpdate = {
|
onUpdate = {
|
||||||
horizontalClues.forEach { it.update(game.grid) }
|
horizontalClues.forEach { it.update(game.grid) }
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package ch.dissem.yaep.ui.common
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class GameTimer {
|
||||||
|
|
||||||
|
private var startTime: Long = 0L
|
||||||
|
private var stopTime: Long = 0L
|
||||||
|
|
||||||
|
val isRunning: Boolean
|
||||||
|
get() = startTime != 0L && stopTime == 0L
|
||||||
|
|
||||||
|
val elapsedSeconds: Flow<Long> = flow {
|
||||||
|
emit(0L)
|
||||||
|
while (startTime == 0L) {
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
var previousSeconds = 0L
|
||||||
|
while (stopTime == 0L) {
|
||||||
|
val elapsedSeconds = System.currentTimeMillis() / 1000 - startTime
|
||||||
|
if (elapsedSeconds != previousSeconds) {
|
||||||
|
emit(elapsedSeconds)
|
||||||
|
}
|
||||||
|
delay(100)
|
||||||
|
previousSeconds = elapsedSeconds
|
||||||
|
}
|
||||||
|
emit(stopTime - startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
val elapsedTime: Flow<String> = elapsedSeconds.map { seconds ->
|
||||||
|
val minutes = seconds / 60
|
||||||
|
val hours = minutes / 60
|
||||||
|
val remainingMinutes = minutes % 60
|
||||||
|
val remainingSeconds = seconds % 60
|
||||||
|
if (hours == 0L) {
|
||||||
|
"%02d:%02d".format(minutes, remainingSeconds)
|
||||||
|
} else {
|
||||||
|
"%02d:%02d:%02d".format(hours, remainingMinutes, remainingSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (startTime == 0L) {
|
||||||
|
startTime = System.currentTimeMillis() / 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (isRunning) {
|
||||||
|
stopTime = System.currentTimeMillis() / 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ class Game(
|
|||||||
val horizontalClues = clues.filterIsInstance<HorizontalClue>()
|
val horizontalClues = clues.filterIsInstance<HorizontalClue>()
|
||||||
val verticalClues = clues.filterIsInstance<SameColumnClue<ItemClass<*>, ItemClass<*>>>()
|
val verticalClues = clues.filterIsInstance<SameColumnClue<ItemClass<*>, ItemClass<*>>>()
|
||||||
|
|
||||||
|
private val onStartListeners = mutableListOf<() -> Unit>()
|
||||||
|
private val onSolvedListeners = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
for (position in clues.filterIsInstance<PositionClue<ItemClass<*>>>()) {
|
for (position in clues.filterIsInstance<PositionClue<ItemClass<*>>>()) {
|
||||||
val row = grid[position.item.itemType.companion]
|
val row = grid[position.item.itemType.companion]
|
||||||
@@ -18,6 +21,29 @@ class Game(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
grid.forEach { row ->
|
||||||
|
row.forEach { cell ->
|
||||||
|
cell.optionsRemovedListeners.add {
|
||||||
|
if (onStartListeners.isNotEmpty()) {
|
||||||
|
onStartListeners.forEach { it() }
|
||||||
|
onStartListeners.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row.onSolved {
|
||||||
|
if (onSolvedListeners.isNotEmpty() && isSolved) {
|
||||||
|
onSolvedListeners.forEach { it() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onStart(listener: () -> Unit) {
|
||||||
|
onStartListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSolved(listener: () -> Unit) {
|
||||||
|
onSolvedListeners.add(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,13 +6,22 @@ class GameRow<C : ItemClass<C>>(
|
|||||||
val cells: List<GameCell<C>>
|
val cells: List<GameCell<C>>
|
||||||
) : List<GameCell<C>> by cells {
|
) : List<GameCell<C>> by cells {
|
||||||
var isSolved = false
|
var isSolved = false
|
||||||
private set
|
private set(value) {
|
||||||
|
field = value
|
||||||
|
if (value) {
|
||||||
|
onSolvedListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var onSolvedListener = {}
|
||||||
|
|
||||||
|
fun onSolved(listener: () -> Unit) {
|
||||||
|
onSolvedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
fun indexOf(element: C) = indexOfFirst { it.selection?.itemType == element }
|
fun indexOf(element: C) = indexOfFirst { it.selection?.itemType == element }
|
||||||
|
|
||||||
fun cleanupOptions() {
|
fun cleanupOptions() {
|
||||||
if (
|
if (isSolved && all {
|
||||||
isSolved && all {
|
|
||||||
it.solution != null
|
it.solution != null
|
||||||
&& it.options.size == 1
|
&& it.options.size == 1
|
||||||
&& it.options.single() == it.selection
|
&& it.options.single() == it.selection
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.5.2"
|
agp = "8.8.2"
|
||||||
jdk = "11"
|
jdk = "21"
|
||||||
android-compileSdk = "34"
|
android-compileSdk = "35"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-targetSdk = "34"
|
android-targetSdk = "35"
|
||||||
androidx-activityCompose = "1.9.1"
|
androidx-activityCompose = "1.10.1"
|
||||||
androidx-compose = "1.6.8"
|
androidx-compose = "1.7.8"
|
||||||
compose-plugin = "1.6.11"
|
compose-plugin = "1.7.0"
|
||||||
kotlin = "2.0.20"
|
kotlin = "2.1.0"
|
||||||
coreKtx = "1.13.1"
|
coreKtx = "1.15.0"
|
||||||
atrium = "1.2.0"
|
atrium = "1.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -18,6 +18,7 @@ androidx-compose-foundation = { module = "androidx.compose.foundation:foundation
|
|||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
atrium = { module = "ch.tutteli.atrium:atrium-fluent", version.ref = "atrium" }
|
atrium = { module = "ch.tutteli.atrium:atrium-fluent", version.ref = "atrium" }
|
||||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||||
|
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.1" }
|
||||||
logging = { module = "io.github.oshai:kotlin-logging", version = "7.0.0" }
|
logging = { module = "io.github.oshai:kotlin-logging", version = "7.0.0" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Reference in New Issue
Block a user