more structure to forms and views
shared ui got an interface to demo the components
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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,18 +54,31 @@ fun App() {
|
||||
ModalDrawerSheet {
|
||||
if (state.isLoggedIn()) {
|
||||
// Display the current player state
|
||||
PlayerUi().view(state.player)
|
||||
PlayerUi().view(state.player) {}
|
||||
} else {
|
||||
if (state.isLoggedIn()) {
|
||||
NavigationDrawerItem(
|
||||
label = {
|
||||
Row {
|
||||
Text("Logout")
|
||||
}
|
||||
},
|
||||
selected = false,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
state.logout()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
NavigationDrawerItem(
|
||||
label = {
|
||||
Row {
|
||||
/*
|
||||
Icon(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
imageVector = ,
|
||||
imageVector = FeatherIcons.LogIn,
|
||||
contentDescription = "Login Icon"
|
||||
)
|
||||
*/
|
||||
Text(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = "Login"
|
||||
@@ -79,6 +95,7 @@ fun App() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
if (state.isLoggedIn()) {
|
||||
mainWindow(state)
|
||||
@@ -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")
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.jvmargs=-Xmx12g
|
||||
org.gradle.tooling.parallel=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -46,6 +46,9 @@ dependencies {
|
||||
// Websockets
|
||||
implementation(libs.ktor.server.websockets)
|
||||
|
||||
// CORS
|
||||
implementation(libs.ktor.server.cors)
|
||||
|
||||
// General Use
|
||||
implementation(libs.kotlinx.datetime)
|
||||
|
||||
|
||||
@@ -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" : {
|
||||
|
||||
@@ -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()}")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ fun Route.marketRoutes(
|
||||
get {
|
||||
val players = PlayerRepository.all().toList()
|
||||
|
||||
val response: MutableList<Market> = mutableListOf();
|
||||
val response: MutableList<Market> = mutableListOf()
|
||||
players.forEach { player ->
|
||||
response.add(player.lastMarket!!)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,7 +8,7 @@ import dev.geekback.dopewars.shared.Market
|
||||
class MarketService {
|
||||
|
||||
fun generateMarket(location: Location): Market {
|
||||
val prices: MutableMap<Drug, Float> = mutableMapOf()
|
||||
val prices: MutableMap<Drug, Int> = mutableMapOf()
|
||||
|
||||
Drugs.all.forEach { drugInfo ->
|
||||
prices[drugInfo.drug] = drugInfo.newPrice()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -3,6 +3,24 @@ package dev.geekback.dopewars.shared
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Location(
|
||||
var name: String
|
||||
)
|
||||
enum class Location {
|
||||
BROOKLYN,
|
||||
BRONX,
|
||||
STATTEN_ISLAND,
|
||||
QUEENS;
|
||||
|
||||
companion object {
|
||||
fun all(): List<Location> {
|
||||
return listOf(
|
||||
BROOKLYN,
|
||||
BRONX,
|
||||
STATTEN_ISLAND,
|
||||
QUEENS
|
||||
)
|
||||
}
|
||||
|
||||
fun random(): Location {
|
||||
return all().random()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Market(
|
||||
// Location of the market
|
||||
val location: Location,
|
||||
val prices: Map<Drug, Float>
|
||||
// Map of Drug to price in cents
|
||||
val prices: Map<Drug, Int>
|
||||
) {
|
||||
companion object {
|
||||
}
|
||||
companion object
|
||||
}
|
||||
@@ -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<InventoryItem> = 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",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,5 +5,4 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MarketResult(
|
||||
val market: Market) {
|
||||
}
|
||||
val market: Market)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
@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(
|
||||
|
||||
@@ -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,20 +35,22 @@ 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 -> {
|
||||
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,
|
||||
onDismissRequest = { expanded = false }
|
||||
@@ -57,7 +64,7 @@ class TravelUi {
|
||||
println("location index to $index")
|
||||
}
|
||||
)
|
||||
index += 1;
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +86,5 @@ class TravelUi {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ data class ApiUi(
|
||||
val playerNameUpdateRequest: PlayerNameUpdateRequestUi,
|
||||
val playerNameUpdateResponse: PlayerNameUpdateResponseUi,
|
||||
val tradeRequest: TradeRequestUi,
|
||||
// TODO: tradeResponse
|
||||
val travelRequest: TravelRequestUi,
|
||||
val travelResponse: TravelResponseUi,
|
||||
) {
|
||||
|
||||
@@ -16,9 +16,11 @@ import dev.geekback.dopewars.ui.LocationUi
|
||||
|
||||
class MarketRequestUi: DopeWarsModelUi<MarketRequest> {
|
||||
@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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,3 +47,8 @@ class MarketRequestUi: DopeWarsModelUi<MarketRequest> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T>form_skeleton(model: T, onModelUpdate: (T) -> Unit, onFieldUpdate: (T) -> Unit) {
|
||||
|
||||
}
|
||||
@@ -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<MarketResult> {
|
||||
@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<MarketResult> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -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<PlayerNameUpdateRequest> {
|
||||
class PlayerNameUpdateRequestUi : DopeWarsModelUi<PlayerNameUpdateRequest> {
|
||||
@Composable
|
||||
override fun view(model: PlayerNameUpdateRequest) {
|
||||
override fun view(model: PlayerNameUpdateRequest, onRequestEdit: () -> Unit) {
|
||||
BorderedCard("Player Name Update Request for ${model.newName}") {
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@ import dev.geekback.dopewars.ui.BorderedCard
|
||||
|
||||
class PlayerNameUpdateResponseUi: DopeWarsModelUi<PlayerNameUpdateResponse> {
|
||||
@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) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import dev.geekback.dopewars.ui.DopeWarsModelUi
|
||||
|
||||
class TradeRequestUi: DopeWarsModelUi<TradeRequest> {
|
||||
@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<TradeRequest> {
|
||||
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 {
|
||||
|
||||
@@ -8,7 +8,7 @@ import dev.geekback.dopewars.ui.BorderedCard
|
||||
|
||||
class TravelRequestUi: DopeWarsModelUi<TravelRequest> {
|
||||
@Composable
|
||||
override fun view(model: TravelRequest) {
|
||||
override fun view(model: TravelRequest, onRequestEdit: () -> Unit) {
|
||||
BorderedCard("Travel Request to ${model.destination.name}") {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,27 +17,24 @@ import dev.geekback.dopewars.ui.MarketUi
|
||||
class TravelResponseUi: DopeWarsModelUi<TravelResult> {
|
||||
|
||||
@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 {
|
||||
@@ -54,3 +51,21 @@ class TravelResponseUi: DopeWarsModelUi<TravelResult> {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@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") }
|
||||
}
|
||||
}
|
||||
@@ -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<DrugInfo> {
|
||||
class DrugInfoUi : DopeWarsModelUi<DrugInfo> {
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
override fun view(model: DrugInfo) {
|
||||
BorderedCard(model.drug.name) {
|
||||
fun picker(default: Drug, onSelect: (Drug) -> Unit) {
|
||||
var showOptions by remember { mutableStateOf(false) }
|
||||
Column {
|
||||
Text("Price: ${model.minValue} - ${model.maxValue}")
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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) }
|
||||
var name by remember { mutableStateOf(model.drug.name) }
|
||||
var minPrice by remember { mutableFloatStateOf(model.minValue) }
|
||||
var maxPrice by remember { mutableFloatStateOf(model.maxValue) }
|
||||
var drug by remember { mutableStateOf(model.drug) }
|
||||
var minPrice by remember { mutableIntStateOf(model.minValue) }
|
||||
var maxPrice by remember { mutableIntStateOf(model.maxValue) }
|
||||
|
||||
BorderedCard("Drug Form") {
|
||||
Column {
|
||||
// Name
|
||||
TextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text("Name") },
|
||||
)
|
||||
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.toFloatOrNull() ?: minPrice },
|
||||
onValueChange = { minPrice = it.toIntOrNull() ?: minPrice },
|
||||
label = { Text("Min Price") },
|
||||
)
|
||||
|
||||
TextField(
|
||||
value = maxPrice.toString(),
|
||||
onValueChange = { maxPrice = it.toFloatOrNull() ?: maxPrice },
|
||||
onValueChange = { maxPrice = it.toIntOrNull() ?: maxPrice },
|
||||
label = { Text("Max Price") },
|
||||
)
|
||||
}
|
||||
|
||||
RangeSlider(
|
||||
value = (minPrice)..(maxPrice),
|
||||
value = (minPrice.toFloat())..(maxPrice.toFloat()),
|
||||
onValueChangeFinished = { },
|
||||
valueRange = 1f..DRUG_MAX_PRICE,
|
||||
onValueChange = { it ->
|
||||
minPrice = it.start
|
||||
maxPrice = it.endInclusive
|
||||
onValueChange = {
|
||||
minPrice = it.start.toInt()
|
||||
maxPrice = it.endInclusive.toInt()
|
||||
}
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<List<InventoryItem>> {
|
||||
@Composable
|
||||
override fun view(model: List<InventoryItem>) {
|
||||
BorderedCard("Inventory Items") {
|
||||
|
||||
override fun view(model: List<InventoryItem>, 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<List<InventoryItem>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<InventoryItem> {
|
||||
@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<InventoryItem> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Location> {
|
||||
class LocationUi : DopeWarsModelUi<Location> {
|
||||
@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)
|
||||
FormFrame(title = title,
|
||||
onModelUpdate = {
|
||||
onModelUpdate(localModel)
|
||||
},
|
||||
onModelReset = {
|
||||
localModel = model
|
||||
},
|
||||
onCancelEdit = {
|
||||
onModelUpdate(model)
|
||||
}) {
|
||||
Text("Apply")
|
||||
}
|
||||
|
||||
Button(onClick = {
|
||||
name = location.name
|
||||
}) {
|
||||
Text("Cancel")
|
||||
}
|
||||
picker(localModel) {
|
||||
localModel = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Market> {
|
||||
@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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,99 @@
|
||||
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<Player> {
|
||||
class PlayerUi : DopeWarsModelUi<Player> {
|
||||
private val inventoryUi = InventoryUi()
|
||||
private val marketUi = MarketUi()
|
||||
private val locationUi = LocationUi()
|
||||
|
||||
@Composable
|
||||
override fun view(model: Player) {
|
||||
BorderedCard(model.name) {
|
||||
Column {
|
||||
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}")
|
||||
// TODO: this is an ugly memory hack
|
||||
InventoryUi().view(model.inventory)
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun form(
|
||||
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) }
|
||||
|
||||
@Composable
|
||||
fun playerView(player: Player) {
|
||||
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!!) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package dev.geekback.dopewars.desktop
|
||||
package dev.geekback.dopewars
|
||||
|
||||
enum class Screens {
|
||||
HOME,
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KotlinProject</title>
|
||||
<link type="text/css" rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body style="text-align: center; align-content: center">
|
||||
<h1>Web Client HTML</h1>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 50 50" role="presentation">
|
||||
<circle cx="25" cy="25" r="20" stroke="#ccc" stroke-width="4" fill="none"/>
|
||||
<circle cx="25" cy="25" r="20" stroke="#333" stroke-width="4" fill="none" stroke-linecap="round"
|
||||
stroke-dasharray="90 125">
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s"
|
||||
repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</svg>
|
||||
<script type="application/javascript" src="web.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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,14 +26,15 @@ import kotlinx.coroutines.launch
|
||||
fun App() {
|
||||
val scope = rememberCoroutineScope()
|
||||
var players by mutableStateOf (listOf<Player>())
|
||||
var activePlayer: Player? by mutableStateOf(null)
|
||||
MaterialTheme {
|
||||
Scaffold(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
content = {
|
||||
Column {
|
||||
Row {
|
||||
Column(modifier = Modifier.width(250.dp)) {
|
||||
// List of current Players
|
||||
Button(onClick = {
|
||||
println("Reload Players Now")
|
||||
scope.launch {
|
||||
players = AdminApi.loadPlayers()
|
||||
}
|
||||
@@ -38,16 +43,26 @@ fun App() {
|
||||
}
|
||||
|
||||
ApiControlBox {
|
||||
println("Setting API BaseUrl to $it")
|
||||
AdminApi.setBaseUrl(it)
|
||||
}
|
||||
|
||||
PlayersList(players) {
|
||||
println("Click on $it")
|
||||
activePlayer = it
|
||||
}
|
||||
}
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
if (players.isEmpty()) {
|
||||
Text("No Players")
|
||||
} else {
|
||||
if (activePlayer != null) {
|
||||
PlayerUi().view(activePlayer!!) {}
|
||||
} else {
|
||||
Text("Select a Player")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ fun PlayersList(players: List<Player>, 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) })
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user