Make app run on Android (WIP)
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user