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 {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
// alias(libs.plugins.kotlin.jvm)
|
|
||||||
alias(libs.plugins.compose)
|
alias(libs.plugins.compose)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
@@ -9,8 +8,6 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":shared"))
|
implementation(project(":shared"))
|
||||||
implementation(project(":shared_ui"))
|
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.kotlinx.serialization)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
implementation(libs.ktor.client.core)
|
implementation(libs.ktor.client.core)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import dev.geekback.dopewars.shared.api.TravelRequest
|
|||||||
|
|
||||||
class ClientState(player: Player) {
|
class ClientState(player: Player) {
|
||||||
var player by mutableStateOf(player)
|
var player by mutableStateOf(player)
|
||||||
|
var hasLoggedIn by mutableStateOf(false)
|
||||||
|
|
||||||
var apiUrl: String = "http://localhost:8080"
|
var apiUrl: String = "http://localhost:8080"
|
||||||
private var api: ApiClient = ApiClient()
|
private var api: ApiClient = ApiClient()
|
||||||
@@ -21,9 +22,15 @@ class ClientState(player: Player) {
|
|||||||
suspend fun login() : Player {
|
suspend fun login() : Player {
|
||||||
val p = this.api.login("http://localhost:8080")
|
val p = this.api.login("http://localhost:8080")
|
||||||
println("Logged in as ${p.name} / ${p.id}")
|
println("Logged in as ${p.name} / ${p.id}")
|
||||||
|
this.hasLoggedIn = true
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun logout() {
|
||||||
|
this.hasLoggedIn = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateMarket() {
|
suspend fun updateMarket() {
|
||||||
println("Updating market")
|
println("Updating market")
|
||||||
this.player.lastMarket = this.api.market(this.player)
|
this.player.lastMarket = this.api.market(this.player)
|
||||||
@@ -37,8 +44,7 @@ class ClientState(player: Player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isLoggedIn(): Boolean {
|
fun isLoggedIn(): Boolean {
|
||||||
println("Testing if user is logged in - ${this.player.id.isNotEmpty()}")
|
return this.hasLoggedIn
|
||||||
return this.player.id.isNotEmpty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshApi(url: String) {
|
fun refreshApi(url: String) {
|
||||||
@@ -48,7 +54,7 @@ class ClientState(player: Player) {
|
|||||||
|
|
||||||
|
|
||||||
suspend fun travel(player: Player, destination: TravelRequest) {
|
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.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.NavigationDrawerItem
|
import androidx.compose.material3.NavigationDrawerItem
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -21,6 +22,8 @@ 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.Window
|
||||||
import androidx.compose.ui.window.application
|
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.AppTheme
|
||||||
import dev.geekback.dopewars.ui.PlayerUi
|
import dev.geekback.dopewars.ui.PlayerUi
|
||||||
import dev.geekback.dopewars.ui.TravelUi
|
import dev.geekback.dopewars.ui.TravelUi
|
||||||
@@ -51,31 +54,45 @@ fun App() {
|
|||||||
ModalDrawerSheet {
|
ModalDrawerSheet {
|
||||||
if (state.isLoggedIn()) {
|
if (state.isLoggedIn()) {
|
||||||
// Display the current player state
|
// Display the current player state
|
||||||
PlayerUi().view(state.player)
|
PlayerUi().view(state.player) {}
|
||||||
} else {
|
} else {
|
||||||
NavigationDrawerItem(
|
if (state.isLoggedIn()) {
|
||||||
label = {
|
NavigationDrawerItem(
|
||||||
Row {
|
label = {
|
||||||
/*
|
Row {
|
||||||
Icon(
|
Text("Logout")
|
||||||
modifier = Modifier.padding(16.dp),
|
}
|
||||||
imageVector = ,
|
},
|
||||||
contentDescription = "Login Icon"
|
selected = false,
|
||||||
)
|
onClick = {
|
||||||
*/
|
scope.launch {
|
||||||
Text(
|
state.logout()
|
||||||
modifier = Modifier.padding(16.dp),
|
}
|
||||||
text = "Login"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
selected = false,
|
} else {
|
||||||
onClick = {
|
NavigationDrawerItem(
|
||||||
scope.launch {
|
label = {
|
||||||
state.player = state.login()
|
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
|
@Composable
|
||||||
fun mainWindow(state: ClientState) {
|
fun mainWindow(state: ClientState) {
|
||||||
val scope = rememberCoroutineScope();
|
val scope = rememberCoroutineScope()
|
||||||
var showingTravelWizard by remember { mutableStateOf(false) }
|
var showingTravelWizard by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
PlayerUi().view(state.player)
|
PlayerUi().view(state.player) {}
|
||||||
Row {
|
Row {
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
println("Transactions")
|
println("Transactions")
|
||||||
|
|||||||
+4
-1
@@ -1,2 +1,5 @@
|
|||||||
org.gradle.configuration-cache=true
|
org.gradle.configuration-cache=true
|
||||||
org.gradle.jvmargs=-Xmx12g
|
org.gradle.jvmargs=-Xmx12g
|
||||||
|
org.gradle.tooling.parallel=true
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.daemon=true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
ktor = "3.5.1"
|
ktor = "3.5.2"
|
||||||
kotlin = "2.4.0"
|
kotlin = "2.4.0"
|
||||||
compose = "1.11.1"
|
compose = "1.11.1"
|
||||||
compose-material3 = "1.11.0-alpha07"
|
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-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-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-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
|
||||||
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
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" }
|
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref="ktor" }
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ dependencies {
|
|||||||
// Websockets
|
// Websockets
|
||||||
implementation(libs.ktor.server.websockets)
|
implementation(libs.ktor.server.websockets)
|
||||||
|
|
||||||
|
// CORS
|
||||||
|
implementation(libs.ktor.server.cors)
|
||||||
|
|
||||||
// General Use
|
// General Use
|
||||||
implementation(libs.kotlinx.datetime)
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
|
|||||||
@@ -698,20 +698,13 @@
|
|||||||
},
|
},
|
||||||
"quantity" : { }
|
"quantity" : { }
|
||||||
}
|
}
|
||||||
};
|
|
||||||
defs["Location"] = {
|
|
||||||
"title" : "Location",
|
|
||||||
"required" : [ "name" ],
|
|
||||||
"properties" : {
|
|
||||||
"name" : { }
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
defs["Market"] = {
|
defs["Market"] = {
|
||||||
"title" : "Market",
|
"title" : "Market",
|
||||||
"required" : [ "location", "prices" ],
|
"required" : [ "location", "prices" ],
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"location" : {
|
"location" : {
|
||||||
"$ref" : "#/components/schemas/Location"
|
"enum" : [ "BROOKLYN", "BRONX", "STATTEN_ISLAND", "QUEENS" ]
|
||||||
},
|
},
|
||||||
"prices" : {
|
"prices" : {
|
||||||
"additionalProperties" : { }
|
"additionalProperties" : { }
|
||||||
@@ -737,7 +730,7 @@
|
|||||||
"debt" : { },
|
"debt" : { },
|
||||||
"health" : { },
|
"health" : { },
|
||||||
"location" : {
|
"location" : {
|
||||||
"$ref" : "#/components/schemas/Location"
|
"enum" : [ "BROOKLYN", "BRONX", "STATTEN_ISLAND", "QUEENS" ]
|
||||||
},
|
},
|
||||||
"day" : { },
|
"day" : { },
|
||||||
"inventory" : {
|
"inventory" : {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import dev.geekback.dopewars.server.routes.modelCrudRoutes
|
|||||||
import dev.geekback.dopewars.server.routes.playerRoutes
|
import dev.geekback.dopewars.server.routes.playerRoutes
|
||||||
import dev.geekback.dopewars.server.services.GameService
|
import dev.geekback.dopewars.server.services.GameService
|
||||||
import dev.geekback.dopewars.server.services.MarketService
|
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.serialization.kotlinx.json.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
@@ -13,6 +15,7 @@ import io.ktor.server.http.content.*
|
|||||||
import io.ktor.server.netty.*
|
import io.ktor.server.netty.*
|
||||||
import io.ktor.server.plugins.calllogging.*
|
import io.ktor.server.plugins.calllogging.*
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
|
import io.ktor.server.plugins.cors.routing.CORS
|
||||||
import io.ktor.server.plugins.openapi.*
|
import io.ktor.server.plugins.openapi.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.server.websocket.*
|
import io.ktor.server.websocket.*
|
||||||
@@ -27,7 +30,7 @@ fun Application.module() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println("[ # ] Loading Web Sockets Plugin")
|
println("[ # ] Loading Web Sockets Plugin")
|
||||||
install(WebSockets);
|
install(WebSockets)
|
||||||
|
|
||||||
println("[ # ] Loading Content Negotiation Plugin")
|
println("[ # ] Loading Content Negotiation Plugin")
|
||||||
install(ContentNegotiation) {
|
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")
|
println("[ # ] Plugins Loaded")
|
||||||
|
|
||||||
val marketService = MarketService()
|
val marketService = MarketService()
|
||||||
@@ -52,7 +62,7 @@ fun Application.module() {
|
|||||||
modelCrudRoutes(marketService)
|
modelCrudRoutes(marketService)
|
||||||
|
|
||||||
webSocket("/ws") {
|
webSocket("/ws") {
|
||||||
send("Welcome to DopeWars");
|
send("Welcome to DopeWars")
|
||||||
for (frame in incoming) {
|
for (frame in incoming) {
|
||||||
if (frame is Frame.Text) send("Echo: ${frame.readText()}")
|
if (frame is Frame.Text) send("Echo: ${frame.readText()}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ fun Route.marketRoutes(
|
|||||||
get {
|
get {
|
||||||
val players = PlayerRepository.all().toList()
|
val players = PlayerRepository.all().toList()
|
||||||
|
|
||||||
val response: MutableList<Market> = mutableListOf();
|
val response: MutableList<Market> = mutableListOf()
|
||||||
players.forEach { player ->
|
players.forEach { player ->
|
||||||
response.add(player.lastMarket!!)
|
response.add(player.lastMarket!!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ fun Route.modelCrudRoutes(marketService: MarketService) {
|
|||||||
|
|
||||||
// List
|
// List
|
||||||
get {
|
get {
|
||||||
val modelName = call.parameters["modelName"]
|
when (val modelName = call.parameters["modelName"]) {
|
||||||
when (modelName) {
|
|
||||||
"market" -> {
|
"market" -> {
|
||||||
call.respond(marketService.generateMarket(Location("BRONX")))
|
call.respond(marketService.generateMarket(Location.BRONX))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
call.respond("Model name is $modelName")
|
call.respond("Model name is $modelName")
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ fun Route.playerRoutes(
|
|||||||
|
|
||||||
// Create
|
// Create
|
||||||
post {
|
post {
|
||||||
val location = Location("Brooklyn")
|
val location = Location.BROOKLYN
|
||||||
val mkt = marketService.generateMarket(location)
|
val mkt = marketService.generateMarket(location)
|
||||||
|
|
||||||
val player = Player(
|
val player = Player(
|
||||||
@@ -59,7 +59,7 @@ fun Route.playerRoutes(
|
|||||||
|
|
||||||
// Read
|
// Read
|
||||||
get {
|
get {
|
||||||
val players = PlayerRepository.all();
|
val players = PlayerRepository.all()
|
||||||
|
|
||||||
call.respond(
|
call.respond(
|
||||||
HttpStatusCode.OK,
|
HttpStatusCode.OK,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class GameService(
|
|||||||
player: Player,
|
player: Player,
|
||||||
drug: Drug,
|
drug: Drug,
|
||||||
quantity: Int,
|
quantity: Int,
|
||||||
price: Float
|
price: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val cost = price * quantity
|
val cost = price * quantity
|
||||||
@@ -90,7 +90,7 @@ class GameService(
|
|||||||
player: Player,
|
player: Player,
|
||||||
drug: Drug,
|
drug: Drug,
|
||||||
quantity: Int,
|
quantity: Int,
|
||||||
price: Float
|
price: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val inventoryItem = player.inventory
|
val inventoryItem = player.inventory
|
||||||
@@ -104,7 +104,7 @@ class GameService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
inventoryItem.quantity -= quantity
|
inventoryItem.quantity -= quantity
|
||||||
player.cash += price * quantity.toFloat()
|
player.cash += price * quantity
|
||||||
|
|
||||||
if (inventoryItem.quantity == 0) {
|
if (inventoryItem.quantity == 0) {
|
||||||
player.inventory.remove(inventoryItem)
|
player.inventory.remove(inventoryItem)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import dev.geekback.dopewars.shared.Market
|
|||||||
class MarketService {
|
class MarketService {
|
||||||
|
|
||||||
fun generateMarket(location: Location): Market {
|
fun generateMarket(location: Location): Market {
|
||||||
val prices: MutableMap<Drug, Float> = mutableMapOf()
|
val prices: MutableMap<Drug, Int> = mutableMapOf()
|
||||||
|
|
||||||
Drugs.all.forEach { drugInfo ->
|
Drugs.all.forEach { drugInfo ->
|
||||||
prices[drugInfo.drug] = drugInfo.newPrice()
|
prices[drugInfo.drug] = drugInfo.newPrice()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.json.Json
|
|||||||
*
|
*
|
||||||
* Client for accessing the Dopewars API server
|
* Client for accessing the Dopewars API server
|
||||||
*/
|
*/
|
||||||
class ApiClient() {
|
class ApiClient {
|
||||||
private var address: String = ""
|
private var address: String = ""
|
||||||
private val client = HttpClient(CIO) {
|
private val client = HttpClient(CIO) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ enum class Drug {
|
|||||||
companion object {
|
companion object {
|
||||||
fun all() = listOf(WEED, COCAINE, HASH, ACID, HEROIN, MUSHROOMS, METH)
|
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
|
@Serializable
|
||||||
data class DrugInfo(
|
data class DrugInfo(
|
||||||
val drug: Drug,
|
var drug: Drug,
|
||||||
val minValue: Float,
|
val minValue: Int,
|
||||||
val maxValue: Float,
|
val maxValue: Int,
|
||||||
) {
|
) {
|
||||||
fun newPrice(): Float {
|
fun newPrice(): Int {
|
||||||
return Random.nextFloat() * (this.maxValue - this.minValue)
|
return Random.nextInt() * (this.maxValue - this.minValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,13 @@ package dev.geekback.dopewars.shared
|
|||||||
|
|
||||||
object Drugs {
|
object Drugs {
|
||||||
val all = listOf(
|
val all = listOf(
|
||||||
DrugInfo(Drug.WEED, 300.0f, 1000.0f),
|
DrugInfo(Drug.WEED, 30000, 100000),
|
||||||
DrugInfo(Drug.ACID, 1000.0f, 4500.0f),
|
DrugInfo(Drug.ACID, 100000, 450000),
|
||||||
DrugInfo(Drug.COCAINE, 15000.0f, 30000.0f),
|
DrugInfo(Drug.COCAINE, 1500000, 3000000),
|
||||||
DrugInfo(Drug.HASH, 5000.0f, 14000.0f),
|
DrugInfo(Drug.HASH, 500000, 1400000),
|
||||||
DrugInfo(Drug.MUSHROOMS, 500.0f, 2500.0f),
|
DrugInfo(Drug.MUSHROOMS, 50000, 250000),
|
||||||
DrugInfo(Drug.METH, 2000.0f, 9000.0f),
|
DrugInfo(Drug.METH, 200000, 900000),
|
||||||
DrugInfo(Drug.HEROIN, minValue = 4000.0f, 9500.0f)
|
DrugInfo(Drug.HEROIN, minValue = 400000, 950000)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun random() = all.random()
|
fun random() = all.random()
|
||||||
|
|||||||
@@ -3,6 +3,24 @@ package dev.geekback.dopewars.shared
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Location(
|
enum class Location {
|
||||||
var name: String
|
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
|
@Serializable
|
||||||
data class Market(
|
data class Market(
|
||||||
|
// Location of the market
|
||||||
val location: Location,
|
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 kotlinx.serialization.Serializable
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Player(
|
data class Player(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
var cash: Float = 2000f,
|
// Cash in cents
|
||||||
var debt: Float = 5500f,
|
var cash: Int = 200000,
|
||||||
|
// Debt in cents
|
||||||
|
var debt: Int = 550000,
|
||||||
var health: Int = 100,
|
var health: Int = 100,
|
||||||
var location: Location = Location("Brooklyn"),
|
var location: Location = Location.BROOKLYN,
|
||||||
var day: Int = 1,
|
var day: Int = 1,
|
||||||
val inventory: MutableList<InventoryItem> = mutableListOf(),
|
val inventory: MutableList<InventoryItem> = mutableListOf(),
|
||||||
var lastMarket: Market? = null,
|
var lastMarket: Market? = null,
|
||||||
@@ -19,7 +22,7 @@ data class Player(
|
|||||||
companion object {
|
companion object {
|
||||||
fun new(): Player {
|
fun new(): Player {
|
||||||
return Player(
|
return Player(
|
||||||
id = "",
|
id = Uuid.random().toString(),
|
||||||
name = "Default Player",
|
name = "Default Player",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package dev.geekback.dopewars.shared
|
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
|
@Serializable
|
||||||
data class MarketResult(
|
data class MarketResult(
|
||||||
val market: Market) {
|
val market: Market)
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,12 +22,25 @@ kotlin {
|
|||||||
implementation(libs.compose.runtime)
|
implementation(libs.compose.runtime)
|
||||||
implementation(libs.compose.foundation)
|
implementation(libs.compose.foundation)
|
||||||
implementation(libs.compose.ui)
|
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 {
|
wasmJsMain.dependencies {
|
||||||
implementation(libs.wrappers.browser)
|
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.BorderStroke
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
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
|
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.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
|
import dev.geekback.dopewars.ui.api.ApiUi
|
||||||
|
|
||||||
interface DopeWarsModelUi<T> {
|
interface DopeWarsModelUi<T> {
|
||||||
@Composable
|
@Composable
|
||||||
fun view(model: T);
|
fun view(model: T, onRequestEdit: () -> Unit)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun form(model: T, onModelUpdate: (T) -> Unit)
|
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(
|
data class DopeWarsUi(
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package dev.geekback.dopewars.ui
|
package dev.geekback.dopewars.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -16,7 +18,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
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.Player
|
||||||
import dev.geekback.dopewars.shared.api.TravelRequest
|
import dev.geekback.dopewars.shared.api.TravelRequest
|
||||||
|
|
||||||
@@ -30,19 +35,21 @@ class TravelUi {
|
|||||||
fun wizard(current: Player, onTicketPunched: (TravelRequest) -> Unit) {
|
fun wizard(current: Player, onTicketPunched: (TravelRequest) -> Unit) {
|
||||||
var stage by remember { mutableStateOf(0) }
|
var stage by remember { mutableStateOf(0) }
|
||||||
var pickedLocation by remember { mutableStateOf(0) }
|
var pickedLocation by remember { mutableStateOf(0) }
|
||||||
val locations = Locations.all
|
val locations = Location.all()
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
// Window(onCloseRequest = { onTicketPunched(TravelRequest(destination = current.location)) }, title = "Select New Location") {
|
|
||||||
/*
|
|
||||||
Card(modifier = Modifier.padding(16.dp).fillMaxSize()) {
|
Card(modifier = Modifier.padding(16.dp).fillMaxSize()) {
|
||||||
Column {
|
Column {
|
||||||
Text("Wizard")
|
Text("Wizard")
|
||||||
var index = 0;
|
var index = 0
|
||||||
when (stage) {
|
when (stage) {
|
||||||
0 -> {
|
0 -> {
|
||||||
Text(text = locations[pickedLocation].name)
|
Row {
|
||||||
IconButton(onClick = { expanded = !expanded }) {
|
Text(text = locations[pickedLocation].name)
|
||||||
// Icon(MaterialIcons.Filled.Menu, contentDescription = "More options")
|
IconButton(onClick = { expanded = !expanded }) {
|
||||||
|
Icon(imageVector = FeatherIcons.Menu, contentDescription = "Login")
|
||||||
|
// Icon(MaterialIcons.Filled.Menu, contentDescription = "More options")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
@@ -57,7 +64,7 @@ class TravelUi {
|
|||||||
println("location index to $index")
|
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 playerNameUpdateRequest: PlayerNameUpdateRequestUi,
|
||||||
val playerNameUpdateResponse: PlayerNameUpdateResponseUi,
|
val playerNameUpdateResponse: PlayerNameUpdateResponseUi,
|
||||||
val tradeRequest: TradeRequestUi,
|
val tradeRequest: TradeRequestUi,
|
||||||
// TODO: tradeResponse
|
|
||||||
val travelRequest: TravelRequestUi,
|
val travelRequest: TravelRequestUi,
|
||||||
val travelResponse: TravelResponseUi,
|
val travelResponse: TravelResponseUi,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ import dev.geekback.dopewars.ui.LocationUi
|
|||||||
|
|
||||||
class MarketRequestUi: DopeWarsModelUi<MarketRequest> {
|
class MarketRequestUi: DopeWarsModelUi<MarketRequest> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: MarketRequest) {
|
override fun view(model: MarketRequest, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Market Request for ${model.location.name}") {
|
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<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.BorderedCard
|
||||||
import dev.geekback.dopewars.ui.DopeWarsModelUi
|
import dev.geekback.dopewars.ui.DopeWarsModelUi
|
||||||
import dev.geekback.dopewars.ui.MarketUi
|
import dev.geekback.dopewars.ui.MarketUi
|
||||||
|
import dev.geekback.dopewars.ui.ViewFrame
|
||||||
|
|
||||||
class MarketResponseUi: DopeWarsModelUi<MarketResult> {
|
class MarketResponseUi: DopeWarsModelUi<MarketResult> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: MarketResult) {
|
override fun view(model: MarketResult, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Market Result for ${model.market.location.name}") {
|
ViewFrame(title = "Market Result for ${model.market.location.name}",
|
||||||
MarketUi().view(model.market)
|
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.shared.api.PlayerNameUpdateRequest
|
||||||
import dev.geekback.dopewars.ui.BorderedCard
|
import dev.geekback.dopewars.ui.BorderedCard
|
||||||
|
|
||||||
class PlayerNameUpdateRequestUi(): DopeWarsModelUi<PlayerNameUpdateRequest> {
|
class PlayerNameUpdateRequestUi : DopeWarsModelUi<PlayerNameUpdateRequest> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: PlayerNameUpdateRequest) {
|
override fun view(model: PlayerNameUpdateRequest, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Player Name Update Request for ${model.newName}") {
|
BorderedCard("Player Name Update Request for ${model.newName}") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -8,9 +8,9 @@ import dev.geekback.dopewars.ui.BorderedCard
|
|||||||
|
|
||||||
class PlayerNameUpdateResponseUi: DopeWarsModelUi<PlayerNameUpdateResponse> {
|
class PlayerNameUpdateResponseUi: DopeWarsModelUi<PlayerNameUpdateResponse> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: PlayerNameUpdateResponse) {
|
override fun view(model: PlayerNameUpdateResponse, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Player Name Update Response") {
|
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> {
|
class TradeRequestUi: DopeWarsModelUi<TradeRequest> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: TradeRequest) {
|
override fun view(model: TradeRequest, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Trade Request for ${model.drug.name} / ${model.action} / ${model.quantity}") {
|
BorderedCard("Trade Request for ${model.drug.name} / ${model.action} / ${model.quantity}") {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,6 @@ class TradeRequestUi: DopeWarsModelUi<TradeRequest> {
|
|||||||
onModelUpdate: (TradeRequest) -> Unit
|
onModelUpdate: (TradeRequest) -> Unit
|
||||||
) {
|
) {
|
||||||
var localModel by remember { mutableStateOf(model) }
|
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") {
|
BorderedCard("Trade Requesst Form") {
|
||||||
Row {
|
Row {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import dev.geekback.dopewars.ui.BorderedCard
|
|||||||
|
|
||||||
class TravelRequestUi: DopeWarsModelUi<TravelRequest> {
|
class TravelRequestUi: DopeWarsModelUi<TravelRequest> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: TravelRequest) {
|
override fun view(model: TravelRequest, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Travel Request to ${model.destination.name}") {
|
BorderedCard("Travel Request to ${model.destination.name}") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,27 +17,24 @@ import dev.geekback.dopewars.ui.MarketUi
|
|||||||
class TravelResponseUi: DopeWarsModelUi<TravelResult> {
|
class TravelResponseUi: DopeWarsModelUi<TravelResult> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: TravelResult) {
|
override fun view(model: TravelResult, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Travel Result ${model.location.name}") {
|
BorderedCard("Travel Result ${model.location.name}") {
|
||||||
LocationUi().view(model.location)
|
LocationUi().view(model.location) {}
|
||||||
Text("Day: ${model.day}")
|
Text("Day: ${model.day}")
|
||||||
MarketUi().view(model.market)
|
MarketUi().view(model.market) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun form(model: TravelResult, onModelUpdate: (TravelResult) -> Unit) {
|
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") {
|
BorderedCard("Travel Result Form") {
|
||||||
LocationUi().form(model.location) {
|
LocationUi().form(model.location) {
|
||||||
println("Update location to $it")
|
println("Update location to $it")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Row {
|
// Row {
|
||||||
@@ -53,4 +50,22 @@ 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
|
package dev.geekback.dopewars.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.onClick
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.RangeSlider
|
import androidx.compose.material3.RangeSlider
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import dev.geekback.dopewars.shared.DRUG_MAX_PRICE
|
import dev.geekback.dopewars.shared.DRUG_MAX_PRICE
|
||||||
import dev.geekback.dopewars.shared.Drug
|
import dev.geekback.dopewars.shared.Drug
|
||||||
import dev.geekback.dopewars.shared.DrugInfo
|
import dev.geekback.dopewars.shared.DrugInfo
|
||||||
|
|
||||||
class DrugInfoUi: DopeWarsModelUi<DrugInfo> {
|
class DrugInfoUi : DopeWarsModelUi<DrugInfo> {
|
||||||
@Composable
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
override fun view(model: DrugInfo) {
|
@Composable
|
||||||
BorderedCard(model.drug.name) {
|
fun picker(default: Drug, onSelect: (Drug) -> Unit) {
|
||||||
Column {
|
var showOptions by remember { mutableStateOf(false) }
|
||||||
Text("Price: ${model.minValue} - ${model.maxValue}")
|
Column {
|
||||||
}
|
Row {
|
||||||
}
|
Text(
|
||||||
}
|
modifier = Modifier.onClick {
|
||||||
|
showOptions = true
|
||||||
@Composable
|
},
|
||||||
override fun form(
|
text = default.name
|
||||||
model: DrugInfo,
|
)
|
||||||
onModelUpdate: (DrugInfo) -> Unit
|
DropdownMenu(
|
||||||
) {
|
expanded = showOptions,
|
||||||
var drug by remember { mutableStateOf(model) }
|
onDismissRequest = { showOptions = false },
|
||||||
var name by remember { mutableStateOf(model.drug.name) }
|
) {
|
||||||
var minPrice by remember { mutableFloatStateOf(model.minValue) }
|
Drug.all().forEach { localDrug ->
|
||||||
var maxPrice by remember { mutableFloatStateOf(model.maxValue) }
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
BorderedCard("Drug Form") {
|
showOptions = false
|
||||||
Column {
|
onSelect(localDrug)
|
||||||
// Name
|
},
|
||||||
TextField(
|
text = { Text(localDrug.name) }
|
||||||
value = name,
|
|
||||||
onValueChange = { name = it },
|
|
||||||
label = { Text("Name") },
|
|
||||||
)
|
|
||||||
|
|
||||||
Row {
|
|
||||||
TextField(
|
|
||||||
value = minPrice.toString(),
|
|
||||||
onValueChange = { minPrice = it.toFloatOrNull() ?: minPrice },
|
|
||||||
label = { Text("Min Price") },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.geekback.dopewars.ui
|
package dev.geekback.dopewars.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -7,9 +8,17 @@ import dev.geekback.dopewars.shared.InventoryItem
|
|||||||
|
|
||||||
class InventoryUi: DopeWarsModelUi<List<InventoryItem>> {
|
class InventoryUi: DopeWarsModelUi<List<InventoryItem>> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: List<InventoryItem>) {
|
override fun view(model: List<InventoryItem>, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard("Inventory Items") {
|
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
|
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.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
|
import dev.geekback.dopewars.shared.InventoryItem
|
||||||
|
|
||||||
class InventoryItemUi: DopeWarsModelUi<InventoryItem> {
|
class InventoryItemUi: DopeWarsModelUi<InventoryItem> {
|
||||||
@Composable
|
private val drugInfoUi = DrugInfoUi()
|
||||||
override fun view(model: InventoryItem) {
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun view(model: InventoryItem, onRequestEdit: () -> Unit) {
|
||||||
|
BorderedCard("") {
|
||||||
|
Text("${model.drug.name} / ${model.quantity}g")
|
||||||
|
|
||||||
|
Button(onClick = onRequestEdit) {
|
||||||
|
Text("Edit")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -14,6 +30,34 @@ class InventoryItemUi: DopeWarsModelUi<InventoryItem> {
|
|||||||
model: InventoryItem,
|
model: InventoryItem,
|
||||||
onModelUpdate: (InventoryItem) -> Unit
|
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
|
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.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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import dev.geekback.dopewars.shared.Location
|
import dev.geekback.dopewars.shared.Location
|
||||||
|
|
||||||
class LocationUi: DopeWarsModelUi<Location> {
|
class LocationUi : DopeWarsModelUi<Location> {
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: Location) {
|
fun picker(default: Location, onSelect: (Location) -> Unit) {
|
||||||
BorderedCard("Location - ${model.name}") {
|
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
|
@Composable
|
||||||
override fun form(
|
override fun form(
|
||||||
model: Location,
|
model: Location,
|
||||||
onModelUpdate: (Location) -> Unit
|
onModelUpdate: (Location) -> Unit
|
||||||
) {
|
) {
|
||||||
var name by remember { mutableStateOf(model.name) }
|
var localModel by remember { mutableStateOf(model) }
|
||||||
var location by remember { mutableStateOf(model)}
|
|
||||||
|
|
||||||
BorderedCard("Location") {
|
val title = if (localModel == model) { "Location" } else { "Location *" }
|
||||||
TextField(value = name, label = {
|
|
||||||
Text("Location Name")
|
|
||||||
}, onValueChange = {
|
|
||||||
name = it
|
|
||||||
})
|
|
||||||
|
|
||||||
Row {
|
FormFrame(title = title,
|
||||||
Button(onClick = {
|
onModelUpdate = {
|
||||||
location.name = name;
|
onModelUpdate(localModel)
|
||||||
onModelUpdate(location)
|
},
|
||||||
}) {
|
onModelReset = {
|
||||||
Text("Apply")
|
localModel = model
|
||||||
}
|
},
|
||||||
|
onCancelEdit = {
|
||||||
Button(onClick = {
|
onModelUpdate(model)
|
||||||
name = location.name
|
}) {
|
||||||
}) {
|
picker(localModel) {
|
||||||
Text("Cancel")
|
localModel = it
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,34 @@ package dev.geekback.dopewars.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Market
|
||||||
|
import dev.geekback.dopewars.shared.Player
|
||||||
|
|
||||||
class MarketUi: DopeWarsModelUi<Market> {
|
class MarketUi: DopeWarsModelUi<Market> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun view(model: Market) {
|
override fun view(model: Market, onRequestEdit: () -> Unit) {
|
||||||
val prices = model.prices.toList()
|
val prices = model.prices.toList()
|
||||||
BorderedCard("Market for ${model.location.name}") {
|
BorderedCard("Market for ${model.location.name}") {
|
||||||
Column {
|
Column {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(prices.size) { index ->
|
items(prices.size) { index ->
|
||||||
val item = prices[index];
|
|
||||||
|
val item = prices[index]
|
||||||
val name = item.first.name
|
val name = item.first.name
|
||||||
val price = item.second.toFloat()
|
val price = item.second
|
||||||
Text("Drug $name $${price}")
|
Text("Drug $name $${price}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Button(onClick = onRequestEdit) {
|
||||||
|
Text("Edit")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
package dev.geekback.dopewars.ui
|
package dev.geekback.dopewars.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Text
|
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 dev.geekback.dopewars.shared.Player
|
||||||
import kotlin.time.Instant
|
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
|
@Composable
|
||||||
override fun view(model: Player) {
|
override fun view(model: Player, onRequestEdit: () -> Unit) {
|
||||||
BorderedCard(model.name) {
|
ViewFrame(
|
||||||
Column {
|
title = model.name,
|
||||||
Text("ID: ${model.id}")
|
onRequestEdit = onRequestEdit
|
||||||
Text("Cash: $${model.cash}")
|
) {
|
||||||
Text("Debt: $${model.debt}")
|
Text("ID: ${model.id}")
|
||||||
Text("Health: ${model.health}")
|
Text("Cash: $${model.cash}")
|
||||||
Text("Location: ${model.location.name}")
|
Text("Debt: $${model.debt}")
|
||||||
// TODO: this is an ugly memory hack
|
Text("Health: ${model.health}")
|
||||||
InventoryUi().view(model.inventory)
|
Text("Location: ${model.location.name}")
|
||||||
// marketView(model.lastMarket ?: Market(location = Location("Unknown"), prices = mapOf()))
|
this.inventoryUi.view(model.inventory) {
|
||||||
val lastTime = Instant.fromEpochSeconds(model.lastActionTime)
|
println("Inventory Edit request")
|
||||||
Text("Last Action: $lastTime")
|
|
||||||
}
|
}
|
||||||
|
// 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<Player> {
|
|||||||
model: Player,
|
model: Player,
|
||||||
onModelUpdate: (Player) -> Unit
|
onModelUpdate: (Player) -> Unit
|
||||||
) {
|
) {
|
||||||
BorderedCard("Player ${model.name}") {
|
var uuid by remember { mutableStateOf(model.id) }
|
||||||
Button(onClick = { onModelUpdate(model) }) {
|
var name by remember { mutableStateOf(model.name) }
|
||||||
Text("Update")
|
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) {
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 {
|
enum class Screens {
|
||||||
HOME,
|
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
|
package dev.geekback.dopewars.web
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
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.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -15,6 +18,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.geekback.dopewars.shared.Player
|
import dev.geekback.dopewars.shared.Player
|
||||||
|
import dev.geekback.dopewars.ui.PlayerUi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -22,32 +26,43 @@ import kotlinx.coroutines.launch
|
|||||||
fun App() {
|
fun App() {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var players by mutableStateOf (listOf<Player>())
|
var players by mutableStateOf (listOf<Player>())
|
||||||
|
var activePlayer: Player? by mutableStateOf(null)
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
content = {
|
content = {
|
||||||
Column {
|
Row {
|
||||||
// List of current Players
|
Column(modifier = Modifier.width(250.dp)) {
|
||||||
Button(onClick = {
|
// List of current Players
|
||||||
println("Reload Players Now")
|
Button(onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
players = AdminApi.loadPlayers()
|
players = AdminApi.loadPlayers()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Reload Players")
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
Text("Reload Players")
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiControlBox {
|
ApiControlBox {
|
||||||
println("Setting API BaseUrl to $it")
|
AdminApi.setBaseUrl(it)
|
||||||
AdminApi.setBaseUrl(it)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PlayersList(players) {
|
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 {
|
} else {
|
||||||
items(players.size) { i ->
|
items(players.size) { i ->
|
||||||
val item = players[i];
|
val item = players[i]
|
||||||
Text("Player ${item.name}", modifier = Modifier.clickable { onPLayerSelected(item) })
|
Text("Player ${item.name}", modifier = Modifier.clickable { onPLayerSelected(item) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user