Add basic network feature.
Basic implementation of a WebSocket protocol with JSON. Note : obfuscation & release build are deactivated again. Netty (needed for Ktor server) is a nightmare to build in release with or without proguard. Spend more time until now on project configuration that in actual prototyping.
This commit is contained in:
parent
ba0cc30a1a
commit
0e5fee6771
22 changed files with 958 additions and 161 deletions
|
|
@ -5,6 +5,8 @@ plugins {
|
||||||
alias(libs.plugins.kotlinSerialization)
|
alias(libs.plugins.kotlinSerialization)
|
||||||
alias(libs.plugins.composeMultiplatform)
|
alias(libs.plugins.composeMultiplatform)
|
||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
|
// kotlin("jvm") version "1.9.20"
|
||||||
|
// alias(libs.plugins.kotlinKtor)
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
|
@ -24,6 +26,12 @@ kotlin {
|
||||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
implementation(libs.ktor.client.core)
|
||||||
|
implementation(libs.ktor.client.cio)
|
||||||
|
implementation(libs.ktor.client.websockets)
|
||||||
|
implementation(libs.ktor.server.core)
|
||||||
|
implementation(libs.ktor.server.netty)
|
||||||
|
implementation(libs.ktor.server.websockets)
|
||||||
}
|
}
|
||||||
|
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
|
|
@ -53,12 +61,12 @@ compose.desktop {
|
||||||
|
|
||||||
includeAllModules = true
|
includeAllModules = true
|
||||||
|
|
||||||
// Use system theming fot the app toolbars.
|
// Use system theming for the app toolbars.
|
||||||
jvmArgs("-Dapple.awt.application.appearance=system")
|
jvmArgs("-Dapple.awt.application.appearance=system")
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes.release.proguard {
|
buildTypes.release.proguard {
|
||||||
obfuscate.set(true) // Obfuscation crash at runtime when try to use datastore.
|
obfuscate.set(false) // Obfuscation doesn't work because of netty.
|
||||||
configurationFiles.from(project.file("compose-desktop.pro"))
|
configurationFiles.from(project.file("compose-desktop.pro"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
## Data Store old dependancies not removed properly.
|
|
||||||
-dontwarn okio.AsyncTimeout$Watchdog
|
|
||||||
|
|
||||||
-keep class androidx.compose.runtime.** { *; }
|
-keep class androidx.compose.runtime.** { *; }
|
||||||
-keep class androidx.collection.** { *; }
|
-keep class androidx.collection.** { *; }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="main_page__create_action">Créer une feuille de personnage</string>
|
<string name="main_page__create_action">Créer une feuille de personnage</string>
|
||||||
|
<string name="main_page__network_action">Configuration réseau</string>
|
||||||
|
|
||||||
<string name="roll_page__critical_success">Réussite critique</string>
|
<string name="roll_page__critical_success">Réussite critique</string>
|
||||||
<string name="roll_page__special_success">Réussite spéciale</string>
|
<string name="roll_page__special_success">Réussite spéciale</string>
|
||||||
|
|
@ -65,4 +66,19 @@
|
||||||
<string name="character_sheet__skills__title">Compétences</string>
|
<string name="character_sheet__skills__title">Compétences</string>
|
||||||
<string name="character_sheet__occupations_title">Occupations</string>
|
<string name="character_sheet__occupations_title">Occupations</string>
|
||||||
<string name="character_sheet__magics__title">Compétences magiques</string>
|
<string name="character_sheet__magics__title">Compétences magiques</string>
|
||||||
|
|
||||||
|
<string name="network__title">Configuration réseau</string>
|
||||||
|
<string name="network__player_name__label">Nom du joueur</string>
|
||||||
|
<string name="network__host__label">host</string>
|
||||||
|
<string name="network__port__label">port</string>
|
||||||
|
<string name="network__socket__connect_action">Se connecter à la table</string>
|
||||||
|
<string name="network__socket__host_action">Héberger la table</string>
|
||||||
|
<string name="network__socket__disconnect_action">Se déconnecter</string>
|
||||||
|
<string name="network__socket__status_state">État de connexion : %1$s</string>
|
||||||
|
<string name="network__socket__status_connected">Connecté</string>
|
||||||
|
<string name="network__socket__status_disconnected">Disconnecté</string>
|
||||||
|
<string name="network__socket__type_state">Type de connexion : %1$s</string>
|
||||||
|
<string name="network__socket__type_server">Serveur</string>
|
||||||
|
<string name="network__socket__type_client">Client</string>
|
||||||
|
<string name="network__socket__type_none">Aucun</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -3,19 +3,58 @@ package com.pixelized.desktop.lwa
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.ApplicationScope
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import com.pixelized.desktop.lwa.navigation.MainNavHost
|
import com.pixelized.desktop.lwa.navigation.MainNavHost
|
||||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
import com.pixelized.desktop.lwa.theme.LwaTheme
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
val LocalWindowController = compositionLocalOf<WindowController> {
|
||||||
|
error("Local Window Controller is not yet ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class WindowController(
|
||||||
|
private val onCloseRequest: () -> Unit
|
||||||
|
) {
|
||||||
|
fun close() = onCloseRequest()
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun App() {
|
fun ApplicationScope.App() {
|
||||||
LwaTheme {
|
val controller = remember {
|
||||||
Surface(
|
WindowController(
|
||||||
modifier = Modifier.fillMaxSize()
|
onCloseRequest = ::exitApplication
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalWindowController provides controller,
|
||||||
|
) {
|
||||||
|
Window(
|
||||||
|
onCloseRequest = {
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
state = rememberWindowState(
|
||||||
|
width = 320.dp + 64.dp,
|
||||||
|
height = 900.dp,
|
||||||
|
),
|
||||||
|
title = "LwaCharacterSheet",
|
||||||
) {
|
) {
|
||||||
MainNavHost()
|
LwaTheme {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
MainNavHost()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,11 +7,10 @@ import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.MainDestination
|
import com.pixelized.desktop.lwa.navigation.destination.MainDestination
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetEditPage
|
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetPage
|
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.composableMainPage
|
import com.pixelized.desktop.lwa.navigation.destination.composableMainPage
|
||||||
|
import com.pixelized.desktop.lwa.navigation.destination.composableNetworkPage
|
||||||
|
|
||||||
val LocalScreen = compositionLocalOf<NavHostController> {
|
val LocalScreenController = compositionLocalOf<NavHostController> {
|
||||||
error("MainNavHost controller is not yet ready")
|
error("MainNavHost controller is not yet ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,16 +20,14 @@ fun MainNavHost(
|
||||||
startDestination: String = MainDestination.navigationRoute(),
|
startDestination: String = MainDestination.navigationRoute(),
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalScreen provides controller,
|
LocalScreenController provides controller,
|
||||||
) {
|
) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = controller,
|
navController = controller,
|
||||||
startDestination = startDestination,
|
startDestination = startDestination,
|
||||||
) {
|
) {
|
||||||
composableMainPage()
|
composableMainPage()
|
||||||
|
composableNetworkPage()
|
||||||
composableCharacterSheetPage()
|
|
||||||
composableCharacterSheetEditPage()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.pixelized.desktop.lwa.navigation.destination
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import com.pixelized.desktop.lwa.screen.network.NetworkPage
|
||||||
|
|
||||||
|
object NetworkDestination {
|
||||||
|
private const val ROUTE = "network"
|
||||||
|
|
||||||
|
fun baseRoute() = ROUTE
|
||||||
|
fun navigationRoute() = ROUTE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableNetworkPage() {
|
||||||
|
composable(
|
||||||
|
route = NetworkDestination.baseRoute(),
|
||||||
|
) {
|
||||||
|
NetworkPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToNetwork() {
|
||||||
|
val route = NetworkDestination.navigationRoute()
|
||||||
|
navigate(route = route)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
package com.pixelized.desktop.lwa.repository.network
|
||||||
|
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.helper.client
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.helper.server
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.protocol.Message
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.server.engine.EmbeddedServer
|
||||||
|
import io.ktor.server.netty.NettyApplicationEngine
|
||||||
|
import io.ktor.websocket.Frame
|
||||||
|
import io.ktor.websocket.readText
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.consumeEach
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
|
|
||||||
|
typealias Server = EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration>
|
||||||
|
typealias Client = HttpClient
|
||||||
|
|
||||||
|
object NetworkRepository {
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
private var networkJob: Job? = null
|
||||||
|
private var server: Server? = null
|
||||||
|
private var client: Client? = null
|
||||||
|
|
||||||
|
private val messageResponseFlow = MutableSharedFlow<String>()
|
||||||
|
private val sharedFlow = messageResponseFlow.asSharedFlow()
|
||||||
|
|
||||||
|
private val _player = MutableStateFlow("")
|
||||||
|
val player: StateFlow<String> get() = _player
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow(Status.DISCONNECTED)
|
||||||
|
val status: StateFlow<Status> get() = _status
|
||||||
|
|
||||||
|
private val _type = MutableStateFlow(Type.NONE)
|
||||||
|
val type: StateFlow<Type> get() = _type
|
||||||
|
|
||||||
|
fun onPlayerNameChange(player: String) {
|
||||||
|
_player.value = player
|
||||||
|
}
|
||||||
|
|
||||||
|
fun host(
|
||||||
|
port: Int,
|
||||||
|
) {
|
||||||
|
_type.value = Type.SERVER
|
||||||
|
_status.value = Status.CONNECTED
|
||||||
|
|
||||||
|
server = server(port = port) {
|
||||||
|
println("Server launched")
|
||||||
|
|
||||||
|
val job = launch {
|
||||||
|
sharedFlow.collect { message ->
|
||||||
|
println("Broadcast: $message")
|
||||||
|
send(Frame.Text(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
incoming.consumeEach { frame ->
|
||||||
|
if (frame is Frame.Text) {
|
||||||
|
val receivedText = frame.readText()
|
||||||
|
messageResponseFlow.emit(receivedText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure { exception ->
|
||||||
|
// TODO
|
||||||
|
println("WebSocket exception: ${exception.localizedMessage}")
|
||||||
|
}.also {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
networkJob?.cancel()
|
||||||
|
networkJob = scope.launch {
|
||||||
|
try {
|
||||||
|
server?.start(wait = true)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// TODO
|
||||||
|
println("WebSocket exception: ${exception.localizedMessage}")
|
||||||
|
} finally {
|
||||||
|
println("Server close")
|
||||||
|
_type.value = Type.NONE
|
||||||
|
_status.value = Status.DISCONNECTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect(
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
) {
|
||||||
|
client = client()
|
||||||
|
|
||||||
|
networkJob?.cancel()
|
||||||
|
networkJob = scope.launch {
|
||||||
|
try {
|
||||||
|
client?.connectWebSocket(host = host, port = port) {
|
||||||
|
_type.value = Type.CLIENT
|
||||||
|
_status.value = Status.CONNECTED
|
||||||
|
println("Client launched")
|
||||||
|
|
||||||
|
val job = launch {
|
||||||
|
sharedFlow.collect { message ->
|
||||||
|
println("Send: $message")
|
||||||
|
send(Frame.Text(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
incoming.consumeEach { frame ->
|
||||||
|
if (frame is Frame.Text) {
|
||||||
|
val receivedText = frame.readText()
|
||||||
|
println("client received: $receivedText")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.also {
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// TODO
|
||||||
|
println("WebSocket exception: ${exception.localizedMessage}")
|
||||||
|
} finally {
|
||||||
|
println("Client close")
|
||||||
|
_type.value = Type.NONE
|
||||||
|
_status.value = Status.DISCONNECTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
networkJob?.cancel()
|
||||||
|
scope.launch {
|
||||||
|
server?.stop()
|
||||||
|
client?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun share(
|
||||||
|
type: String,
|
||||||
|
value: String,
|
||||||
|
) {
|
||||||
|
if (status.value == Status.CONNECTED) {
|
||||||
|
scope.launch {
|
||||||
|
val message = Message(from = player.value, type = type, value = value)
|
||||||
|
val json = Json.encodeToJsonElement(message)
|
||||||
|
messageResponseFlow.emit(json.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
CONNECTED,
|
||||||
|
DISCONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
CLIENT,
|
||||||
|
SERVER,
|
||||||
|
NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.pixelized.desktop.lwa.repository.network.helper
|
||||||
|
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.cio.CIO
|
||||||
|
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
|
||||||
|
import io.ktor.client.plugins.websocket.WebSockets
|
||||||
|
import io.ktor.client.plugins.websocket.webSocket
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
|
||||||
|
// https://ktor.io/docs/client-websockets.html#handle-session
|
||||||
|
fun client(): HttpClient {
|
||||||
|
val client = HttpClient(CIO) {
|
||||||
|
install(WebSockets) {
|
||||||
|
pingIntervalMillis = 20_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun HttpClient.connectWebSocket(
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
block: suspend DefaultClientWebSocketSession.() -> Unit
|
||||||
|
) {
|
||||||
|
webSocket(
|
||||||
|
method = HttpMethod.Get,
|
||||||
|
host = host,
|
||||||
|
port = port,
|
||||||
|
path = "/ws",
|
||||||
|
block = block,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.pixelized.desktop.lwa.repository.network.helper
|
||||||
|
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.engine.EmbeddedServer
|
||||||
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.netty.Netty
|
||||||
|
import io.ktor.server.netty.NettyApplicationEngine
|
||||||
|
import io.ktor.server.routing.routing
|
||||||
|
import io.ktor.server.websocket.DefaultWebSocketServerSession
|
||||||
|
import io.ktor.server.websocket.WebSockets
|
||||||
|
import io.ktor.server.websocket.pingPeriod
|
||||||
|
import io.ktor.server.websocket.timeout
|
||||||
|
import io.ktor.server.websocket.webSocket
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
// https://ktor.io/docs/server-websockets.html#handle-multiple-session
|
||||||
|
fun server(
|
||||||
|
port: Int = 8080,
|
||||||
|
handler: suspend DefaultWebSocketServerSession.() -> Unit
|
||||||
|
): EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
|
||||||
|
return embeddedServer(
|
||||||
|
factory = Netty,
|
||||||
|
port = port,
|
||||||
|
module = {
|
||||||
|
install(WebSockets) {
|
||||||
|
pingPeriod = 15.seconds
|
||||||
|
timeout = 15.seconds
|
||||||
|
maxFrameSize = Long.MAX_VALUE
|
||||||
|
masking = false
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
webSocket(
|
||||||
|
path = "/ws",
|
||||||
|
handler = handler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.pixelized.desktop.lwa.repository.network.protocol
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Message(
|
||||||
|
val from: String,
|
||||||
|
val type: String,
|
||||||
|
val value: String,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.characterSheet
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.pixelized.desktop.lwa.navigation.LocalScreenController
|
||||||
|
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetEditPage
|
||||||
|
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetPage
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CharacterSheetMainNavHost(
|
||||||
|
controller: NavHostController = rememberNavController(),
|
||||||
|
startDestination: String,
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalScreenController provides controller,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
NavHost(
|
||||||
|
navController = controller,
|
||||||
|
startDestination = startDestination,
|
||||||
|
) {
|
||||||
|
composableCharacterSheetPage()
|
||||||
|
composableCharacterSheetEditPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,13 +4,12 @@ import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
|
@ -36,14 +35,14 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.createSavedStateHandle
|
import androidx.lifecycle.createSavedStateHandle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.pixelized.desktop.lwa.LocalWindowController
|
||||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||||
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlay
|
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlay
|
||||||
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlayViewModel
|
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlayViewModel
|
||||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
import com.pixelized.desktop.lwa.navigation.LocalScreenController
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
|
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
|
||||||
import com.pixelized.desktop.lwa.screen.roll.RollPage
|
import com.pixelized.desktop.lwa.screen.roll.RollPage
|
||||||
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
|
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
|
||||||
|
|
@ -95,7 +94,8 @@ fun CharacterSheetPage(
|
||||||
overlayViewModel: BlurOverlayViewModel = viewModel { BlurOverlayViewModel() },
|
overlayViewModel: BlurOverlayViewModel = viewModel { BlurOverlayViewModel() },
|
||||||
rollViewModel: RollViewModel = viewModel { RollViewModel() },
|
rollViewModel: RollViewModel = viewModel { RollViewModel() },
|
||||||
) {
|
) {
|
||||||
val screen = LocalScreen.current
|
val window = LocalWindowController.current
|
||||||
|
val screen = LocalScreenController.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -123,7 +123,9 @@ fun CharacterSheetPage(
|
||||||
onDelete = {
|
onDelete = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.deleteCharacter(id = sheet.id)
|
viewModel.deleteCharacter(id = sheet.id)
|
||||||
screen.popBackStack()
|
if (screen.popBackStack().not()) {
|
||||||
|
window.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCharacteristic = { characteristic ->
|
onCharacteristic = { characteristic ->
|
||||||
|
|
@ -148,12 +150,10 @@ fun CharacterSheetPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CharacterSheetPageContent(
|
fun CharacterSheetPageContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
scrollState: ScrollState = rememberScrollState(),
|
scrollState: ScrollState = rememberScrollState(),
|
||||||
width: Dp = 320.dp,
|
|
||||||
characterSheet: CharacterSheetPageUio,
|
characterSheet: CharacterSheetPageUio,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onEdit: () -> Unit,
|
onEdit: () -> Unit,
|
||||||
|
|
@ -163,6 +163,7 @@ fun CharacterSheetPageContent(
|
||||||
onRoll: (roll: CharacterSheetPageUio.Roll) -> Unit,
|
onRoll: (roll: CharacterSheetPageUio.Roll) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
|
|
@ -203,115 +204,131 @@ fun CharacterSheetPageContent(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = { paddingValues ->
|
content = { paddingValues ->
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(state = scrollState).padding(all = 16.dp)
|
.verticalScroll(state = scrollState)
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.then(other = modifier),
|
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
) {
|
||||||
FlowRow(
|
Column(
|
||||||
maxItemsInEachRow = 3,
|
modifier = Modifier
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
.fillMaxHeight()
|
||||||
space = 16.dp,
|
.width(100.dp),
|
||||||
alignment = Alignment.CenterHorizontally,
|
|
||||||
),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||||
) {
|
) {
|
||||||
characterSheet.characteristics.forEach {
|
characterSheet.characteristics.forEach {
|
||||||
Stat(
|
Stat(
|
||||||
modifier = Modifier.width(width = width / 3 - 32.dp)
|
modifier = Modifier
|
||||||
.height(height = 112.dp),
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 120.dp),
|
||||||
characteristic = it,
|
characteristic = it,
|
||||||
onClick = { onCharacteristic(it) },
|
onClick = { onCharacteristic(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DecoratedBox(
|
Column(
|
||||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(2f / 3f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Column {
|
DecoratedBox(
|
||||||
Text(
|
modifier = Modifier
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
.fillMaxWidth()
|
||||||
style = MaterialTheme.typography.caption,
|
.padding(vertical = 8.dp),
|
||||||
textAlign = TextAlign.Center,
|
) {
|
||||||
text = stringResource(Res.string.character_sheet__sub_characteristics__title),
|
Column {
|
||||||
)
|
Text(
|
||||||
characterSheet.subCharacteristics.forEach {
|
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||||
Characteristics(
|
style = MaterialTheme.typography.caption,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
textAlign = TextAlign.Center,
|
||||||
characteristic = it,
|
text = stringResource(Res.string.character_sheet__sub_characteristics__title),
|
||||||
)
|
)
|
||||||
|
characterSheet.subCharacteristics.forEach {
|
||||||
|
Characteristics(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
characteristic = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
DecoratedBox(
|
||||||
DecoratedBox(
|
modifier = Modifier
|
||||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
.fillMaxWidth()
|
||||||
) {
|
.padding(vertical = 8.dp),
|
||||||
Column {
|
) {
|
||||||
Text(
|
Column {
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
Text(
|
||||||
style = MaterialTheme.typography.caption,
|
modifier = Modifier
|
||||||
textAlign = TextAlign.Center,
|
.fillMaxWidth()
|
||||||
text = stringResource(Res.string.character_sheet__skills__title),
|
.padding(bottom = 8.dp),
|
||||||
)
|
style = MaterialTheme.typography.caption,
|
||||||
characterSheet.skills.forEach {
|
textAlign = TextAlign.Center,
|
||||||
Skill(
|
text = stringResource(Res.string.character_sheet__skills__title),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
label = it.label,
|
|
||||||
value = it.value,
|
|
||||||
onClick = { onSkill(it) },
|
|
||||||
)
|
)
|
||||||
|
characterSheet.skills.forEach {
|
||||||
|
Skill(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
label = it.label,
|
||||||
|
value = it.value,
|
||||||
|
onClick = { onSkill(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
DecoratedBox(
|
||||||
DecoratedBox(
|
modifier = Modifier
|
||||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
.fillMaxWidth()
|
||||||
) {
|
.padding(vertical = 8.dp),
|
||||||
Column {
|
) {
|
||||||
Text(
|
Column {
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
Text(
|
||||||
style = MaterialTheme.typography.caption,
|
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||||
textAlign = TextAlign.Center,
|
style = MaterialTheme.typography.caption,
|
||||||
text = stringResource(Res.string.character_sheet__occupations_title),
|
textAlign = TextAlign.Center,
|
||||||
)
|
text = stringResource(Res.string.character_sheet__occupations_title),
|
||||||
characterSheet.occupations.forEach {
|
|
||||||
Skill(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
label = it.label,
|
|
||||||
value = it.value,
|
|
||||||
onClick = { onSkill(it) },
|
|
||||||
)
|
)
|
||||||
|
characterSheet.occupations.forEach {
|
||||||
|
Skill(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
label = it.label,
|
||||||
|
value = it.value,
|
||||||
|
onClick = { onSkill(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
DecoratedBox(
|
||||||
DecoratedBox(
|
modifier = Modifier
|
||||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
.fillMaxWidth()
|
||||||
) {
|
.padding(vertical = 8.dp),
|
||||||
Column {
|
) {
|
||||||
Text(
|
Column {
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
Text(
|
||||||
style = MaterialTheme.typography.caption,
|
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||||
textAlign = TextAlign.Center,
|
style = MaterialTheme.typography.caption,
|
||||||
text = stringResource(Res.string.character_sheet__magics__title),
|
textAlign = TextAlign.Center,
|
||||||
)
|
text = stringResource(Res.string.character_sheet__magics__title),
|
||||||
characterSheet.magics.forEach {
|
|
||||||
Skill(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
label = it.label,
|
|
||||||
value = it.value,
|
|
||||||
onClick = { onSkill(it) },
|
|
||||||
)
|
)
|
||||||
|
characterSheet.magics.forEach {
|
||||||
|
Skill(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
label = it.label,
|
||||||
|
value = it.value,
|
||||||
|
onClick = { onSkill(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
characterSheet.rolls.forEach {
|
||||||
characterSheet.rolls.forEach {
|
Roll(
|
||||||
Roll(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
label = it.label,
|
||||||
label = it.label,
|
onClick = { onRoll(it) },
|
||||||
onClick = { onRoll(it) },
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +343,9 @@ private fun Stat(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
modifier = Modifier.clickable(onClick = onClick).padding(paddingValues = paddingValues)
|
modifier = Modifier
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(paddingValues = paddingValues)
|
||||||
.then(other = modifier),
|
.then(other = modifier),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.createSavedStateHandle
|
import androidx.lifecycle.createSavedStateHandle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.pixelized.desktop.lwa.LocalWindowController
|
||||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
import com.pixelized.desktop.lwa.navigation.LocalScreenController
|
||||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.Form
|
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.Form
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -75,7 +76,8 @@ fun CharacterSheetEditPage(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
val screen = LocalScreen.current
|
val window = LocalWindowController.current
|
||||||
|
val screen = LocalScreenController.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -89,7 +91,9 @@ fun CharacterSheetEditPage(
|
||||||
onSave = {
|
onSave = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.save()
|
viewModel.save()
|
||||||
screen.popBackStack()
|
if (screen.popBackStack().not()) {
|
||||||
|
window.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,40 @@ package com.pixelized.desktop.lwa.screen.main
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextButton
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
import com.pixelized.desktop.lwa.LocalWindowController
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheet
|
import com.pixelized.desktop.lwa.WindowController
|
||||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
|
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||||
|
import com.pixelized.desktop.lwa.navigation.LocalScreenController
|
||||||
|
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
|
||||||
|
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
|
||||||
|
import com.pixelized.desktop.lwa.navigation.destination.navigateToNetwork
|
||||||
|
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
|
||||||
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
|
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.main_page__network_action
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -35,62 +49,165 @@ data class CharacterUio(
|
||||||
fun MainPage(
|
fun MainPage(
|
||||||
viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
|
viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
|
||||||
) {
|
) {
|
||||||
val screen = LocalScreen.current
|
val screen = LocalScreenController.current
|
||||||
|
|
||||||
Surface {
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.verticalScroll(state = rememberScrollState())
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 24.dp),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
MainPageContent(
|
MainPageContent(
|
||||||
characters = viewModel.characters,
|
characters = viewModel.characters,
|
||||||
onCharacter = {
|
onCharacter = {
|
||||||
screen.navigateToCharacterSheet(id = it.id)
|
viewModel.showCharacterSheet(sheet = it)
|
||||||
},
|
},
|
||||||
onCreateCharacter = {
|
onCreateCharacter = {
|
||||||
screen.navigateToCharacterSheetEdit()
|
viewModel.showCreateCharacterSheet()
|
||||||
|
},
|
||||||
|
onNetwork = {
|
||||||
|
screen.navigateToNetwork()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HandleCharacterSheet(
|
||||||
|
sheets = viewModel.sheet,
|
||||||
|
onCloseRequest = { viewModel.hideCharacterSheet(sheet = it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
HandleCharacterSheetCreation(
|
||||||
|
sheets = viewModel.create,
|
||||||
|
onCloseRequest = { viewModel.hideCreateCharacterSheet(id = it) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HandleCharacterSheet(
|
||||||
|
sheets: State<Set<CharacterUio>>,
|
||||||
|
onCloseRequest: (id: CharacterUio) -> Unit,
|
||||||
|
) {
|
||||||
|
sheets.value.forEach { sheet ->
|
||||||
|
val controller = remember {
|
||||||
|
WindowController(
|
||||||
|
onCloseRequest = { onCloseRequest(sheet) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalWindowController provides controller,
|
||||||
|
) {
|
||||||
|
Window(
|
||||||
|
onCloseRequest = { onCloseRequest(sheet) },
|
||||||
|
state = rememberWindowState(
|
||||||
|
width = 400.dp + 64.dp,
|
||||||
|
height = 900.dp,
|
||||||
|
),
|
||||||
|
title = sheet.name,
|
||||||
|
) {
|
||||||
|
CharacterSheetMainNavHost(
|
||||||
|
startDestination = CharacterSheetDestination.navigationRoute(id = sheet.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HandleCharacterSheetCreation(
|
||||||
|
sheets: State<Set<Int>>,
|
||||||
|
onCloseRequest: (id: Int) -> Unit,
|
||||||
|
) {
|
||||||
|
sheets.value.forEach { sheet ->
|
||||||
|
val controller = remember {
|
||||||
|
WindowController(
|
||||||
|
onCloseRequest = { onCloseRequest(sheet) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalWindowController provides controller,
|
||||||
|
) {
|
||||||
|
Window(
|
||||||
|
onCloseRequest = { controller.close() },
|
||||||
|
state = rememberWindowState(
|
||||||
|
width = 400.dp + 64.dp,
|
||||||
|
height = 900.dp,
|
||||||
|
),
|
||||||
|
title = stringResource(Res.string.character_sheet_edit__title),
|
||||||
|
) {
|
||||||
|
CharacterSheetMainNavHost(
|
||||||
|
startDestination = CharacterSheetEditDestination.navigationRoute(id = null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainPageContent(
|
fun MainPageContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
characters: State<List<CharacterUio>>,
|
characters: State<List<CharacterUio>>,
|
||||||
onCharacter: (CharacterUio) -> Unit,
|
onCharacter: (CharacterUio) -> Unit,
|
||||||
onCreateCharacter: () -> Unit,
|
onCreateCharacter: () -> Unit,
|
||||||
|
onNetwork: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.padding(horizontal = 24.dp),
|
modifier = modifier,
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 32.dp),
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Spacer(
|
||||||
characters.value.forEach { sheet ->
|
modifier = Modifier.weight(weight = 1f)
|
||||||
|
)
|
||||||
|
DecoratedBox {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 32.dp),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
characters.value.forEach { sheet ->
|
||||||
|
TextButton(
|
||||||
|
onClick = { onCharacter(sheet) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
maxLines = 1,
|
||||||
|
text = sheet.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { onCharacter(sheet) },
|
onClick = { onCreateCharacter() },
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Start,
|
||||||
maxLines = 1,
|
text = stringResource(Res.string.main_page__create_action),
|
||||||
text = sheet.name,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.weight(weight = 1f)
|
||||||
|
)
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { onCreateCharacter() },
|
onClick = { onNetwork() },
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Start,
|
||||||
text = stringResource(Res.string.main_page__create_action),
|
text = stringResource(Res.string.main_page__network_action),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,25 @@ package com.pixelized.desktop.lwa.screen.main
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
|
import com.pixelized.desktop.lwa.utils.extention.collectAsState
|
||||||
|
|
||||||
class MainPageViewModel : ViewModel() {
|
class MainPageViewModel : ViewModel() {
|
||||||
// using a variable to help with later injection.
|
// using a variable to help with later injection.
|
||||||
private val characterSheetRepository = CharacterSheetRepository
|
private val repository = CharacterSheetRepository
|
||||||
|
|
||||||
|
private val _sheet = mutableStateOf<Set<CharacterUio>>(emptySet())
|
||||||
|
val sheet: State<Set<CharacterUio>> get() = _sheet
|
||||||
|
|
||||||
|
private val _create = mutableStateOf<Set<Int>>(emptySet())
|
||||||
|
val create: State<Set<Int>> get() = _create
|
||||||
|
|
||||||
val characters: State<List<CharacterUio>>
|
val characters: State<List<CharacterUio>>
|
||||||
@Composable
|
@Composable
|
||||||
@Stable
|
@Stable
|
||||||
get() = characterSheetRepository
|
get() = repository
|
||||||
.characterSheetFlow()
|
.characterSheetFlow()
|
||||||
.collectAsState { sheets ->
|
.collectAsState { sheets ->
|
||||||
sheets.map { sheet ->
|
sheets.map { sheet ->
|
||||||
|
|
@ -24,4 +31,20 @@ class MainPageViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showCreateCharacterSheet() {
|
||||||
|
_create.value = _create.value.toMutableSet().apply { add(size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideCreateCharacterSheet(id: Int) {
|
||||||
|
_create.value = _create.value.toMutableSet().apply { remove(id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showCharacterSheet(sheet: CharacterUio) {
|
||||||
|
_sheet.value = _sheet.value.toMutableSet().apply { add(sheet) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideCharacterSheet(sheet: CharacterUio) {
|
||||||
|
_sheet.value = _sheet.value.toMutableSet().apply { remove(sheet) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.network
|
||||||
|
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Type
|
||||||
|
|
||||||
|
class NetworkFactory {
|
||||||
|
|
||||||
|
fun convertToUio(
|
||||||
|
player: String,
|
||||||
|
status: Status,
|
||||||
|
type: Type,
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
): NetworkPageUio {
|
||||||
|
return NetworkPageUio(
|
||||||
|
player = player,
|
||||||
|
host = host,
|
||||||
|
port = "$port",
|
||||||
|
enableFields = status == Status.DISCONNECTED,
|
||||||
|
enableActions = status == Status.DISCONNECTED && player.isNotBlank() && host.isNotBlank() && port > 0,
|
||||||
|
enableCancel = type != Type.NONE && status == Status.CONNECTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.network
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
|
import androidx.compose.material.TextField
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.pixelized.desktop.lwa.navigation.LocalScreenController
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__host__label
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__player_name__label
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__port__label
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__socket__host_action
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__title
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class NetworkPageUio(
|
||||||
|
val player: String,
|
||||||
|
val host: String,
|
||||||
|
val port: String,
|
||||||
|
val enableFields: Boolean,
|
||||||
|
val enableActions: Boolean,
|
||||||
|
val enableCancel: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NetworkPage(
|
||||||
|
viewModel: NetworkViewModel = viewModel { NetworkViewModel() },
|
||||||
|
) {
|
||||||
|
val screen = LocalScreenController.current
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
NetworkContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
player = viewModel.network,
|
||||||
|
onBack = { screen.popBackStack() },
|
||||||
|
onPlayerChange = viewModel::onPlayerNameChange,
|
||||||
|
onHostChange = viewModel::onHostChange,
|
||||||
|
onPortChange = viewModel::onPortChange,
|
||||||
|
onConnect = viewModel::connect,
|
||||||
|
onHost = viewModel::host,
|
||||||
|
onDisconnect = viewModel::disconnect,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NetworkContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
scrollState: ScrollState = rememberScrollState(),
|
||||||
|
player: State<NetworkPageUio>,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onPlayerChange: (String) -> Unit,
|
||||||
|
onHostChange: (String) -> Unit,
|
||||||
|
onPortChange: (String) -> Unit,
|
||||||
|
onConnect: () -> Unit,
|
||||||
|
onHost: () -> Unit,
|
||||||
|
onDisconnect: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
text = stringResource(Res.string.network__title),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onBack,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(state = scrollState)
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(all = 16.dp),
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
enabled = player.value.enableFields,
|
||||||
|
label = { Text(text = stringResource(Res.string.network__player_name__label)) },
|
||||||
|
onValueChange = { onPlayerChange(it) },
|
||||||
|
value = player.value.player,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.height(16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true,
|
||||||
|
enabled = player.value.enableFields,
|
||||||
|
label = { Text(text = stringResource(Res.string.network__host__label)) },
|
||||||
|
onValueChange = { onHostChange(it) },
|
||||||
|
value = player.value.host,
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier.width(100.dp),
|
||||||
|
singleLine = true,
|
||||||
|
enabled = player.value.enableFields,
|
||||||
|
label = { Text(text = stringResource(Res.string.network__port__label)) },
|
||||||
|
onValueChange = { onPortChange(it) },
|
||||||
|
value = player.value.port,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
enabled = player.value.enableActions,
|
||||||
|
onClick = onConnect,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(Res.string.network__socket__connect_action))
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
enabled = player.value.enableActions,
|
||||||
|
onClick = onHost,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(Res.string.network__socket__host_action))
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
enabled = player.value.enableCancel,
|
||||||
|
onClick = onDisconnect,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(Res.string.network__socket__disconnect_action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.network
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
|
|
||||||
|
class NetworkViewModel : ViewModel() {
|
||||||
|
private val repository = NetworkRepository
|
||||||
|
private val factory = NetworkFactory()
|
||||||
|
|
||||||
|
private val host = mutableStateOf("localhost")
|
||||||
|
private val port = mutableStateOf(27030)
|
||||||
|
val network: State<NetworkPageUio>
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
get() {
|
||||||
|
val player = repository.player.collectAsState()
|
||||||
|
val status = repository.status.collectAsState()
|
||||||
|
val type = repository.type.collectAsState()
|
||||||
|
return remember {
|
||||||
|
derivedStateOf {
|
||||||
|
factory.convertToUio(
|
||||||
|
player = player.value,
|
||||||
|
status = status.value,
|
||||||
|
type = type.value,
|
||||||
|
host = host.value,
|
||||||
|
port = port.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPlayerNameChange(player: String) {
|
||||||
|
repository.onPlayerNameChange(player = player)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPortChange(port: String) {
|
||||||
|
this.port.value = port.toIntOrNull() ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onHostChange(host: String) {
|
||||||
|
this.host.value = host
|
||||||
|
}
|
||||||
|
|
||||||
|
fun host() {
|
||||||
|
repository.host(port = port.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect() {
|
||||||
|
repository.connect(host = host.value, port = port.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
repository.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -109,7 +109,7 @@ fun RollPage(
|
||||||
this.alpha = 0.8f
|
this.alpha = 0.8f
|
||||||
this.rotationZ = viewModel.rollRotation.value
|
this.rotationZ = viewModel.rollRotation.value
|
||||||
},
|
},
|
||||||
tint = MaterialTheme.colors.onSurface,
|
tint = MaterialTheme.colors.primary,
|
||||||
painter = painterResource(Res.drawable.ic_d20_32dp),
|
painter = painterResource(Res.drawable.ic_d20_32dp),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.pixelized.desktop.lwa.business.RollUseCase
|
||||||
import com.pixelized.desktop.lwa.business.SkillStepUseCase
|
import com.pixelized.desktop.lwa.business.SkillStepUseCase
|
||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
|
||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
|
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
@ -25,6 +26,8 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__success
|
||||||
import org.jetbrains.compose.resources.getString
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
class RollViewModel : ViewModel() {
|
class RollViewModel : ViewModel() {
|
||||||
|
private val network = NetworkRepository
|
||||||
|
|
||||||
private val _roll = mutableStateOf(RollUio(label = "", value = 0))
|
private val _roll = mutableStateOf(RollUio(label = "", value = 0))
|
||||||
val roll: State<RollUio> get() = _roll
|
val roll: State<RollUio> get() = _roll
|
||||||
|
|
||||||
|
|
@ -134,8 +137,14 @@ class RollViewModel : ViewModel() {
|
||||||
} ?: "",
|
} ?: "",
|
||||||
value = roll,
|
value = roll,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
share(roll = roll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun share(roll: Int) {
|
||||||
|
network.share(type = "roll", value = "$roll")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,9 @@
|
||||||
package com.pixelized.desktop.lwa
|
package com.pixelized.desktop.lwa
|
||||||
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.Window
|
|
||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
application {
|
application {
|
||||||
Window(
|
App()
|
||||||
onCloseRequest = ::exitApplication,
|
|
||||||
state = rememberWindowState(
|
|
||||||
width = 320.dp + 64.dp,
|
|
||||||
height = 900.dp,
|
|
||||||
),
|
|
||||||
title = "LwaCharacterSheet",
|
|
||||||
) {
|
|
||||||
App()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,22 +6,27 @@ junit = "4.13.2"
|
||||||
compose-multiplatform = "1.7.0"
|
compose-multiplatform = "1.7.0"
|
||||||
androidx-lifecycle = "2.8.3"
|
androidx-lifecycle = "2.8.3"
|
||||||
androidx-navigation = "2.8.0-alpha10"
|
androidx-navigation = "2.8.0-alpha10"
|
||||||
|
ktor_version = "3.0.1"
|
||||||
[libraries]
|
|
||||||
# Test
|
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
|
||||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
|
||||||
|
|
||||||
# Compose
|
|
||||||
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
|
||||||
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
|
||||||
androidx-navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
|
|
||||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
|
||||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-json" }
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
|
kotlinKtor = { id = "io.ktor.plugin", version.ref = "ktor_version"}
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
|
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
||||||
|
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||||
|
androidx-navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
|
||||||
|
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-json" }
|
||||||
|
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor_version" }
|
||||||
|
ktor-client-cio = { group = 'io.ktor', name = "ktor-client-cio", version.ref = "ktor_version" }
|
||||||
|
ktor-client-websockets = { group = 'io.ktor', name = "ktor-client-websockets", version.ref = "ktor_version" }
|
||||||
|
ktor-server-core = { group = 'io.ktor', name = "ktor-server-core", version.ref = "ktor_version" }
|
||||||
|
ktor-server-netty = { group = 'io.ktor', name = "ktor-server-netty", version.ref = "ktor_version" }
|
||||||
|
ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor_version" }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue