From 2e47b8bfce8e0c5deb6a1c738326ff69e9d81455 Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Tue, 25 Aug 2026 15:20:37 -0400 Subject: [PATCH] more structure to forms and views shared ui got an interface to demo the components --- desktop/build.gradle.kts | 3 - .../geekback/dopewars/desktop/ClientState.kt | 12 +- .../geekback/dopewars/desktop/DemoMain3.kt | 180 -------------- .../dev/geekback/dopewars/desktop/Main.kt | 131 ---------- .../dev/geekback/dopewars/desktop/Main3.kt | 66 ++++-- gradle.properties | 5 +- gradle/libs.versions.toml | 4 +- server/build.gradle.kts | 3 + server/docs/index.html | 11 +- .../dev/geekback/dopewars/server/Server.kt | 14 +- .../dopewars/server/routes/MarketRoutes.kt | 2 +- .../dopewars/server/routes/ModelCrudRoutes.kt | 5 +- .../dopewars/server/routes/PlayerRoutes.kt | 4 +- .../dopewars/server/services/GameService.kt | 6 +- .../dopewars/server/services/MarketService.kt | 2 +- .../dev/geekback/dopewars/shared/ApiClient.kt | 2 +- .../dev/geekback/dopewars/shared/Drug.kt | 2 +- .../dev/geekback/dopewars/shared/DrugInfo.kt | 10 +- .../dev/geekback/dopewars/shared/Drugs.kt | 14 +- .../dev/geekback/dopewars/shared/Location.kt | 24 +- .../dev/geekback/dopewars/shared/Locations.kt | 13 - .../dev/geekback/dopewars/shared/Market.kt | 7 +- .../dev/geekback/dopewars/shared/Player.kt | 11 +- .../geekback/dopewars/shared/SanityValues.kt | 2 +- .../dopewars/shared/api/MarketResult.kt | 3 +- shared_ui/build.gradle.kts | 15 +- .../dev/geekback/dopewars/ui/AppTheme.kt | 45 ++++ .../geekback/dopewars/ui/DopeWarsModelUi.kt | 32 ++- .../dev/geekback/dopewars/ui/TravelUi.kt | 27 ++- .../dev/geekback/dopewars/ui/api/ApiUi.kt | 1 - .../dopewars/ui/api/MarketRequestUi.kt | 11 +- .../dopewars/ui/api/MarketResponseUi.kt | 11 +- .../ui/api/PlayerNameUpdateRequestUi.kt | 4 +- .../ui/api/PlayerNameUpdateResponseUi.kt | 4 +- .../dopewars/ui/api/TradeRequestUi.kt | 5 +- .../dopewars/ui/api/TravelRequestUi.kt | 2 +- .../dopewars/ui/api/TravelResponseUi.kt | 29 ++- .../kotlin/dev/geekback/dopewars/ui/drug.kt | 154 ++++++------ .../dev/geekback/dopewars/ui/inventory.kt | 16 +- .../dev/geekback/dopewars/ui/inventoryItem.kt | 50 +++- .../dev/geekback/dopewars/ui/location.kt | 79 +++--- .../kotlin/dev/geekback/dopewars/ui/market.kt | 16 +- .../kotlin/dev/geekback/dopewars/ui/player.kt | 103 ++++++-- .../geekback/util/ComposeTimestampControl.kt | 82 +++++++ .../dopewars/ComposeTimestampControlDemo.kt | 33 +++ .../kotlin/dev/geekback/dopewars/DemoMain3.kt | 224 ++++++++++++++++++ .../kotlin/dev/geekback/dopewars}/Screens.kt | 2 +- .../processedResources/wasmJs/main/index.html | 21 -- .../kotlin/dev/geekback/dopewars/web/App.kt | 47 ++-- .../dev/geekback/dopewars/web/PlayersList.kt | 2 +- 50 files changed, 941 insertions(+), 610 deletions(-) delete mode 100644 desktop/src/main/kotlin/dev/geekback/dopewars/desktop/DemoMain3.kt delete mode 100644 desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main.kt delete mode 100644 shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Locations.kt create mode 100644 shared_ui/src/commonMain/kotlin/dev/geekback/util/ComposeTimestampControl.kt create mode 100644 shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/ComposeTimestampControlDemo.kt create mode 100644 shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/DemoMain3.kt rename {desktop/src/main/kotlin/dev/geekback/dopewars/desktop => shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars}/Screens.kt (70%) delete mode 100644 web/build/processedResources/wasmJs/main/index.html diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 358f8c6..4cf4865 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -1,6 +1,5 @@ plugins { kotlin("jvm") - // alias(libs.plugins.kotlin.jvm) alias(libs.plugins.compose) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.serialization) @@ -9,8 +8,6 @@ plugins { dependencies { implementation(project(":shared")) implementation(project(":shared_ui")) - // implementation(libs.compose.icons.material) - // implementation("com.composables:icons-material-icons-filled-cmp:2.2.1") implementation(libs.kotlinx.serialization) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.ktor.client.core) diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt index a433ce8..a8cb873 100644 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt +++ b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt @@ -9,6 +9,7 @@ import dev.geekback.dopewars.shared.api.TravelRequest class ClientState(player: Player) { var player by mutableStateOf(player) + var hasLoggedIn by mutableStateOf(false) var apiUrl: String = "http://localhost:8080" private var api: ApiClient = ApiClient() @@ -21,9 +22,15 @@ class ClientState(player: Player) { suspend fun login() : Player { val p = this.api.login("http://localhost:8080") println("Logged in as ${p.name} / ${p.id}") + this.hasLoggedIn = true return p } + suspend fun logout() { + this.hasLoggedIn = false + + } + suspend fun updateMarket() { println("Updating market") this.player.lastMarket = this.api.market(this.player) @@ -37,8 +44,7 @@ class ClientState(player: Player) { } fun isLoggedIn(): Boolean { - println("Testing if user is logged in - ${this.player.id.isNotEmpty()}") - return this.player.id.isNotEmpty() + return this.hasLoggedIn } fun refreshApi(url: String) { @@ -48,7 +54,7 @@ class ClientState(player: Player) { suspend fun travel(player: Player, destination: TravelRequest) { - val result = this.api.travel("http://localhost:8080", player.id, destination) + this.api.travel("http://localhost:8080", player.id, destination) } diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/DemoMain3.kt b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/DemoMain3.kt deleted file mode 100644 index 08e7c22..0000000 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/DemoMain3.kt +++ /dev/null @@ -1,180 +0,0 @@ -package dev.geekback.dopewars.desktop - -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button -import androidx.compose.material3.DrawerValue -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalDrawerSheet -import androidx.compose.material3.ModalNavigationDrawer -import androidx.compose.material3.Text -import androidx.compose.material3.rememberDrawerState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import dev.geekback.dopewars.shared.Drug -import dev.geekback.dopewars.shared.DrugInfo -import dev.geekback.dopewars.shared.Location -import dev.geekback.dopewars.shared.Locations -import dev.geekback.dopewars.shared.Market -import dev.geekback.dopewars.shared.Player -import dev.geekback.dopewars.shared.api.MarketRequest -import dev.geekback.dopewars.shared.api.MarketResult -import dev.geekback.dopewars.shared.api.PlayerNameUpdateRequest -import dev.geekback.dopewars.shared.api.PlayerNameUpdateResponse -import dev.geekback.dopewars.shared.api.TradeAction -import dev.geekback.dopewars.shared.api.TradeRequest -import dev.geekback.dopewars.shared.api.TravelRequest -import dev.geekback.dopewars.shared.api.TravelResult -import dev.geekback.dopewars.ui.DopeWarsUi -import kotlinx.coroutines.launch - -fun main() = application { - Window( - onCloseRequest = ::exitApplication, - title = "DopeWars Dev Client" - ) { - DemoApp() - } -} - -data class ApiData( - var marketRequest: MarketRequest, - var marketResult: MarketResult, - var playerNameUpdateRequest: PlayerNameUpdateRequest, - var playerNameUpadteResponse: PlayerNameUpdateResponse, - var tradeAction: TradeAction, - var tradeRequest: TradeRequest, - var travelRequest: TravelRequest, - var travelResult: TravelResult -) - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DemoApp() { - val drawerState = rememberDrawerState(DrawerValue.Closed) - val scope = rememberCoroutineScope() - var currentScreen by remember { mutableStateOf(Screens.HOME) } - var drug by remember { mutableStateOf(DrugInfo(drug = Drug.WEED, minValue = 100.0f, maxValue = 1000.0f)) } - var player by remember { mutableStateOf(Player(id = "____", name = "TBD")) } - var location by remember { mutableStateOf(Location(name = "LocationDefault"))} - var market by remember { mutableStateOf(Market( - location = Location(name = "MarketDefault"), - prices = mapOf() - ))} - - val ui = DopeWarsUi.new() - - val travelResultLocation = Locations.random(); - - var api by remember { mutableStateOf(ApiData( - marketRequest = MarketRequest( - location = Location(name = "MarketRequestLocation") - ), - marketResult = MarketResult( - market = Market(location = Location(name = "MarketResultLocation"), - prices = emptyMap() - ) - ), - playerNameUpdateRequest = PlayerNameUpdateRequest("newPlayerName"), - playerNameUpadteResponse = PlayerNameUpdateResponse( - updatedPlayer = Player(id = "____", name = "updatedPlayerName") - ), - tradeAction = TradeAction.BUY, - tradeRequest = TradeRequest( - action = TradeAction.BUY, - drug = Drug.random(), - quantity = 10 - ), - travelRequest = TravelRequest( - destination = Locations.random() - ), - travelResult = TravelResult( - location = travelResultLocation, - day = 1, - market = Market(location = travelResultLocation, mapOf()) - ) - )) } - - MaterialTheme { - ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { - ModalDrawerSheet { - Button(onClick = { - currentScreen = Screens.HOME - scope.launch { drawerState.close() } - }) { Text("Home")} - - - Button( onClick = { - currentScreen = Screens.DRUG - scope.launch { drawerState.close() } - }) { Text("Drug") } - - Button( onClick = { - currentScreen = Screens.PLAYER - scope.launch { drawerState.close() } - }) { Text("Player") } - - Button(onClick = { - currentScreen = Screens.LOCATION - scope.launch { drawerState.close() } - }) { Text("Location") } - - Button( onClick = { - currentScreen = Screens.API - scope.launch { drawerState.close() } - }) { Text("API") } - - } - } - ) { - // Main content - when (currentScreen) { - Screens.HOME -> { - Column { - ui.player.view(player) - // ui.drug.view(drug) - } - } - Screens.DRUG -> { - // ui.drug.form(drug) { - // drug = it - // } - } - Screens.PLAYER -> { - ui.player.form(player) { - player = it - } - } - Screens.LOCATION -> { - ui.location.form(location) { - location = it - } - } - Screens.MARKET -> { - ui.market.form(market) { - market = it - } - } - Screens.API -> { - Column { - ui.api.marketRequest.form(api.marketRequest) { api.marketRequest = it } - ui.api.marketResponse.form(api.marketResult) { api.marketResult = it } - ui.api.playerNameUpdateRequest.form(api.playerNameUpdateRequest) { api.playerNameUpdateRequest = it } - ui.api.playerNameUpdateResponse.form(api.playerNameUpadteResponse) { api.playerNameUpadteResponse = it } - ui.api.travelRequest.form(api.travelRequest) { api.travelRequest = it } - ui.api.travelResponse.form(api.travelResult) { api.travelResult = it} - ui.api.tradeRequest.form(api.tradeRequest) { api.tradeRequest = it} - } - } - } - } - } -} diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main.kt b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main.kt deleted file mode 100644 index dd3e447..0000000 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main.kt +++ /dev/null @@ -1,131 +0,0 @@ -package dev.geekback.dopewars.desktop - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import dev.geekback.dopewars.shared.Location -import dev.geekback.dopewars.shared.Player -import dev.geekback.dopewars.ui.AppTheme - - -fun main() { - val state = ClientState(Player(id = "", name = "TestClient")) - // var drug = Drug("TestDrug", minPrice = 10.0f, maxPrice = 100.0f) - var player = Player(id = "default", name = "defaultName") - var location = Location(name = "DefaultLocation") - application { - var currentScreen = remember { Screens.DRUG } - val scope = rememberCoroutineScope() - // state.updateApiUrl("http://localhost:8080") - // val scaffoldState = rememberScaffoldState(drawerState = DrawerState(DrawerValue.Closed)) - - // var menuVisible by remember { mutableStateOf(false) } - Window(onCloseRequest = ::exitApplication, title = "DopeWars") { - /* - Scaffold( - scaffoldState = scaffoldState, - drawerContent = { - Button(onClick = { - currentScreen = Screens.DRUG - scope.launch { - scaffoldState.drawerState.close() - } - }) { Text("Drug")} - - Button(onClick = { - currentScreen = Screens.PLAYER - scope.launch { - scaffoldState.drawerState.close() - } - }) { Text("Player") } - }, - topBar = { - TopAppBar( - title = { - Text("Title") - }, - navigationIcon = { - IconButton(onClick = { - scope.launch { - scaffoldState.drawerState.open() - } - }) { - Icon(imageVector = MaterialIcons.Filled.Menu , contentDescription = "Menu") - } - }) - } - ) { - */ - AppTheme { - Text("Test123") - - Column(modifier = Modifier.fillMaxSize()) { - /* - when (currentScreen) { - Screens.DRUG -> { - drugForm(drug) { - drug = it - } - - drugView(drug) - } - Screens.PLAYER -> { - playerView(player) - } - Screens.LOCATION -> { - locationView(location) - locationForm(location) { - location = it - } - } - Screens.INVENTORY -> { - inventoryView(emptyList()) - } - Screens.MARKET -> { - marketView(Market(Location("Unknown Location"), emptyMap())) - } - } - */ - } - } - } - - /* - println("Session ID = ${state.player.id}") - Column { - Text("DopeWars Desktop Client") - configPanel(config = state, onUrlChanged = { - state.updateApiUrl(it) - }) - PlayerForm( - initialState = state.player, - onRequestLogin = { - scope.launch { - state.login() - } - }, - onRequestPlayerRename = { newName -> - scope.launch { - state.renamePlayer(newName) - } - }, - onGetMarket = { - scope.launch { - state.updateMarket() - } - }, - onRefresh = { - scope.launch { - state.refreshPlayer() - } - } - ) - } - */ - } -} \ No newline at end of file diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main3.kt b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main3.kt index 6a2368b..702d1f4 100644 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main3.kt +++ b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Main3.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.Text +import androidx.compose.material3.Icon import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -21,6 +22,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import compose.icons.FeatherIcons +import compose.icons.feathericons.LogIn import dev.geekback.dopewars.ui.AppTheme import dev.geekback.dopewars.ui.PlayerUi import dev.geekback.dopewars.ui.TravelUi @@ -51,31 +54,45 @@ fun App() { ModalDrawerSheet { if (state.isLoggedIn()) { // Display the current player state - PlayerUi().view(state.player) + PlayerUi().view(state.player) {} } else { - NavigationDrawerItem( - label = { - Row { - /* - Icon( - modifier = Modifier.padding(16.dp), - imageVector = , - contentDescription = "Login Icon" - ) - */ - Text( - modifier = Modifier.padding(16.dp), - text = "Login" - ) + if (state.isLoggedIn()) { + NavigationDrawerItem( + label = { + Row { + Text("Logout") + } + }, + selected = false, + onClick = { + scope.launch { + state.logout() + } } - }, - selected = false, - onClick = { - scope.launch { - state.player = state.login() + ) + } else { + NavigationDrawerItem( + label = { + Row { + Icon( + modifier = Modifier.padding(16.dp), + imageVector = FeatherIcons.LogIn, + contentDescription = "Login Icon" + ) + Text( + modifier = Modifier.padding(16.dp), + text = "Login" + ) + } + }, + selected = false, + onClick = { + scope.launch { + state.player = state.login() + } } - } - ) + ) + } } } } @@ -91,13 +108,12 @@ fun App() { } } - @Composable fun mainWindow(state: ClientState) { - val scope = rememberCoroutineScope(); + val scope = rememberCoroutineScope() var showingTravelWizard by remember { mutableStateOf(false) } - PlayerUi().view(state.player) + PlayerUi().view(state.player) {} Row { Button(onClick = { println("Transactions") diff --git a/gradle.properties b/gradle.properties index 257de41..4f5ba7f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,5 @@ org.gradle.configuration-cache=true -org.gradle.jvmargs=-Xmx12g \ No newline at end of file +org.gradle.jvmargs=-Xmx12g +org.gradle.tooling.parallel=true +org.gradle.parallel=true +org.gradle.daemon=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f787155..8a1406e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -ktor = "3.5.1" +ktor = "3.5.2" kotlin = "2.4.0" compose = "1.11.1" compose-material3 = "1.11.0-alpha07" @@ -34,6 +34,8 @@ ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" } ktor-server-status-pages = { group ="io.ktor", name="ktor-server-status-pages", version.ref="ktor" } ktor-server-logging = { group = "io.ktor", name="ktor-server-call-logging", version.ref="ktor" } +ktor-server-cors = { group = "io.ktor", name="ktor-server-cors", version.ref="ktor" } + # KTOR client ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref="ktor" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index eb98050..0dcf03c 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -46,6 +46,9 @@ dependencies { // Websockets implementation(libs.ktor.server.websockets) + // CORS + implementation(libs.ktor.server.cors) + // General Use implementation(libs.kotlinx.datetime) diff --git a/server/docs/index.html b/server/docs/index.html index fb8312c..8d73b11 100644 --- a/server/docs/index.html +++ b/server/docs/index.html @@ -698,20 +698,13 @@ }, "quantity" : { } } -}; - defs["Location"] = { - "title" : "Location", - "required" : [ "name" ], - "properties" : { - "name" : { } - } }; defs["Market"] = { "title" : "Market", "required" : [ "location", "prices" ], "properties" : { "location" : { - "$ref" : "#/components/schemas/Location" + "enum" : [ "BROOKLYN", "BRONX", "STATTEN_ISLAND", "QUEENS" ] }, "prices" : { "additionalProperties" : { } @@ -737,7 +730,7 @@ "debt" : { }, "health" : { }, "location" : { - "$ref" : "#/components/schemas/Location" + "enum" : [ "BROOKLYN", "BRONX", "STATTEN_ISLAND", "QUEENS" ] }, "day" : { }, "inventory" : { diff --git a/server/src/main/kotlin/dev/geekback/dopewars/server/Server.kt b/server/src/main/kotlin/dev/geekback/dopewars/server/Server.kt index 318f2f9..b17bc4f 100644 --- a/server/src/main/kotlin/dev/geekback/dopewars/server/Server.kt +++ b/server/src/main/kotlin/dev/geekback/dopewars/server/Server.kt @@ -6,6 +6,8 @@ import dev.geekback.dopewars.server.routes.modelCrudRoutes import dev.geekback.dopewars.server.routes.playerRoutes import dev.geekback.dopewars.server.services.GameService import dev.geekback.dopewars.server.services.MarketService +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.engine.* @@ -13,6 +15,7 @@ import io.ktor.server.http.content.* import io.ktor.server.netty.* import io.ktor.server.plugins.calllogging.* import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.cors.routing.CORS import io.ktor.server.plugins.openapi.* import io.ktor.server.routing.* import io.ktor.server.websocket.* @@ -27,7 +30,7 @@ fun Application.module() { } println("[ # ] Loading Web Sockets Plugin") - install(WebSockets); + install(WebSockets) println("[ # ] Loading Content Negotiation Plugin") install(ContentNegotiation) { @@ -39,6 +42,13 @@ fun Application.module() { }) } + println("[ # ] Loading CORS Plugin") + install(CORS) { + anyHost() + allowMethod(HttpMethod.Options) + allowHeader(HttpHeaders.ContentType) + } + println("[ # ] Plugins Loaded") val marketService = MarketService() @@ -52,7 +62,7 @@ fun Application.module() { modelCrudRoutes(marketService) webSocket("/ws") { - send("Welcome to DopeWars"); + send("Welcome to DopeWars") for (frame in incoming) { if (frame is Frame.Text) send("Echo: ${frame.readText()}") } diff --git a/server/src/main/kotlin/dev/geekback/dopewars/server/routes/MarketRoutes.kt b/server/src/main/kotlin/dev/geekback/dopewars/server/routes/MarketRoutes.kt index 01b6040..774eb31 100644 --- a/server/src/main/kotlin/dev/geekback/dopewars/server/routes/MarketRoutes.kt +++ b/server/src/main/kotlin/dev/geekback/dopewars/server/routes/MarketRoutes.kt @@ -24,7 +24,7 @@ fun Route.marketRoutes( get { val players = PlayerRepository.all().toList() - val response: MutableList = mutableListOf(); + val response: MutableList = mutableListOf() players.forEach { player -> response.add(player.lastMarket!!) } diff --git a/server/src/main/kotlin/dev/geekback/dopewars/server/routes/ModelCrudRoutes.kt b/server/src/main/kotlin/dev/geekback/dopewars/server/routes/ModelCrudRoutes.kt index 66b9b31..677fa9a 100644 --- a/server/src/main/kotlin/dev/geekback/dopewars/server/routes/ModelCrudRoutes.kt +++ b/server/src/main/kotlin/dev/geekback/dopewars/server/routes/ModelCrudRoutes.kt @@ -15,10 +15,9 @@ fun Route.modelCrudRoutes(marketService: MarketService) { // List get { - val modelName = call.parameters["modelName"] - when (modelName) { + when (val modelName = call.parameters["modelName"]) { "market" -> { - call.respond(marketService.generateMarket(Location("BRONX"))) + call.respond(marketService.generateMarket(Location.BRONX)) } else -> { call.respond("Model name is $modelName") diff --git a/server/src/main/kotlin/dev/geekback/dopewars/server/routes/PlayerRoutes.kt b/server/src/main/kotlin/dev/geekback/dopewars/server/routes/PlayerRoutes.kt index 10dfdb5..a4b5814 100644 --- a/server/src/main/kotlin/dev/geekback/dopewars/server/routes/PlayerRoutes.kt +++ b/server/src/main/kotlin/dev/geekback/dopewars/server/routes/PlayerRoutes.kt @@ -27,7 +27,7 @@ fun Route.playerRoutes( // Create post { - val location = Location("Brooklyn") + val location = Location.BROOKLYN val mkt = marketService.generateMarket(location) val player = Player( @@ -59,7 +59,7 @@ fun Route.playerRoutes( // Read get { - val players = PlayerRepository.all(); + val players = PlayerRepository.all() call.respond( HttpStatusCode.OK, diff --git a/server/src/main/kotlin/dev/geekback/dopewars/server/services/GameService.kt b/server/src/main/kotlin/dev/geekback/dopewars/server/services/GameService.kt index 9810817..8c9a99f 100644 --- a/server/src/main/kotlin/dev/geekback/dopewars/server/services/GameService.kt +++ b/server/src/main/kotlin/dev/geekback/dopewars/server/services/GameService.kt @@ -60,7 +60,7 @@ class GameService( player: Player, drug: Drug, quantity: Int, - price: Float + price: Int ) { val cost = price * quantity @@ -90,7 +90,7 @@ class GameService( player: Player, drug: Drug, quantity: Int, - price: Float + price: Int ) { val inventoryItem = player.inventory @@ -104,7 +104,7 @@ class GameService( } inventoryItem.quantity -= quantity - player.cash += price * quantity.toFloat() + player.cash += price * quantity if (inventoryItem.quantity == 0) { player.inventory.remove(inventoryItem) diff --git a/server/src/main/kotlin/dev/geekback/dopewars/server/services/MarketService.kt b/server/src/main/kotlin/dev/geekback/dopewars/server/services/MarketService.kt index 48d27c2..5a839cd 100644 --- a/server/src/main/kotlin/dev/geekback/dopewars/server/services/MarketService.kt +++ b/server/src/main/kotlin/dev/geekback/dopewars/server/services/MarketService.kt @@ -8,7 +8,7 @@ import dev.geekback.dopewars.shared.Market class MarketService { fun generateMarket(location: Location): Market { - val prices: MutableMap = mutableMapOf() + val prices: MutableMap = mutableMapOf() Drugs.all.forEach { drugInfo -> prices[drugInfo.drug] = drugInfo.newPrice() diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt index 06c7fd7..84273d8 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt @@ -20,7 +20,7 @@ import kotlinx.serialization.json.Json * * Client for accessing the Dopewars API server */ -class ApiClient() { +class ApiClient { private var address: String = "" private val client = HttpClient(CIO) { install(ContentNegotiation) { diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drug.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drug.kt index 2511aee..5bd71ba 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drug.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drug.kt @@ -17,6 +17,6 @@ enum class Drug { companion object { fun all() = listOf(WEED, COCAINE, HASH, ACID, HEROIN, MUSHROOMS, METH) - fun random() = Drug.WEED + fun random() = WEED } } diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/DrugInfo.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/DrugInfo.kt index d6efeec..e165151 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/DrugInfo.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/DrugInfo.kt @@ -5,11 +5,11 @@ import kotlin.random.Random @Serializable data class DrugInfo( - val drug: Drug, - val minValue: Float, - val maxValue: Float, + var drug: Drug, + val minValue: Int, + val maxValue: Int, ) { - fun newPrice(): Float { - return Random.nextFloat() * (this.maxValue - this.minValue) + fun newPrice(): Int { + return Random.nextInt() * (this.maxValue - this.minValue) } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drugs.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drugs.kt index 11520dd..8e7b42c 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drugs.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Drugs.kt @@ -2,13 +2,13 @@ package dev.geekback.dopewars.shared object Drugs { val all = listOf( - DrugInfo(Drug.WEED, 300.0f, 1000.0f), - DrugInfo(Drug.ACID, 1000.0f, 4500.0f), - DrugInfo(Drug.COCAINE, 15000.0f, 30000.0f), - DrugInfo(Drug.HASH, 5000.0f, 14000.0f), - DrugInfo(Drug.MUSHROOMS, 500.0f, 2500.0f), - DrugInfo(Drug.METH, 2000.0f, 9000.0f), - DrugInfo(Drug.HEROIN, minValue = 4000.0f, 9500.0f) + DrugInfo(Drug.WEED, 30000, 100000), + DrugInfo(Drug.ACID, 100000, 450000), + DrugInfo(Drug.COCAINE, 1500000, 3000000), + DrugInfo(Drug.HASH, 500000, 1400000), + DrugInfo(Drug.MUSHROOMS, 50000, 250000), + DrugInfo(Drug.METH, 200000, 900000), + DrugInfo(Drug.HEROIN, minValue = 400000, 950000) ) fun random() = all.random() diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Location.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Location.kt index 03c09a7..5d7b510 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Location.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Location.kt @@ -3,6 +3,24 @@ package dev.geekback.dopewars.shared import kotlinx.serialization.Serializable @Serializable -data class Location( - var name: String -) \ No newline at end of file +enum class Location { + BROOKLYN, + BRONX, + STATTEN_ISLAND, + QUEENS; + + companion object { + fun all(): List { + return listOf( + BROOKLYN, + BRONX, + STATTEN_ISLAND, + QUEENS + ) + } + + fun random(): Location { + return all().random() + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Locations.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Locations.kt deleted file mode 100644 index 2da139c..0000000 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Locations.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.geekback.dopewars.shared - -object Locations { - val all = listOf( - Location("Brooklyn"), - Location("Bronx"), - Location("Queens"), - Location("Statten Island"), - ) - - fun random() = all.random() -} - diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Market.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Market.kt index 53b0038..0fe679a 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Market.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Market.kt @@ -4,9 +4,10 @@ import kotlinx.serialization.Serializable @Serializable data class Market( + // Location of the market val location: Location, - val prices: Map + // Map of Drug to price in cents + val prices: Map ) { - companion object { - } + companion object } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Player.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Player.kt index 476f1cb..0e0119a 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Player.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/Player.kt @@ -2,15 +2,18 @@ package dev.geekback.dopewars.shared import kotlinx.serialization.Serializable import kotlin.time.Clock +import kotlin.uuid.Uuid @Serializable data class Player( val id: String, val name: String, - var cash: Float = 2000f, - var debt: Float = 5500f, + // Cash in cents + var cash: Int = 200000, + // Debt in cents + var debt: Int = 550000, var health: Int = 100, - var location: Location = Location("Brooklyn"), + var location: Location = Location.BROOKLYN, var day: Int = 1, val inventory: MutableList = mutableListOf(), var lastMarket: Market? = null, @@ -19,7 +22,7 @@ data class Player( companion object { fun new(): Player { return Player( - id = "", + id = Uuid.random().toString(), name = "Default Player", ) } diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/SanityValues.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/SanityValues.kt index cfab8d8..4d3f033 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/SanityValues.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/SanityValues.kt @@ -1,3 +1,3 @@ package dev.geekback.dopewars.shared -const val DRUG_MAX_PRICE: Float = 100_000f; +const val DRUG_MAX_PRICE: Float = 100_000f diff --git a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/api/MarketResult.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/api/MarketResult.kt index 7e9ecbe..3e34708 100644 --- a/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/api/MarketResult.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/api/MarketResult.kt @@ -5,5 +5,4 @@ import kotlinx.serialization.Serializable @Serializable data class MarketResult( - val market: Market) { -} + val market: Market) diff --git a/shared_ui/build.gradle.kts b/shared_ui/build.gradle.kts index f66f6a8..0f95f6b 100644 --- a/shared_ui/build.gradle.kts +++ b/shared_ui/build.gradle.kts @@ -22,12 +22,25 @@ kotlin { implementation(libs.compose.runtime) implementation(libs.compose.foundation) implementation(libs.compose.ui) + implementation("org.jetbrains.compose.ui:ui-tooling-preview:1.10.0") + implementation(libs.kotlinx.datetime) + api("br.com.devsrsouza.compose.icons:feather:1.1.1") + } wasmJsMain.dependencies { implementation(libs.wrappers.browser) } - jvmMain.dependencies {} + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + } } } + +compose.desktop { + application { + mainClass = "dev.geekback.dopewars.DemoMain3Kt" + // mainClass = "dev.geekback.dopewars.ComposeTimestampControlDemoKt" + } +} \ No newline at end of file diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/AppTheme.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/AppTheme.kt index 7ecc748..de9abdf 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/AppTheme.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/AppTheme.kt @@ -3,8 +3,10 @@ package dev.geekback.dopewars.ui import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -40,3 +42,46 @@ fun BorderedCard(title: String, content: @Composable () -> Unit) { } } } + +@Composable +fun ViewFrame( + title: String, + onRequestEdit: () -> Unit, + content: @Composable () -> Unit) { + BorderedCard(title) { + Column { + content() + + Row { + Button(onClick = onRequestEdit) { Text("Edit") } + } + } + } +} + +@Composable +fun FormFrame( + title: String, + onModelUpdate: () -> Unit, + onModelReset: () -> Unit, + onCancelEdit: () -> Unit, + content: @Composable () -> Unit +) { + BorderedCard(title) { + Column { + content() + + Row { + Button(onClick = onModelUpdate) { + Text("Update") + } + Button(onClick = onModelReset) { + Text("Reset") + } + Button(onClick = onCancelEdit) { + Text("Cancel") + } + } + } + } +} diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/DopeWarsModelUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/DopeWarsModelUi.kt index 4e87710..785ab6b 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/DopeWarsModelUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/DopeWarsModelUi.kt @@ -1,13 +1,43 @@ package dev.geekback.dopewars.ui +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import dev.geekback.dopewars.shared.Player import dev.geekback.dopewars.ui.api.ApiUi interface DopeWarsModelUi { @Composable - fun view(model: T); + fun view(model: T, onRequestEdit: () -> Unit) + @Composable fun form(model: T, onModelUpdate: (T) -> Unit) + + @Composable + fun field(model: T, onChanged: (T) -> Unit) { + var displayModel by remember { mutableStateOf(model) } + var isEditing by remember { mutableStateOf(false) } + + Column { + Text("IsEditing -> $isEditing") + + if (isEditing) { + form(displayModel) { + displayModel = it + isEditing = false + onChanged(it) + } + } else { + view(displayModel) { + isEditing = true + } + } + } + } } data class DopeWarsUi( diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/TravelUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/TravelUi.kt index 7982d0a..f5cf58b 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/TravelUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/TravelUi.kt @@ -1,12 +1,14 @@ package dev.geekback.dopewars.ui import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -16,7 +18,10 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import dev.geekback.dopewars.shared.Locations +import compose.icons.FeatherIcons +import compose.icons.feathericons.LogIn +import compose.icons.feathericons.Menu +import dev.geekback.dopewars.shared.Location import dev.geekback.dopewars.shared.Player import dev.geekback.dopewars.shared.api.TravelRequest @@ -30,19 +35,21 @@ class TravelUi { fun wizard(current: Player, onTicketPunched: (TravelRequest) -> Unit) { var stage by remember { mutableStateOf(0) } var pickedLocation by remember { mutableStateOf(0) } - val locations = Locations.all + val locations = Location.all() var expanded by remember { mutableStateOf(false) } - // Window(onCloseRequest = { onTicketPunched(TravelRequest(destination = current.location)) }, title = "Select New Location") { - /* + Card(modifier = Modifier.padding(16.dp).fillMaxSize()) { Column { Text("Wizard") - var index = 0; + var index = 0 when (stage) { 0 -> { - Text(text = locations[pickedLocation].name) - IconButton(onClick = { expanded = !expanded }) { - // Icon(MaterialIcons.Filled.Menu, contentDescription = "More options") + Row { + Text(text = locations[pickedLocation].name) + IconButton(onClick = { expanded = !expanded }) { + Icon(imageVector = FeatherIcons.Menu, contentDescription = "Login") + // Icon(MaterialIcons.Filled.Menu, contentDescription = "More options") + } } DropdownMenu( expanded = expanded, @@ -57,7 +64,7 @@ class TravelUi { println("location index to $index") } ) - index += 1; + index += 1 } } @@ -79,7 +86,5 @@ class TravelUi { } } } - */ - // } } } \ No newline at end of file diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/ApiUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/ApiUi.kt index 26b2cf3..6c6e9db 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/ApiUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/ApiUi.kt @@ -6,7 +6,6 @@ data class ApiUi( val playerNameUpdateRequest: PlayerNameUpdateRequestUi, val playerNameUpdateResponse: PlayerNameUpdateResponseUi, val tradeRequest: TradeRequestUi, - // TODO: tradeResponse val travelRequest: TravelRequestUi, val travelResponse: TravelResponseUi, ) { diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketRequestUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketRequestUi.kt index d485a5c..407c89f 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketRequestUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketRequestUi.kt @@ -16,9 +16,11 @@ import dev.geekback.dopewars.ui.LocationUi class MarketRequestUi: DopeWarsModelUi { @Composable - override fun view(model: MarketRequest) { + override fun view(model: MarketRequest, onRequestEdit: () -> Unit) { BorderedCard("Market Request for ${model.location.name}") { - LocationUi().view(model.location) + LocationUi().view(model.location) { + println("location edit requested") + } } } @@ -44,4 +46,9 @@ class MarketRequestUi: DopeWarsModelUi { } } } +} + +@Composable +fun form_skeleton(model: T, onModelUpdate: (T) -> Unit, onFieldUpdate: (T) -> Unit) { + } \ No newline at end of file diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketResponseUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketResponseUi.kt index 13d2669..6923730 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketResponseUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/MarketResponseUi.kt @@ -5,12 +5,14 @@ import dev.geekback.dopewars.shared.api.MarketResult import dev.geekback.dopewars.ui.BorderedCard import dev.geekback.dopewars.ui.DopeWarsModelUi import dev.geekback.dopewars.ui.MarketUi +import dev.geekback.dopewars.ui.ViewFrame class MarketResponseUi: DopeWarsModelUi { @Composable - override fun view(model: MarketResult) { - BorderedCard("Market Result for ${model.market.location.name}") { - MarketUi().view(model.market) + override fun view(model: MarketResult, onRequestEdit: () -> Unit) { + ViewFrame(title = "Market Result for ${model.market.location.name}", + onRequestEdit = onRequestEdit) { + println("On Request Edit") } } @@ -25,5 +27,4 @@ class MarketResponseUi: DopeWarsModelUi { } } } - -} \ No newline at end of file +} diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateRequestUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateRequestUi.kt index be999a5..5dfb2c0 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateRequestUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateRequestUi.kt @@ -7,9 +7,9 @@ import dev.geekback.dopewars.ui.DopeWarsModelUi import dev.geekback.dopewars.shared.api.PlayerNameUpdateRequest import dev.geekback.dopewars.ui.BorderedCard -class PlayerNameUpdateRequestUi(): DopeWarsModelUi { +class PlayerNameUpdateRequestUi : DopeWarsModelUi { @Composable - override fun view(model: PlayerNameUpdateRequest) { + override fun view(model: PlayerNameUpdateRequest, onRequestEdit: () -> Unit) { BorderedCard("Player Name Update Request for ${model.newName}") { } } diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateResponseUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateResponseUi.kt index a3dcb53..77504f6 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateResponseUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/PlayerNameUpdateResponseUi.kt @@ -8,9 +8,9 @@ import dev.geekback.dopewars.ui.BorderedCard class PlayerNameUpdateResponseUi: DopeWarsModelUi { @Composable - override fun view(model: PlayerNameUpdateResponse) { + override fun view(model: PlayerNameUpdateResponse, onRequestEdit: () -> Unit) { BorderedCard("Player Name Update Response") { - PlayerUi().view(model.updatedPlayer) + PlayerUi().view(model.updatedPlayer) {} } } diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TradeRequestUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TradeRequestUi.kt index 21e0d6d..4278d19 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TradeRequestUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TradeRequestUi.kt @@ -14,7 +14,7 @@ import dev.geekback.dopewars.ui.DopeWarsModelUi class TradeRequestUi: DopeWarsModelUi { @Composable - override fun view(model: TradeRequest) { + override fun view(model: TradeRequest, onRequestEdit: () -> Unit) { BorderedCard("Trade Request for ${model.drug.name} / ${model.action} / ${model.quantity}") { } @@ -26,9 +26,6 @@ class TradeRequestUi: DopeWarsModelUi { onModelUpdate: (TradeRequest) -> Unit ) { var localModel by remember { mutableStateOf(model) } - var actionIndex by remember { mutableStateOf(0) } - var quantity by remember { mutableStateOf(model.quantity) } - var drug by remember { mutableStateOf(model.drug) } BorderedCard("Trade Requesst Form") { Row { diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelRequestUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelRequestUi.kt index 9ef2b1e..6e3c82c 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelRequestUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelRequestUi.kt @@ -8,7 +8,7 @@ import dev.geekback.dopewars.ui.BorderedCard class TravelRequestUi: DopeWarsModelUi { @Composable - override fun view(model: TravelRequest) { + override fun view(model: TravelRequest, onRequestEdit: () -> Unit) { BorderedCard("Travel Request to ${model.destination.name}") { } } diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelResponseUi.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelResponseUi.kt index 1e18b1e..542bfd3 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelResponseUi.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/api/TravelResponseUi.kt @@ -17,27 +17,24 @@ import dev.geekback.dopewars.ui.MarketUi class TravelResponseUi: DopeWarsModelUi { @Composable - override fun view(model: TravelResult) { + override fun view(model: TravelResult, onRequestEdit: () -> Unit) { BorderedCard("Travel Result ${model.location.name}") { - LocationUi().view(model.location) + LocationUi().view(model.location) {} Text("Day: ${model.day}") - MarketUi().view(model.market) + MarketUi().view(model.market) {} } } @Composable override fun form(model: TravelResult, onModelUpdate: (TravelResult) -> Unit) { - var day by remember { mutableStateOf(model.day) } - var location by remember { mutableStateOf(model.location) } - var market by remember { mutableStateOf(model.market) } - var localModel by remember { mutableStateOf(model) } BorderedCard("Travel Result Form") { LocationUi().form(model.location) { println("Update location to $it") } + // // // Row { @@ -53,4 +50,22 @@ class TravelResponseUi: DopeWarsModelUi { } } +} + + +@Composable +fun ViewFormButtons(onRequestEdit: () -> Unit) { + Row { + Button(onClick = onRequestEdit) { + Text("Edit") + } + } +} + +@Composable +fun FormButtons(onModelUpdate: () -> Unit, onModelReset: () -> Unit) { + Row { + Button(onClick = onModelUpdate) { Text("Apply") } + Button(onClick = onModelReset) { Text("Reset") } + } } \ No newline at end of file diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/drug.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/drug.kt index 5cc5a8e..1207fe5 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/drug.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/drug.kt @@ -1,92 +1,110 @@ package dev.geekback.dopewars.ui +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.onClick import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.RangeSlider import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import dev.geekback.dopewars.shared.DRUG_MAX_PRICE import dev.geekback.dopewars.shared.Drug import dev.geekback.dopewars.shared.DrugInfo -class DrugInfoUi: DopeWarsModelUi { - @Composable - override fun view(model: DrugInfo) { - BorderedCard(model.drug.name) { - Column { - Text("Price: ${model.minValue} - ${model.maxValue}") - } - } - } - - @Composable - override fun form( - model: DrugInfo, - onModelUpdate: (DrugInfo) -> Unit - ) { - var drug by remember { mutableStateOf(model) } - var name by remember { mutableStateOf(model.drug.name) } - var minPrice by remember { mutableFloatStateOf(model.minValue) } - var maxPrice by remember { mutableFloatStateOf(model.maxValue) } - - BorderedCard("Drug Form") { - Column { - // Name - TextField( - value = name, - onValueChange = { name = it }, - label = { Text("Name") }, - ) - - Row { - TextField( - value = minPrice.toString(), - onValueChange = { minPrice = it.toFloatOrNull() ?: minPrice }, - label = { Text("Min Price") }, +class DrugInfoUi : DopeWarsModelUi { + @OptIn(ExperimentalFoundationApi::class) + @Composable + fun picker(default: Drug, onSelect: (Drug) -> Unit) { + var showOptions by remember { mutableStateOf(false) } + Column { + Row { + Text( + modifier = Modifier.onClick { + showOptions = true + }, + text = default.name + ) + DropdownMenu( + expanded = showOptions, + onDismissRequest = { showOptions = false }, + ) { + Drug.all().forEach { localDrug -> + DropdownMenuItem( + onClick = { + showOptions = false + onSelect(localDrug) + }, + text = { Text(localDrug.name) } ) - - TextField( - value = maxPrice.toString(), - onValueChange = { maxPrice = it.toFloatOrNull() ?: maxPrice }, - label = { Text("Max Price") }, - ) - } - - RangeSlider( - value = (minPrice)..(maxPrice), - onValueChangeFinished = { }, - valueRange = 1f..DRUG_MAX_PRICE, - onValueChange = { it -> - minPrice = it.start - maxPrice = it.endInclusive - } - ) - - Row { - Button(onClick = { - drug = DrugInfo(Drug.WEED , minPrice, maxPrice) - onModelUpdate(drug) - }) { - Text("Apply") - } - - Button(onClick = { - name = Drug.WEED.name - minPrice = drug.minValue - maxPrice = drug.maxValue - }) { - Text("Cancel") - } } } } - } } + + @Composable + override fun view(model: DrugInfo, onRequestEdit: () -> Unit) { + ViewFrame(title = model.drug.name, onRequestEdit = onRequestEdit) { + Text("Price: ${model.minValue} - ${model.maxValue}") + } + } + + @Composable + override fun form( + model: DrugInfo, + onModelUpdate: (DrugInfo) -> Unit + ) { + var drug by remember { mutableStateOf(model.drug) } + var minPrice by remember { mutableIntStateOf(model.minValue) } + var maxPrice by remember { mutableIntStateOf(model.maxValue) } + + FormFrame( + title = "Drug Form", + onModelUpdate = { onModelUpdate(DrugInfo(drug, minPrice, maxPrice)) }, + onModelReset = { + drug = model.drug + minPrice = model.minValue + maxPrice = model.maxValue + }, + onCancelEdit = { onModelUpdate(model) }) { + picker(drug) { + drug = it + } + + Row { + TextField( + value = minPrice.toString(), + onValueChange = { minPrice = it.toIntOrNull() ?: minPrice }, + label = { Text("Min Price") }, + ) + + TextField( + value = maxPrice.toString(), + onValueChange = { maxPrice = it.toIntOrNull() ?: maxPrice }, + label = { Text("Max Price") }, + ) + } + + RangeSlider( + value = (minPrice.toFloat())..(maxPrice.toFloat()), + onValueChangeFinished = { }, + valueRange = 1f..DRUG_MAX_PRICE, + onValueChange = { + minPrice = it.start.toInt() + maxPrice = it.endInclusive.toInt() + } + ) + } + } +} \ No newline at end of file diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventory.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventory.kt index cf3bdce..3d31d1f 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventory.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventory.kt @@ -1,5 +1,6 @@ package dev.geekback.dopewars.ui +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -7,9 +8,17 @@ import dev.geekback.dopewars.shared.InventoryItem class InventoryUi: DopeWarsModelUi> { @Composable - override fun view(model: List) { - BorderedCard("Inventory Items") { - + override fun view(model: List, onRequestEdit: () -> Unit) { + ViewFrame("Inventory", + onRequestEdit = onRequestEdit) { + LazyColumn { + items(model.size) { index -> + val item = model[index] + InventoryItemUi().view(item) { + println("Item Edit Request") + } + } + } } } @@ -24,5 +33,4 @@ class InventoryUi: DopeWarsModelUi> { } } } - } \ No newline at end of file diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventoryItem.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventoryItem.kt index c066862..559b9f7 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventoryItem.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/inventoryItem.kt @@ -1,12 +1,28 @@ package dev.geekback.dopewars.ui +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import dev.geekback.dopewars.shared.InventoryItem class InventoryItemUi: DopeWarsModelUi { - @Composable - override fun view(model: InventoryItem) { + private val drugInfoUi = DrugInfoUi() + @Composable + override fun view(model: InventoryItem, onRequestEdit: () -> Unit) { + BorderedCard("") { + Text("${model.drug.name} / ${model.quantity}g") + + Button(onClick = onRequestEdit) { + Text("Edit") + } + } } @Composable @@ -14,6 +30,34 @@ class InventoryItemUi: DopeWarsModelUi { model: InventoryItem, onModelUpdate: (InventoryItem) -> Unit ) { - TODO("Not yet implemented") + var drug by remember { mutableStateOf(model.drug) } + var quantity by remember { mutableStateOf(model.quantity) } + + ViewFrame("Inventory Item", + onRequestEdit = {}) { + drugInfoUi.picker(drug) { + drug = it + } + + TextField( + value = quantity.toString(), + label = { Text("Quantity in g") }, + onValueChange = { + quantity = it.toInt() + }) + + Row { + Button(onClick = { onModelUpdate(InventoryItem(drug, quantity)) }) { + Text("Apply") + } + + Button(onClick = { + drug = model.drug + quantity = model.quantity + } ) { + Text("Reset") + } + } + } } } diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/location.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/location.kt index f8dd3fc..b48e923 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/location.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/location.kt @@ -1,53 +1,78 @@ package dev.geekback.dopewars.ui +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Button +import androidx.compose.foundation.onClick +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import dev.geekback.dopewars.shared.Location -class LocationUi: DopeWarsModelUi { +class LocationUi : DopeWarsModelUi { + @OptIn(ExperimentalFoundationApi::class) @Composable - override fun view(model: Location) { - BorderedCard("Location - ${model.name}") { + fun picker(default: Location, onSelect: (Location) -> Unit) { + var showOptions by remember { mutableStateOf(false) } + Column { + Row { + Text( + text = default.name, + modifier = Modifier.onClick { showOptions = true } + ) + + DropdownMenu( + expanded = showOptions, + onDismissRequest = { showOptions = false } + ) { + Location.all().forEach { localLocation -> + DropdownMenuItem(onClick = { + showOptions = false + onSelect(localLocation) + }, text = { Text(localLocation.name) }) + } + } + } } } - @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun view(model: Location, onRequestEdit: () -> Unit) { + ViewFrame(title = "Location", + onRequestEdit = onRequestEdit) { + Text(model.name) + } + } + + @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable override fun form( model: Location, onModelUpdate: (Location) -> Unit ) { - var name by remember { mutableStateOf(model.name) } - var location by remember { mutableStateOf(model)} + var localModel by remember { mutableStateOf(model) } - BorderedCard("Location") { - TextField(value = name, label = { - Text("Location Name") - }, onValueChange = { - name = it - }) + val title = if (localModel == model) { "Location" } else { "Location *" } - Row { - Button(onClick = { - location.name = name; - onModelUpdate(location) - }) { - Text("Apply") - } - - Button(onClick = { - name = location.name - }) { - Text("Cancel") - } + FormFrame(title = title, + onModelUpdate = { + onModelUpdate(localModel) + }, + onModelReset = { + localModel = model + }, + onCancelEdit = { + onModelUpdate(model) + }) { + picker(localModel) { + localModel = it } } } diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/market.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/market.kt index 7f06b53..4f942f6 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/market.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/market.kt @@ -2,24 +2,34 @@ package dev.geekback.dopewars.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import dev.geekback.dopewars.shared.Market +import dev.geekback.dopewars.shared.Player class MarketUi: DopeWarsModelUi { @Composable - override fun view(model: Market) { + override fun view(model: Market, onRequestEdit: () -> Unit) { val prices = model.prices.toList() BorderedCard("Market for ${model.location.name}") { Column { LazyColumn { items(prices.size) { index -> - val item = prices[index]; + + val item = prices[index] val name = item.first.name - val price = item.second.toFloat() + val price = item.second Text("Drug $name $${price}") } } + Button(onClick = onRequestEdit) { + Text("Edit") + } } } } diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/player.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/player.kt index bcf287a..e6346f4 100644 --- a/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/player.kt +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/dopewars/ui/player.kt @@ -1,28 +1,33 @@ package dev.geekback.dopewars.ui -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Button import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.material3.TextField +import androidx.compose.runtime.* import dev.geekback.dopewars.shared.Player import kotlin.time.Instant -class PlayerUi: DopeWarsModelUi { +class PlayerUi : DopeWarsModelUi { + private val inventoryUi = InventoryUi() + private val marketUi = MarketUi() + private val locationUi = LocationUi() + @Composable - override fun view(model: Player) { - BorderedCard(model.name) { - Column { - Text("ID: ${model.id}") - Text("Cash: $${model.cash}") - Text("Debt: $${model.debt}") - Text("Health: ${model.health}") - Text("Location: ${model.location.name}") - // TODO: this is an ugly memory hack - InventoryUi().view(model.inventory) - // marketView(model.lastMarket ?: Market(location = Location("Unknown"), prices = mapOf())) - val lastTime = Instant.fromEpochSeconds(model.lastActionTime) - Text("Last Action: $lastTime") + override fun view(model: Player, onRequestEdit: () -> Unit) { + ViewFrame( + title = model.name, + onRequestEdit = onRequestEdit + ) { + Text("ID: ${model.id}") + Text("Cash: $${model.cash}") + Text("Debt: $${model.debt}") + Text("Health: ${model.health}") + Text("Location: ${model.location.name}") + this.inventoryUi.view(model.inventory) { + println("Inventory Edit request") } + // marketView(model.lastMarket ?: Market(location = Location("Unknown"), prices = mapOf())) + val lastTime = Instant.fromEpochSeconds(model.lastActionTime) + Text("Last Action: $lastTime") } } @@ -31,14 +36,64 @@ class PlayerUi: DopeWarsModelUi { model: Player, onModelUpdate: (Player) -> Unit ) { - BorderedCard("Player ${model.name}") { - Button(onClick = { onModelUpdate(model) }) { - Text("Update") + var uuid by remember { mutableStateOf(model.id) } + var name by remember { mutableStateOf(model.name) } + var day by remember { mutableStateOf(model.day) } + var cash by remember { mutableStateOf(model.cash) } + var debt by remember { mutableStateOf(model.debt) } + var health by remember { mutableStateOf(model.health) } + var location by remember { mutableStateOf(model.location) } + var inventory by remember { mutableStateOf(model.inventory) } + var lastActionTime by remember { mutableStateOf(model.lastActionTime) } + var lastMarket by remember { mutableStateOf(model.lastMarket) } + + FormFrame( + title = "Player ${model.name} Form", + onModelUpdate = { + onModelUpdate( + Player( + id = uuid, + cash = cash, + debt = debt, + health = health, + location = location, + inventory = inventory, + name = name, + day = day, + lastMarket = lastMarket, + lastActionTime = lastActionTime, + ) + ) + }, + onModelReset = { + uuid = model.id + name = model.name + day = model.day + cash = model.cash + debt = model.debt + health = model.health + location = model.location + inventory = model.inventory + lastActionTime = model.lastActionTime + lastMarket = model.lastMarket + }, + onCancelEdit = { + onModelUpdate(model) + }) { + TextField(value = uuid, onValueChange = { uuid = it }) + TextField(value = name, onValueChange = { name = it }) + TextField(value = day.toString(), onValueChange = { day = it.toInt() }) + TextField(value = cash.toString(), onValueChange = { cash = it.toInt() }) + TextField(value = debt.toString(), onValueChange = { debt = it.toInt() }) + TextField(value = health.toString(), onValueChange = { health = it.toInt() }) + this.locationUi.picker(location) { location = it } + TextField(value = lastActionTime.toString(), onValueChange = { lastActionTime = it.toLong() }) + + if (lastMarket == null) { + Text("No Market") + } else { + this.marketUi.view(lastMarket!!) { } } } } } - -@Composable -fun playerView(player: Player) { -} diff --git a/shared_ui/src/commonMain/kotlin/dev/geekback/util/ComposeTimestampControl.kt b/shared_ui/src/commonMain/kotlin/dev/geekback/util/ComposeTimestampControl.kt new file mode 100644 index 0000000..cc1f814 --- /dev/null +++ b/shared_ui/src/commonMain/kotlin/dev/geekback/util/ComposeTimestampControl.kt @@ -0,0 +1,82 @@ +package dev.geekback.util + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.onClick +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlin.time.Instant + +data class ComposeTimestampConfiguration( + val minimumDate: Long, + val maximumDate: Long, +) + +@Composable +@Preview +fun ComposableTimestampControlPreview() { + Box { + ComposeTimestampControl( + initialValue = 123456789, + configuration = ComposeTimestampConfiguration(minimumDate = 0, maximumDate = 1234567890), + onValueUpdated = { println("Updated to $it") }, + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ComposeTimestampControl(initialValue: Long, configuration: ComposeTimestampConfiguration, onValueUpdated: (Long) -> Unit) { + var currentValue by remember { mutableStateOf(initialValue) } + + val currentDate = Instant.fromEpochSeconds(currentValue).toLocalDateTime(TimeZone.currentSystemDefault()).date + val currentTime = Instant.fromEpochSeconds(currentValue).toLocalDateTime(TimeZone.currentSystemDefault()).time + + var showingDatePicker by remember { mutableStateOf(false) } + var showingTimePicker by remember { mutableStateOf(false) } + + var datePickerState = rememberDatePickerState( + initialSelectedDateMillis = initialValue, + ) + + Column { + Row { + Text("$currentDate", modifier = Modifier.onClick(enabled = true, onClick = { + showingDatePicker = true + })) + Text("$currentTime", modifier = Modifier.onClick(enabled = true, onClick = { + showingTimePicker = true + })) + } + + Row { + Text("Date Visible $showingDatePicker") + Text("Time Visible $showingTimePicker") + } + + HorizontalDivider() + + if (showingDatePicker) { + DatePicker( + state = datePickerState + ) + } + + if (showingTimePicker) { + + } + + // Controls + Row { + Button(onClick = { currentValue = initialValue }) { Text("Reset") } + Button(onClick = { onValueUpdated(currentValue) }) { Text("Set") } + Button(onClick = { onValueUpdated(initialValue + currentValue) }) { Text("Cancel") } + } + } +} \ No newline at end of file diff --git a/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/ComposeTimestampControlDemo.kt b/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/ComposeTimestampControlDemo.kt new file mode 100644 index 0000000..5bb7e91 --- /dev/null +++ b/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/ComposeTimestampControlDemo.kt @@ -0,0 +1,33 @@ +package dev.geekback.dopewars + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import dev.geekback.util.ComposeTimestampConfiguration +import dev.geekback.util.ComposeTimestampControl + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "ComposeTimestampControlDemoApp" + ) { + ComposeTimestampControlDemoApp() + } +} + +@Composable +fun ComposeTimestampControlDemoApp() { + var currentValue by remember { mutableStateOf(1234567890L )} + + ComposeTimestampControl( + initialValue = currentValue, + configuration = ComposeTimestampConfiguration(minimumDate = 0, maximumDate = 12345678900), + ) { + println("Updated from $currentValue to $it") + currentValue = it + } +} \ No newline at end of file diff --git a/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/DemoMain3.kt b/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/DemoMain3.kt new file mode 100644 index 0000000..37395d9 --- /dev/null +++ b/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/DemoMain3.kt @@ -0,0 +1,224 @@ +package dev.geekback.dopewars + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDrawerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import dev.geekback.dopewars.shared.Drug +import dev.geekback.dopewars.shared.DrugInfo +import dev.geekback.dopewars.shared.Location +import dev.geekback.dopewars.shared.Market +import dev.geekback.dopewars.shared.Player +import dev.geekback.dopewars.shared.api.MarketRequest +import dev.geekback.dopewars.shared.api.MarketResult +import dev.geekback.dopewars.shared.api.PlayerNameUpdateRequest +import dev.geekback.dopewars.shared.api.PlayerNameUpdateResponse +import dev.geekback.dopewars.shared.api.TradeAction +import dev.geekback.dopewars.shared.api.TradeRequest +import dev.geekback.dopewars.shared.api.TravelRequest +import dev.geekback.dopewars.shared.api.TravelResult +import dev.geekback.dopewars.ui.DopeWarsUi + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "DopeWars Dev Client" + ) { + DemoApp() + } +} + +data class ApiData( + var marketRequest: MarketRequest, + var marketResult: MarketResult, + var playerNameUpdateRequest: PlayerNameUpdateRequest, + var playerNameUpdateResponse: PlayerNameUpdateResponse, + var tradeAction: TradeAction, + var tradeRequest: TradeRequest, + var travelRequest: TravelRequest, + var travelResult: TravelResult +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DemoApp() { + val drawerState = rememberDrawerState(DrawerValue.Closed) + val scope = rememberCoroutineScope() + var currentScreen by remember { mutableStateOf(Screens.HOME) } + var drug by remember { mutableStateOf(DrugInfo(drug = Drug.random(), minValue = 10000, maxValue = 100000)) } + var player by remember { mutableStateOf(Player.new()) } + var location by remember { mutableStateOf(Location.random()) } + var market by remember { + mutableStateOf( + Market( + location = Location.random(), + prices = mapOf() + ) + ) + } + + val ui = DopeWarsUi.new() + + val travelResultLocation = Location.random() + + var api by remember { + mutableStateOf( + ApiData( + marketRequest = MarketRequest( + location = Location.random() + ), + marketResult = MarketResult( + market = Market( + location = Location.random(), + prices = emptyMap() + ) + ), + playerNameUpdateRequest = PlayerNameUpdateRequest("newPlayerName"), + playerNameUpdateResponse = PlayerNameUpdateResponse( + updatedPlayer = Player.new() + ), + tradeAction = TradeAction.BUY, + tradeRequest = TradeRequest( + action = TradeAction.BUY, + drug = Drug.random(), + quantity = 10 + ), + travelRequest = TravelRequest( + destination = Location.random() + ), + travelResult = TravelResult( + location = travelResultLocation, + day = 1, + market = Market(location = travelResultLocation, mapOf()) + ) + ) + ) + } + + MaterialTheme { + ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { + ModalDrawerSheet { + val buttons = listOf( + Pair(Screens.HOME, "Home"), + Pair(Screens.DRUG, "Drugs"), + Pair(Screens.PLAYER, "Player"), + Pair(Screens.LOCATION, "Location"), + Pair(Screens.API, "API") + ) + + buttons.forEach { pair -> + Button(onClick = { + currentScreen = pair.first + }) { + Text(pair.second) + } + } + } + } + ) { + Column { // Main content + when (currentScreen) { + Screens.HOME -> { + Row { + Box(modifier = Modifier.width(400.dp)) { + ui.player.field(player) { + println("Player changed -> $it") + } + } + ui.location.field(location) { + println("Location changed -> $it") + } + } + } + + Screens.DRUG -> { + ui.drug.field(drug) { + println("Drug Updated -> $it") + } + } + + Screens.PLAYER -> { + ui.player.form(player) { + player = it + } + } + + Screens.LOCATION -> { + ui.location.form(location) { + println("Location updated from $location to $it") + location = it + } + } + + Screens.MARKET -> { + ui.market.form(market) { + market = it + } + } + + Screens.API -> { + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + SizeBox(250.dp, 250.dp) { + ui.api.marketRequest.form(api.marketRequest) { api.marketRequest = it } + } + + SizeBox(250.dp, 250.dp) { + ui.api.marketResponse.form(api.marketResult) { api.marketResult = it } + + } + } + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + SizeBox(250.dp, 250.dp) { + ui.api.playerNameUpdateRequest.form(api.playerNameUpdateRequest) { + api.playerNameUpdateRequest = it + } + } + + SizeBox(250.dp, 250.dp) { + ui.api.playerNameUpdateResponse.form(api.playerNameUpdateResponse) { + api.playerNameUpdateResponse = it + } + } + } + + ui.api.travelRequest.form(api.travelRequest) { api.travelRequest = it } + ui.api.travelResponse.form(api.travelResult) { api.travelResult = it } + ui.api.tradeRequest.form(api.tradeRequest) { api.tradeRequest = it } + } + } + } + } + } +} + + +@Composable +fun SizeBox(width: Dp, height: Dp, body: @Composable () -> Unit) { + Box(modifier = Modifier.width(width).height(height)) { + body() + } +} \ No newline at end of file diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Screens.kt b/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/Screens.kt similarity index 70% rename from desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Screens.kt rename to shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/Screens.kt index c1a2b6f..3bb1a34 100644 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/Screens.kt +++ b/shared_ui/src/jvmMain/kotlin/dev/geekback/dopewars/Screens.kt @@ -1,4 +1,4 @@ -package dev.geekback.dopewars.desktop +package dev.geekback.dopewars enum class Screens { HOME, diff --git a/web/build/processedResources/wasmJs/main/index.html b/web/build/processedResources/wasmJs/main/index.html deleted file mode 100644 index be5bedc..0000000 --- a/web/build/processedResources/wasmJs/main/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - KotlinProject - - - -

Web Client HTML

- - - - - - - - - \ No newline at end of file diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt index 44242a0..30bf579 100644 --- a/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt @@ -1,7 +1,10 @@ package dev.geekback.dopewars.web import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme @@ -15,6 +18,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dev.geekback.dopewars.shared.Player +import dev.geekback.dopewars.ui.PlayerUi import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -22,32 +26,43 @@ import kotlinx.coroutines.launch fun App() { val scope = rememberCoroutineScope() var players by mutableStateOf (listOf()) + var activePlayer: Player? by mutableStateOf(null) MaterialTheme { Scaffold( modifier = Modifier.padding(16.dp), content = { - Column { - // List of current Players - Button(onClick = { - println("Reload Players Now") - scope.launch { - players = AdminApi.loadPlayers() + Row { + Column(modifier = Modifier.width(250.dp)) { + // List of current Players + Button(onClick = { + scope.launch { + players = AdminApi.loadPlayers() + } + }) { + Text("Reload Players") } - }) { - Text("Reload Players") - } - ApiControlBox { - println("Setting API BaseUrl to $it") - AdminApi.setBaseUrl(it) - } + ApiControlBox { + AdminApi.setBaseUrl(it) + } - PlayersList(players) { - println("Click on $it") + PlayersList(players) { + activePlayer = it + } + } + Column(modifier = Modifier.fillMaxSize()) { + if (players.isEmpty()) { + Text("No Players") + } else { + if (activePlayer != null) { + PlayerUi().view(activePlayer!!) {} + } else { + Text("Select a Player") + } + } } } } ) } } - diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt index 67a84f9..7918d94 100644 --- a/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt @@ -19,7 +19,7 @@ fun PlayersList(players: List, onPLayerSelected: (Player) -> Unit) { } } else { items(players.size) { i -> - val item = players[i]; + val item = players[i] Text("Player ${item.name}", modifier = Modifier.clickable { onPLayerSelected(item) }) } }