diff --git a/.idea/artifacts/composeApp_js.xml b/.idea/artifacts/composeApp_js.xml new file mode 100644 index 0000000..54559a3 --- /dev/null +++ b/.idea/artifacts/composeApp_js.xml @@ -0,0 +1,6 @@ + + + $PROJECT_DIR$/composeApp/build/libs + + + \ No newline at end of file diff --git a/.idea/artifacts/composeApp_jvm.xml b/.idea/artifacts/composeApp_jvm.xml new file mode 100644 index 0000000..df75785 --- /dev/null +++ b/.idea/artifacts/composeApp_jvm.xml @@ -0,0 +1,6 @@ + + + $PROJECT_DIR$/composeApp/build/libs + + + \ No newline at end of file diff --git a/.idea/artifacts/composeApp_wasm_js.xml b/.idea/artifacts/composeApp_wasm_js.xml new file mode 100644 index 0000000..2c0f6b1 --- /dev/null +++ b/.idea/artifacts/composeApp_wasm_js.xml @@ -0,0 +1,6 @@ + + + $PROJECT_DIR$/composeApp/build/libs + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..cebe225 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/entersteps/admin/EnterStepsAdminScreen.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/entersteps/admin/EnterStepsAdminScreen.kt new file mode 100644 index 0000000..a4f0bb1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/entersteps/admin/EnterStepsAdminScreen.kt @@ -0,0 +1,23 @@ +package hu.mokegyesulet.it.dunestrat.feature.entersteps.admin + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import hu.mokegyesulet.it.dunestrat.model.Player +import hu.mokegyesulet.it.dunestrat.ui.step.EnterStepsCard + +@Composable +fun EnterStepsAdminScreen(players: List) { + val viewModel = viewModel { EnterStepsAdminViewModel(players) } + val steps = viewModel.stepStates.value + LazyColumn( + modifier = Modifier.fillMaxWidth(1f), + ) { + itemsIndexed(steps) { index, step -> + EnterStepsCard(step, viewModel.onStepCardStateChanged(index)) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/entersteps/admin/EnterStepsAdminViewModel.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/entersteps/admin/EnterStepsAdminViewModel.kt new file mode 100644 index 0000000..e16a529 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/entersteps/admin/EnterStepsAdminViewModel.kt @@ -0,0 +1,45 @@ +package hu.mokegyesulet.it.dunestrat.feature.entersteps.admin + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import hu.mokegyesulet.it.dunestrat.model.Player +import hu.mokegyesulet.it.dunestrat.ui.step.StepCardEvent +import hu.mokegyesulet.it.dunestrat.ui.step.StepCardState + +class EnterStepsAdminViewModel(players: List) : ViewModel() { + private val _stepStates: MutableList = ArrayList() + init { + for (p in players) { + _stepStates.add(StepCardState(p.id)) + } + } + val stepStates: MutableState> + get() = mutableStateOf(_stepStates) + + private fun onStepCardStateChanged( + event: StepCardEvent, + playerIndex: Int, + ) { + when (event) { + is StepCardEvent.EnterFieldsChanged -> _stepStates[playerIndex] = + _stepStates[playerIndex].copy(enterFields = event.enterFields) + + is StepCardEvent.LeaveFieldsChanged -> _stepStates[playerIndex] = + _stepStates[playerIndex].copy(leaveFields = event.leaveFields) + + is StepCardEvent.BuildHarvestersChanged -> _stepStates[playerIndex] = + _stepStates[playerIndex].copy(buildHarvesters = event.buildHarvesters) + + is StepCardEvent.PurchaseWeaponsChanged -> { + val x = _stepStates[playerIndex].purchaseWeapons.toMutableMap() + x[event.weaponType] = event.amount + _stepStates[playerIndex] = _stepStates[playerIndex].copy(purchaseWeapons = x) + } + } + } + + fun onStepCardStateChanged(playerIndex: Int): (StepCardEvent) -> Unit = { e -> + onStepCardStateChanged(e, playerIndex) + } +} diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenu.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenu.kt index 0787087..79da864 100644 --- a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenu.kt +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenu.kt @@ -1,66 +1,109 @@ package hu.mokegyesulet.it.dunestrat.feature.mainmenu -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Button -import androidx.compose.material3.Text +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import hu.mokegyesulet.it.dunestrat.model.Game +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainMenu( - onPlayerStepInput: () -> Unit, - onPlaceholder2: () -> Unit, - onTestData: () -> Unit, + onInputMovesClick: (Int) -> Unit, + onStat: (Int) -> Unit, + onNewGameClick: () -> Unit, ) { - val viewModel: MainMenuViewModel = viewModel { MainMenuViewModel() } + val viewModel = viewModel { MainMenuViewModel() } + val loggedIn by viewModel.isLoggedIn.collectAsState(initial = false) - val deserts by viewModel.deserts.collectAsState(initial = emptyList()) + if (loggedIn) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceEvenly, - ) { - Button( - onClick = onPlayerStepInput, - ) { - Text(text = "Player step input") - } + val games by viewModel.games.collectAsStateWithLifecycle() + val selectedGame by viewModel.selectedGame + val players by viewModel.players - Button( - onClick = {}, - ) { - Text(text = "Placeholder 2") - } + Row( + modifier = Modifier.fillMaxSize().padding(50.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + LazyColumn { + item { + Button(onClick = onNewGameClick) { + Row { + Icon(Icons.Filled.Add, contentDescription = "Add new game") + Text(text = "Új játék") + } + } + } - Button( - onClick = onTestData, - ) { - Text(text = "Test data") + items(games) { game: Game -> + Card( + border = if (selectedGame == game) { + BorderStroke(2.dp, Color.Red) + } else { + null + }, + modifier = Modifier.clickable { + viewModel.onEvent(MainMenuViewModel.Event.SelectGame(game)) + }.padding(4.dp), + ) { + Text( + text = game.name, + modifier = Modifier.padding(8.dp), + ) + } + } } - - Button( - onClick = viewModel::onLogout, + Column( + modifier = Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween, ) { - Text(text = "Log out") + Button( + onClick = { onInputMovesClick(selectedGame?.id ?: -1) }, + enabled = selectedGame != null, + ) { + Text(text = "Lépések bevitele") + } + LazyColumn( + modifier = Modifier.weight(1f) + .wrapContentHeight(), + ) { + items(players.toList()) { player -> + Text(text = "${player.id}: ${player.water} víz, ${player.spice} fűszer") + } + } + Button( + onClick = { onStat(selectedGame?.id ?: -1) }, + enabled = selectedGame != null, + ) { + Text(text = "Statisztika") + } } } } else { - Column( + Box( modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, ) { Button( - onClick = viewModel::onLogin, + onClick = { + viewModel.onLogin() + }, + modifier = Modifier.align(Alignment.Center), ) { - Text(text = "Log in") + Text(text = "Bejelentkezés") } } } diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenuViewModel.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenuViewModel.kt index 242159e..2a97070 100644 --- a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenuViewModel.kt +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/mainmenu/MainMenuViewModel.kt @@ -1,18 +1,22 @@ package hu.mokegyesulet.it.dunestrat.feature.mainmenu +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import hu.mokegyesulet.it.dunestrat.backend.AuthStatus import hu.mokegyesulet.it.dunestrat.backend.SupabaseAuth import hu.mokegyesulet.it.dunestrat.backend.SupabaseRepository +import hu.mokegyesulet.it.dunestrat.model.Game +import hu.mokegyesulet.it.dunestrat.model.GameState import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class MainMenuViewModel : ViewModel() { - val deserts = SupabaseRepository.getDeserts() - fun onLogin() { viewModelScope.launch { SupabaseAuth.logIn() @@ -29,4 +33,38 @@ class MainMenuViewModel : ViewModel() { get() = SupabaseAuth.authStatus.map { it is AuthStatus.Authenticated } + + val games = SupabaseRepository.getGames().stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(), + emptyList(), + ) + + val selectedGame = mutableStateOf(null) + + val latestGameState = mutableStateOf(null) + + val players = derivedStateOf { + latestGameState.value?.players ?: emptySet() + } + + sealed class Event { + data class SelectGame(val game: Game) : Event() + } + fun onEvent(event: Event) { + when (event) { + is Event.SelectGame -> { + if (selectedGame.value != event.game) { + selectedGame.value = event.game + viewModelScope.launch { + latestGameState.value = + SupabaseRepository.getLatestGameStateByGameId(event.game.id) + } + } else { + selectedGame.value = null + latestGameState.value = null + } + } + } + } } diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/testdata/TestDataScreen.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/testdata/TestDataScreen.kt index 97d4c14..968cd3d 100644 --- a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/testdata/TestDataScreen.kt +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/feature/testdata/TestDataScreen.kt @@ -15,7 +15,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel @Composable -fun TestDataScreen() { +fun TestDataScreen(onTestBack: () -> Unit) { val viewModel = viewModel { TestDataViewModel() } val deserts by viewModel.deserts.collectAsStateWithLifecycle() val games by viewModel.games.collectAsStateWithLifecycle() @@ -51,6 +51,7 @@ fun TestDataScreen() { Text(text = it.id.toString()) } } + Button(onClick = { viewModel.initDesert() }) { Text("Init test desert") } Button(onClick = { viewModel.initGame(deserts.last()) }) { Text("Init test game") } Button(onClick = { @@ -59,5 +60,6 @@ fun TestDataScreen() { Button(onClick = { viewModel.initPlayerSteps(gameStates.last()) }) { Text("Init test steps") } + Button(onClick = onTestBack) { Text("Vissza") } } } diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/navigation/NavGraph.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/navigation/NavGraph.kt index 3a2ba64..c49eae8 100644 --- a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/navigation/NavGraph.kt +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/navigation/NavGraph.kt @@ -19,14 +19,16 @@ fun NavGraph(navController: NavHostController = rememberNavController()) { ) { composable { MainMenu( - onPlayerStepInput = { navController.navigate(Screen.PlayerStepInput(32)) }, - onPlaceholder2 = {}, - onTestData = { navController.navigate(Screen.TestData) }, + onStat = {}, + onInputMovesClick = { gameId -> + navController.navigate(Screen.PlayerStepInput(gameId)) + }, + onNewGameClick = { + navController.navigate(Screen.InitScreen) + }, ) } - composable { TestDataScreen() } - composable { InitScreen( onNavigateBack = { @@ -48,5 +50,6 @@ fun NavGraph(navController: NavHostController = rememberNavController()) { }, ) } + composable { TestDataScreen(onTestBack = { navController.navigateUp() }) } } } diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/ui/step/EnterStepsCard.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/ui/step/EnterStepsCard.kt new file mode 100644 index 0000000..49a1ae5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/ui/step/EnterStepsCard.kt @@ -0,0 +1,61 @@ +package hu.mokegyesulet.it.dunestrat.ui.step + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import hu.mokegyesulet.it.dunestrat.model.Weapon + +@Composable +fun EnterStepsCard( + stepCardState: StepCardState, + onStepCardStateChanged: (StepCardEvent) -> Unit, +) { + Card { + Column { + Text("Player: ${stepCardState.playerId}") + Row { + Text("Lelép:") + TextField( + value = stepCardState.leaveFields, + onValueChange = { + onStepCardStateChanged(StepCardEvent.LeaveFieldsChanged(it)) + }, + ) + } + Row { + Text("Rálép:") + TextField( + value = stepCardState.enterFields, + onValueChange = { + onStepCardStateChanged(StepCardEvent.EnterFieldsChanged(it)) + }, + ) + } + Row { + Text("Fegyver:") + for (w in Weapon.entries) { + TextField( + value = stepCardState.purchaseWeapons[w].toString(), + onValueChange = { + onStepCardStateChanged( + StepCardEvent.PurchaseWeaponsChanged(w, it.toInt()), + ) + }, + ) + } + } + Row { + Text("Harvester:") + TextField( + value = stepCardState.buildHarvesters, + onValueChange = { + onStepCardStateChanged(StepCardEvent.BuildHarvestersChanged(it)) + }, + ) + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/ui/step/StepCardState.kt b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/ui/step/StepCardState.kt new file mode 100644 index 0000000..431799a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/hu/mokegyesulet/it/dunestrat/ui/step/StepCardState.kt @@ -0,0 +1,22 @@ +package hu.mokegyesulet.it.dunestrat.ui.step + +import hu.mokegyesulet.it.dunestrat.model.Weapon + +data class StepCardState( + val playerId: Int, + val leaveFields: String = "", + val enterFields: String = "", + val purchaseWeapons: Map = mapOf( + Weapon.LASGUN to 0, + Weapon.PISTOL to 0, + Weapon.CRYSKNIFE to 0, + Weapon.LEGION to 0, + ), + val buildHarvesters: String = "", +) +sealed class StepCardEvent { + class LeaveFieldsChanged(val leaveFields: String) : StepCardEvent() + class EnterFieldsChanged(val enterFields: String) : StepCardEvent() + class PurchaseWeaponsChanged(val weaponType: Weapon, val amount: Int) : StepCardEvent() + class BuildHarvestersChanged(val buildHarvesters: String) : StepCardEvent() +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 55299ca..2c934a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.3" +agp = "8.13.2" android-compileSdk = "36" android-minSdk = "24" android-targetSdk = "36"