Make app run on Android (WIP)

This commit is contained in:
Christian Basler
2025-07-14 21:34:13 +02:00
parent 1fb3b39590
commit 1800174087
6 changed files with 72 additions and 32 deletions

View File

@@ -17,6 +17,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import ch.dissem.yaep.domain.Game
import ch.dissem.yaep.domain.generateGame
import ch.dissem.yaep.ui.common.App
@@ -63,6 +64,8 @@ class MainActivity : ComponentActivity() {
) { insets ->
App(
modifier = Modifier.padding(insets),
spacing = 4.dp,
selectDirectly = false,
game = game,
onNewGame = { game = generateGame() },
resetCluesBeacon = resetCluesBeacon
@@ -75,11 +78,13 @@ class MainActivity : ComponentActivity() {
@Preview
@Composable
fun AppAndroidPreview() {
var game by remember { mutableStateOf<Game>(generateGame()) }
var game by remember { mutableStateOf(generateGame()) }
var resetCluesBeacon by remember { mutableStateOf(Any()) }
App(
game = game,
spacing = 4.dp,
selectDirectly = false,
onNewGame = { game = generateGame() },
resetCluesBeacon = resetCluesBeacon
)

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
@@ -24,6 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
@@ -59,6 +61,8 @@ class DisplayClue<C : Clue>(val clue: C) {
@OptIn(ExperimentalTime::class)
fun App(
modifier: Modifier = Modifier,
selectDirectly: Boolean,
spacing: Dp,
game: Game,
onNewGame: () -> Unit,
resetCluesBeacon: Any = Any()
@@ -89,6 +93,8 @@ fun App(
grid = {
PuzzleGrid(
grid = game.grid,
spacing = spacing,
selectDirectly = selectDirectly,
onUpdate = {
horizontalClues.forEach { it.update(game.grid) }
verticalClues.forEach { it.update(game.grid) }
@@ -98,7 +104,8 @@ fun App(
horizontalClues = {
for (clue in horizontalClues) {
HorizontalClue(
modifier = Modifier.forClue(clue),
modifier = Modifier.forClue(clue, spacing),
spacing = spacing,
clue = clue.clue,
isClueViolated = clue.isViolated
)
@@ -107,7 +114,8 @@ fun App(
verticalClues = {
for (clue in verticalClues) {
VerticalClue(
modifier = Modifier.forClue(clue),
modifier = Modifier.forClue(clue, spacing),
spacing = spacing,
clue = clue.clue,
isClueViolated = clue.isViolated
)
@@ -119,7 +127,8 @@ fun App(
fontSize = TextUnit(4f, TextUnitType.Companion.Em),
textAlign = TextAlign.End
)
}
},
spacing = spacing
)
EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame)
}
@@ -128,6 +137,8 @@ fun App(
@Composable
fun PuzzleGrid(
modifier: Modifier = Modifier,
selectDirectly: Boolean,
spacing: Dp = 8.dp,
grid: Grid,
onUpdate: () -> Unit
) {
@@ -155,8 +166,10 @@ fun PuzzleGrid(
}
Selector(
modifier = Modifier
.padding(8.dp)
.padding(spacing)
.weight(1f),
spacing,
selectDirectly = selectDirectly,
options = options,
onOptionRemoved = {
grid.snapshot()
@@ -188,43 +201,49 @@ fun PuzzleGrid(
}
}
private fun Modifier.forClue(clue: DisplayClue<out Clue>): Modifier = this
private fun Modifier.forClue(clue: DisplayClue<out Clue>, padding: Dp = 8.dp): Modifier = this
.alpha(if (clue.isActive) 1f else 0.2f)
.padding(8.dp)
.padding(padding)
.onEitherPointerAction { clue.isActive = !clue.isActive }
@Composable
fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue, isClueViolated: Boolean) {
fun HorizontalClue(
modifier: Modifier = Modifier,
spacing: Dp,
clue: HorizontalClue,
isClueViolated: Boolean
) {
ClueCard(
modifier = modifier,
spacing = spacing,
isClueViolated = isClueViolated
) {
Row {
when (clue) {
is NeighbourClue<*, *> -> {
DrawItem(modifier = Modifier.weight(1f), clue.a)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.a)
Image(
modifier = Modifier.aspectRatio(1f).weight(1f),
painter = painterResource(Res.drawable.neighbour),
contentDescription = null
)
DrawItem(modifier = Modifier.weight(1f), clue.b)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.b)
}
is OrderClue<*, *> -> {
DrawItem(modifier = Modifier.weight(1f), clue.left)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.left)
Image(
modifier = Modifier.aspectRatio(1f).weight(1f),
painter = painterResource(Res.drawable.order),
contentDescription = null
)
DrawItem(modifier = Modifier.weight(1f), clue.right)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.right)
}
is TripletClue<*, *, *> -> {
DrawItem(modifier = Modifier.weight(1f), clue.a)
DrawItem(modifier = Modifier.weight(1f), clue.b)
DrawItem(modifier = Modifier.weight(1f), clue.c)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.a)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.b)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.c)
}
}
}
@@ -234,16 +253,18 @@ fun HorizontalClue(modifier: Modifier = Modifier, clue: HorizontalClue, isClueVi
@Composable
fun VerticalClue(
modifier: Modifier = Modifier,
spacing: Dp,
clue: SameColumnClue<*, *>,
isClueViolated: Boolean = false
) {
ClueCard(
modifier = modifier.aspectRatio(0.5f),
spacing = spacing,
isClueViolated = isClueViolated
) {
Column {
DrawItem(modifier = Modifier.weight(1f), clue.a)
DrawItem(modifier = Modifier.weight(1f), clue.b)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.a)
DrawItem(modifier = Modifier.weight(1f), spacing = spacing, item = clue.b)
}
}
}
@@ -253,6 +274,7 @@ expect fun CoroutineScope.logGame(game: Game)
@Composable
fun ClueCard(
modifier: Modifier = Modifier,
spacing: Dp,
isClueViolated: Boolean,
content: @Composable () -> Unit
) {
@@ -268,6 +290,7 @@ fun ClueCard(
} else {
modifier
},
shape = RoundedCornerShape(spacing),
border = if (isClueViolated) {
remember { BorderStroke(1.0.dp, colors.error) }
} else {

View File

@@ -9,6 +9,7 @@ import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Constraints.Companion.fixed
import androidx.compose.ui.unit.Constraints.Companion.fixedWidth
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ch.dissem.yaep.ui.common.AspectRatio.LANDSCAPE
import ch.dissem.yaep.ui.common.AspectRatio.PORTRAIT
@@ -17,8 +18,6 @@ import kotlin.math.max
import kotlin.math.min
internal val spacing = 8.dp
private enum class AspectRatio {
PORTRAIT, LANDSCAPE, SQUARISH;
@@ -44,7 +43,8 @@ fun AdaptiveGameLayout(
horizontalClues: @Composable () -> Unit,
verticalClues: @Composable () -> Unit,
time: @Composable () -> Unit,
divider: @Composable () -> Unit = { HorizontalDivider() }
divider: @Composable () -> Unit = { HorizontalDivider() },
spacing: Dp = 8.dp
) {
Layout(
contents = listOf(grid, horizontalClues, verticalClues, time, divider, divider),

View File

@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -18,6 +19,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Dp
import ch.dissem.yaep.domain.Item
import ch.dissem.yaep.domain.ItemClass
import ch.dissem.yaep.ui.common.theme.emojiFontFamily
@@ -26,16 +28,26 @@ import kotlin.math.min
@Composable
fun <C : ItemClass<C>> Selector(
modifier: Modifier = Modifier,
spacing: Dp,
selectDirectly: Boolean,
options: List<Toggleable<Item<C>>>,
onOptionRemoved: (Item<C>) -> Unit,
onOptionAdded: (Item<C>) -> Unit,
selectedItem: Item<C>?,
onSelectItem: (Item<C>?) -> Unit,
) {
val radius = spacing * 1.5f
if (selectedItem != null) {
DrawItem(item = selectedItem, modifier = modifier.clickable { onSelectItem(null) })
DrawItem(
item = selectedItem,
modifier = modifier.clickable { onSelectItem(null) },
spacing = radius
)
} else {
OutlinedCard(modifier = modifier.aspectRatio(1f)) {
OutlinedCard(
modifier = modifier.aspectRatio(1f),
shape = RoundedCornerShape(radius)
) {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
verticalArrangement = Arrangement.Center,
@@ -58,7 +70,8 @@ fun <C : ItemClass<C>> Selector(
onOptionAdded(option.item)
}
}
)
),
spacing = radius
)
}
}
@@ -70,9 +83,10 @@ fun <C : ItemClass<C>> Selector(
@Composable
fun <C : ItemClass<C>> DrawItem(
modifier: Modifier = Modifier,
spacing: Dp,
item: Item<C>
) {
OutlinedCard(modifier = modifier.aspectRatio(1f)) {
OutlinedCard(modifier = modifier.aspectRatio(1f), shape = RoundedCornerShape(spacing)) {
val emoji = item.symbol
val textMeasurer = rememberTextMeasurer()

View File

@@ -6,10 +6,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists
import kotlin.io.path.writeText
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
@@ -21,17 +19,15 @@ actual fun CoroutineScope.logGame(game: Game) {
launch(Dispatchers.IO) {
val dirName = """${System.getProperty("user.home")}/.yaep"""
val dir = Path(dirName)
if (dir.notExists()) {
dir.createDirectories()
} else if (!dir.isDirectory()) {
log.error { "Yaep data directory already exists and is not a directory: $dir" }
log.debug { "Game: $game" }
} else {
if (dir.isDirectory()) {
val fileName = "$dirName/${Clock.System.now()}.yaep"
Path(fileName)
.createFile()
.writeText(game.toString())
log.info { "Saved game to $fileName" }
} else {
log.debug { "Yaep data directory does not exist or is not a directory: $dir" }
log.debug { "Game: $game" }
}
}
}

View File

@@ -68,6 +68,8 @@ fun main(): Unit = application {
) {
App(
modifier = Modifier.padding(it),
spacing = 8.dp,
selectDirectly = true,
game = game,
onNewGame = { game = generateGame() },
resetCluesBeacon = resetCluesBeacon