diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 56eaa82..359eca4 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -5,6 +5,8 @@ plugins {
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
+// kotlin("jvm") version "1.9.20"
+// alias(libs.plugins.kotlinKtor)
}
kotlin {
@@ -24,6 +26,12 @@ kotlin {
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)
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 {
@@ -53,12 +61,12 @@ compose.desktop {
includeAllModules = true
- // Use system theming fot the app toolbars.
+ // Use system theming for the app toolbars.
jvmArgs("-Dapple.awt.application.appearance=system")
}
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"))
}
}
diff --git a/composeApp/compose-desktop.pro b/composeApp/compose-desktop.pro
index a9bbc0e..f28c79e 100644
--- a/composeApp/compose-desktop.pro
+++ b/composeApp/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.collection.** { *; }
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 896deb3..84f3684 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -1,6 +1,7 @@
Créer une feuille de personnage
+ Configuration réseau
Réussite critique
Réussite spéciale
@@ -65,4 +66,19 @@
Compétences
Occupations
Compétences magiques
+
+ Configuration réseau
+ Nom du joueur
+ host
+ port
+ Se connecter à la table
+ Héberger la table
+ Se déconnecter
+ État de connexion : %1$s
+ Connecté
+ Disconnecté
+ Type de connexion : %1$s
+ Serveur
+ Client
+ Aucun
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt
index b56fac6..9636519 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt
@@ -3,19 +3,58 @@ package com.pixelized.desktop.lwa
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
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.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.theme.LwaTheme
import org.jetbrains.compose.ui.tooling.preview.Preview
+val LocalWindowController = compositionLocalOf {
+ error("Local Window Controller is not yet ready")
+}
+
+@Stable
+data class WindowController(
+ private val onCloseRequest: () -> Unit
+) {
+ fun close() = onCloseRequest()
+}
+
@Composable
@Preview
-fun App() {
- LwaTheme {
- Surface(
- modifier = Modifier.fillMaxSize()
+fun ApplicationScope.App() {
+ val controller = remember {
+ WindowController(
+ 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()
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt
index 49ecf42..22d6c92 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt
@@ -7,11 +7,10 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
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.composableNetworkPage
-val LocalScreen = compositionLocalOf {
+val LocalScreenController = compositionLocalOf {
error("MainNavHost controller is not yet ready")
}
@@ -21,16 +20,14 @@ fun MainNavHost(
startDestination: String = MainDestination.navigationRoute(),
) {
CompositionLocalProvider(
- LocalScreen provides controller,
+ LocalScreenController provides controller,
) {
NavHost(
navController = controller,
startDestination = startDestination,
) {
composableMainPage()
-
- composableCharacterSheetPage()
- composableCharacterSheetEditPage()
+ composableNetworkPage()
}
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/NetworkDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/NetworkDestination.kt
new file mode 100644
index 0000000..edd3b41
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/NetworkDestination.kt
@@ -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)
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt
new file mode 100644
index 0000000..d39b0e1
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt
@@ -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
+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()
+ private val sharedFlow = messageResponseFlow.asSharedFlow()
+
+ private val _player = MutableStateFlow("")
+ val player: StateFlow get() = _player
+
+ private val _status = MutableStateFlow(Status.DISCONNECTED)
+ val status: StateFlow get() = _status
+
+ private val _type = MutableStateFlow(Type.NONE)
+ val type: StateFlow 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,
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt
new file mode 100644
index 0000000..76884b1
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt
@@ -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,
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Server.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Server.kt
new file mode 100644
index 0000000..e5c321d
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Server.kt
@@ -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 {
+ 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,
+ )
+ }
+ },
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt
new file mode 100644
index 0000000..1048cc4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt
@@ -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,
+)
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetNavHost.kt
new file mode 100644
index 0000000..5257758
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetNavHost.kt
@@ -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()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt
index cb3f03d..8677308 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt
@@ -4,13 +4,12 @@ import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.Row
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.size
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.style.TextAlign
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.viewmodel.compose.viewModel
+import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlay
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.screen.roll.RollPage
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
@@ -95,7 +94,8 @@ fun CharacterSheetPage(
overlayViewModel: BlurOverlayViewModel = viewModel { BlurOverlayViewModel() },
rollViewModel: RollViewModel = viewModel { RollViewModel() },
) {
- val screen = LocalScreen.current
+ val window = LocalWindowController.current
+ val screen = LocalScreenController.current
val scope = rememberCoroutineScope()
Surface(
@@ -123,7 +123,9 @@ fun CharacterSheetPage(
onDelete = {
scope.launch {
viewModel.deleteCharacter(id = sheet.id)
- screen.popBackStack()
+ if (screen.popBackStack().not()) {
+ window.close()
+ }
}
},
onCharacteristic = { characteristic ->
@@ -148,12 +150,10 @@ fun CharacterSheetPage(
}
}
-@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CharacterSheetPageContent(
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
- width: Dp = 320.dp,
characterSheet: CharacterSheetPageUio,
onBack: () -> Unit,
onEdit: () -> Unit,
@@ -163,6 +163,7 @@ fun CharacterSheetPageContent(
onRoll: (roll: CharacterSheetPageUio.Roll) -> Unit,
) {
Scaffold(
+ modifier = modifier,
topBar = {
TopAppBar(
title = {
@@ -203,115 +204,131 @@ fun CharacterSheetPageContent(
)
},
content = { paddingValues ->
- Column(
+ Row(
modifier = Modifier
- .verticalScroll(state = scrollState).padding(all = 16.dp)
+ .verticalScroll(state = scrollState)
.padding(paddingValues)
- .then(other = modifier),
- verticalArrangement = Arrangement.spacedBy(space = 16.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
+ .padding(horizontal = 24.dp, vertical = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
- FlowRow(
- maxItemsInEachRow = 3,
- horizontalArrangement = Arrangement.spacedBy(
- space = 16.dp,
- alignment = Alignment.CenterHorizontally,
- ),
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .width(100.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
characterSheet.characteristics.forEach {
Stat(
- modifier = Modifier.width(width = width / 3 - 32.dp)
- .height(height = 112.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(min = 120.dp),
characteristic = it,
onClick = { onCharacteristic(it) },
)
}
}
- DecoratedBox(
- modifier = Modifier.width(width = width).padding(vertical = 8.dp),
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .weight(2f / 3f),
+ verticalArrangement = Arrangement.spacedBy(space = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
) {
- Column {
- Text(
- modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
- style = MaterialTheme.typography.caption,
- textAlign = TextAlign.Center,
- text = stringResource(Res.string.character_sheet__sub_characteristics__title),
- )
- characterSheet.subCharacteristics.forEach {
- Characteristics(
- modifier = Modifier.fillMaxWidth(),
- characteristic = it,
+ DecoratedBox(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ ) {
+ Column {
+ Text(
+ modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
+ style = MaterialTheme.typography.caption,
+ textAlign = TextAlign.Center,
+ text = stringResource(Res.string.character_sheet__sub_characteristics__title),
)
+ characterSheet.subCharacteristics.forEach {
+ Characteristics(
+ modifier = Modifier.fillMaxWidth(),
+ characteristic = it,
+ )
+ }
}
}
- }
- DecoratedBox(
- modifier = Modifier.width(width = width).padding(vertical = 8.dp),
- ) {
- Column {
- Text(
- modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
- style = MaterialTheme.typography.caption,
- textAlign = TextAlign.Center,
- text = stringResource(Res.string.character_sheet__skills__title),
- )
- characterSheet.skills.forEach {
- Skill(
- modifier = Modifier.fillMaxWidth(),
- label = it.label,
- value = it.value,
- onClick = { onSkill(it) },
+ DecoratedBox(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ ) {
+ Column {
+ Text(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 8.dp),
+ style = MaterialTheme.typography.caption,
+ textAlign = TextAlign.Center,
+ text = stringResource(Res.string.character_sheet__skills__title),
)
+ characterSheet.skills.forEach {
+ Skill(
+ modifier = Modifier.fillMaxWidth(),
+ label = it.label,
+ value = it.value,
+ onClick = { onSkill(it) },
+ )
+ }
}
}
- }
- DecoratedBox(
- modifier = Modifier.width(width = width).padding(vertical = 8.dp),
- ) {
- Column {
- Text(
- modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
- style = MaterialTheme.typography.caption,
- 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) },
+ DecoratedBox(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ ) {
+ Column {
+ Text(
+ modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
+ style = MaterialTheme.typography.caption,
+ 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) },
+ )
+ }
}
}
- }
- DecoratedBox(
- modifier = Modifier.width(width = width).padding(vertical = 8.dp),
- ) {
- Column {
- Text(
- modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
- style = MaterialTheme.typography.caption,
- 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) },
+ DecoratedBox(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ ) {
+ Column {
+ Text(
+ modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
+ style = MaterialTheme.typography.caption,
+ 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.rolls.forEach {
- Roll(
- modifier = Modifier.fillMaxWidth(),
- label = it.label,
- onClick = { onRoll(it) },
- )
+ characterSheet.rolls.forEach {
+ Roll(
+ modifier = Modifier.fillMaxWidth(),
+ label = it.label,
+ onClick = { onRoll(it) },
+ )
+ }
}
}
}
@@ -326,7 +343,9 @@ private fun Stat(
onClick: () -> Unit,
) {
DecoratedBox(
- modifier = Modifier.clickable(onClick = onClick).padding(paddingValues = paddingValues)
+ modifier = Modifier
+ .clickable(onClick = onClick)
+ .padding(paddingValues = paddingValues)
.then(other = modifier),
) {
Text(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt
index fdf9161..a4af668 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt
@@ -29,8 +29,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.createSavedStateHandle
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.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.Form
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()
Surface(
@@ -89,7 +91,9 @@ fun CharacterSheetEditPage(
onSave = {
scope.launch {
viewModel.save()
- screen.popBackStack()
+ if (screen.popBackStack().not()) {
+ window.close()
+ }
}
},
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt
index a8bcbaa..f7ee73e 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt
@@ -3,26 +3,40 @@ package com.pixelized.desktop.lwa.screen.main
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
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 com.pixelized.desktop.lwa.navigation.LocalScreen
-import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheet
-import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
+import com.pixelized.desktop.lwa.LocalWindowController
+import com.pixelized.desktop.lwa.WindowController
+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.character_sheet_edit__title
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
+import lwacharactersheet.composeapp.generated.resources.main_page__network_action
import org.jetbrains.compose.resources.stringResource
@Stable
@@ -35,62 +49,165 @@ data class CharacterUio(
fun MainPage(
viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
) {
- val screen = LocalScreen.current
+ val screen = LocalScreenController.current
- Surface {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ ) {
Box(
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier
+ .verticalScroll(state = rememberScrollState())
+ .fillMaxSize()
+ .padding(horizontal = 24.dp),
contentAlignment = Alignment.Center,
) {
MainPageContent(
characters = viewModel.characters,
onCharacter = {
- screen.navigateToCharacterSheet(id = it.id)
+ viewModel.showCharacterSheet(sheet = it)
},
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>,
+ 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>,
+ 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
fun MainPageContent(
modifier: Modifier = Modifier,
characters: State>,
onCharacter: (CharacterUio) -> Unit,
onCreateCharacter: () -> Unit,
+ onNetwork: () -> Unit,
) {
Column(
- modifier = modifier.padding(horizontal = 24.dp),
- verticalArrangement = Arrangement.spacedBy(space = 32.dp),
+ modifier = modifier,
) {
- Column {
- characters.value.forEach { sheet ->
+ Spacer(
+ 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(
- onClick = { onCharacter(sheet) },
+ onClick = { onCreateCharacter() },
) {
Text(
modifier = Modifier.fillMaxWidth(),
+ maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
- maxLines = 1,
- text = sheet.name,
+ text = stringResource(Res.string.main_page__create_action),
)
}
}
}
-
+ Spacer(
+ modifier = Modifier.weight(weight = 1f)
+ )
TextButton(
- onClick = { onCreateCharacter() },
+ onClick = { onNetwork() },
) {
Text(
modifier = Modifier.fillMaxWidth(),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Start,
- text = stringResource(Res.string.main_page__create_action),
+ text = stringResource(Res.string.main_page__network_action),
)
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt
index d627306..0947b2a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt
@@ -3,18 +3,25 @@ package com.pixelized.desktop.lwa.screen.main
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.utils.extention.collectAsState
class MainPageViewModel : ViewModel() {
// using a variable to help with later injection.
- private val characterSheetRepository = CharacterSheetRepository
+ private val repository = CharacterSheetRepository
+
+ private val _sheet = mutableStateOf>(emptySet())
+ val sheet: State> get() = _sheet
+
+ private val _create = mutableStateOf>(emptySet())
+ val create: State> get() = _create
val characters: State>
@Composable
@Stable
- get() = characterSheetRepository
+ get() = repository
.characterSheetFlow()
.collectAsState { sheets ->
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) }
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkFactory.kt
new file mode 100644
index 0000000..dbfbc2a
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkFactory.kt
@@ -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
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkPage.kt
new file mode 100644
index 0000000..8a1f644
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkPage.kt
@@ -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,
+ 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))
+ }
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt
new file mode 100644
index 0000000..1ce3c31
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt
@@ -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
+ @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()
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt
index 7f12bc6..189e2de 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt
@@ -109,7 +109,7 @@ fun RollPage(
this.alpha = 0.8f
this.rotationZ = viewModel.rollRotation.value
},
- tint = MaterialTheme.colors.onSurface,
+ tint = MaterialTheme.colors.primary,
painter = painterResource(Res.drawable.ic_d20_32dp),
contentDescription = null,
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt
index 60bcf17..e3b83d1 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt
@@ -10,6 +10,7 @@ import com.pixelized.desktop.lwa.business.RollUseCase
import com.pixelized.desktop.lwa.business.SkillStepUseCase
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
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 kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -25,6 +26,8 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__success
import org.jetbrains.compose.resources.getString
class RollViewModel : ViewModel() {
+ private val network = NetworkRepository
+
private val _roll = mutableStateOf(RollUio(label = "", value = 0))
val roll: State get() = _roll
@@ -134,8 +137,14 @@ class RollViewModel : ViewModel() {
} ?: "",
value = roll,
)
+
+ share(roll = roll)
}
}
}
}
+
+ private fun share(roll: Int) {
+ network.share(type = "roll", value = "$roll")
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt
index af06c8c..bdbba3a 100644
--- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt
+++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt
@@ -1,21 +1,9 @@
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.rememberWindowState
fun main() {
application {
- Window(
- onCloseRequest = ::exitApplication,
- state = rememberWindowState(
- width = 320.dp + 64.dp,
- height = 900.dp,
- ),
- title = "LwaCharacterSheet",
- ) {
- App()
- }
+ App()
}
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 64e71f8..e2f7e34 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,22 +6,27 @@ junit = "4.13.2"
compose-multiplatform = "1.7.0"
androidx-lifecycle = "2.8.3"
androidx-navigation = "2.8.0-alpha10"
-
-[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" }
+ktor_version = "3.0.1"
[plugins]
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
-kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
\ No newline at end of file
+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" }
\ No newline at end of file