diff --git a/build.gradle.kts b/build.gradle.kts
index eb8e169..c255cba 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,9 +1,7 @@
plugins {
- // this is necessary to avoid the plugins to be loaded multiple times
- // in each subproject's classloader
- alias(libs.plugins.kotlinMultiplatform) apply false
- alias(libs.plugins.kotlinSerialization) apply false
- // alias(libs.plugins.kotlinKtor) apply false
alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.kotlinSerialization) apply false
alias(libs.plugins.composeCompiler) apply false
+ alias(libs.plugins.kotlinJvm) apply false
+ alias(libs.plugins.kotlinMultiplatform) apply false
}
\ No newline at end of file
diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 78f9ff2..2dc5255 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -29,16 +29,15 @@ kotlin {
api(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
- // network.
+ // common
+ implementation(projects.shared)
+ // network
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)
// shell
- implementation(libs.lordcodes.turttle)
+ implementation(libs.turtle)
}
commonTest.dependencies {
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index ce157a1..2d1506b 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -136,7 +136,6 @@
host
port
Se connecter à la table
- Héberger la table
Se déconnecter
État de connexion : %1$s
Connecté
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
index 95cb46d..c3deaea 100644
--- 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
@@ -1,15 +1,17 @@
package com.pixelized.desktop.lwa.repository.network
+//import com.pixelized.desktop.lwa.repository.network.helper.server
+//import io.ktor.server.engine.EmbeddedServer
+//import io.ktor.server.netty.NettyApplicationEngine
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 com.pixelized.desktop.lwa.repository.network.protocol.MessageContent
+import com.pixelized.desktop.lwa.utils.extention.decodeFromFrame
+import com.pixelized.desktop.lwa.utils.extention.encodeToFrame
+import com.pixelized.server.lwa.SERVER_PORT
+import com.pixelized.server.lwa.protocol.Message
+import com.pixelized.server.lwa.protocol.MessageContent
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
@@ -21,20 +23,17 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.encodeToJsonElement
-typealias Server = EmbeddedServer
typealias Client = HttpClient
class NetworkRepository {
companion object {
- const val DEFAULT_PORT = 16030
+ const val DEFAULT_PORT = SERVER_PORT
const val DEFAULT_HOST = "pixelized.freeboxos.fr"
}
private val scope = CoroutineScope(Dispatchers.IO)
private var networkJob: Job? = null
- private var server: Server? = null
private var client: Client? = null
private val outgoingMessageBuffer = MutableSharedFlow()
@@ -47,65 +46,14 @@ class NetworkRepository {
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 {
- // send local message to the clients
- outgoingMessageBuffer.collect { message ->
- send(Json.encodeToFrame(message = message))
- }
- }
- runCatching {
- // watching for clients incoming message
- incoming.consumeEach { frame ->
- if (frame is Frame.Text) {
- val message = Json.decodeFromFrame(frame = frame)
- incomingMessageBuffer.emit(message)
- // broadcast to clients the message
- outgoingMessageBuffer.emit(message)
- }
- }
- }.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,
- onConnect: (Type) -> Unit = { },
+ onConnect: () -> Unit = { },
onFailure: (Exception) -> Unit = { },
onClose: () -> Unit = { },
) {
@@ -115,9 +63,8 @@ class NetworkRepository {
networkJob = scope.launch {
try {
client?.connectWebSocket(host = host, port = port) {
- _type.value = Type.CLIENT
_status.value = Status.CONNECTED
- onConnect(Type.CLIENT)
+ onConnect()
val job = launch {
// send message to the server
@@ -140,7 +87,6 @@ class NetworkRepository {
} catch (exception: Exception) {
onFailure(exception)
} finally {
- _type.value = Type.NONE
_status.value = Status.DISCONNECTED
onClose()
}
@@ -150,7 +96,6 @@ class NetworkRepository {
fun disconnect() {
networkJob?.cancel()
scope.launch {
- server?.stop()
client?.close()
}
}
@@ -165,10 +110,6 @@ class NetworkRepository {
)
// emit the message into the outgoing buffer
outgoingMessageBuffer.emit(message)
- // emit the message into the incoming buffer IF we are the server
- if (type.value == Type.SERVER) {
- incomingMessageBuffer.emit(message)
- }
}
}
@@ -176,20 +117,4 @@ class NetworkRepository {
CONNECTED,
DISCONNECTED
}
-
- enum class Type {
- CLIENT,
- SERVER,
- NONE,
- }
-}
-
-private fun Json.decodeFromFrame(frame: Frame.Text): Message {
- val json = frame.readText()
- return decodeFromString(json)
-}
-
-private fun Json.encodeToFrame(message: Message): Frame {
- val json = encodeToJsonElement(message)
- return Frame.Text(text = json.toString())
}
\ 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
deleted file mode 100644
index ee202be..0000000
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Server.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-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,
- 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/roll_history/RollHistoryRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt
index 1c045bb..b6b8a90 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt
@@ -1,8 +1,8 @@
package com.pixelized.desktop.lwa.repository.roll_history
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
-import com.pixelized.desktop.lwa.repository.network.protocol.Message
-import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
+import com.pixelized.server.lwa.protocol.Message
+import com.pixelized.server.lwa.protocol.RollMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharedFlow
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
index dbfbc2a..e9ec5e3 100644
--- 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
@@ -1,14 +1,12 @@
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 {
@@ -18,7 +16,7 @@ class NetworkFactory {
port = "$port",
enableFields = status == Status.DISCONNECTED,
enableActions = status == Status.DISCONNECTED && player.isNotBlank() && host.isNotBlank() && port > 0,
- enableCancel = type != Type.NONE && status == Status.CONNECTED
+ enableCancel = 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
index 27bb1e9..0b5c07f 100644
--- 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
@@ -48,11 +48,9 @@ import lwacharactersheet.composeapp.generated.resources.network__player_name__la
import lwacharactersheet.composeapp.generated.resources.network__port__label
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
-import lwacharactersheet.composeapp.generated.resources.network__socket__host_action
import lwacharactersheet.composeapp.generated.resources.network__title
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
-import org.koin.core.annotation.KoinExperimentalAPI
@Stable
data class NetworkPageUio(
@@ -64,7 +62,6 @@ data class NetworkPageUio(
val enableCancel: Boolean,
)
-@OptIn(KoinExperimentalAPI::class)
@Composable
fun NetworkPage(
viewModel: NetworkViewModel = koinViewModel(),
@@ -91,7 +88,6 @@ fun NetworkPage(
onHostChange = viewModel::onHostChange,
onPortChange = viewModel::onPortChange,
onConnect = viewModel::connect,
- onHost = viewModel::host,
onDisconnect = viewModel::disconnect,
)
}
@@ -133,7 +129,6 @@ private fun NetworkContent(
onHostChange: (String) -> Unit,
onPortChange: (String) -> Unit,
onConnect: () -> Unit,
- onHost: () -> Unit,
onDisconnect: () -> Unit,
) {
Scaffold(
@@ -207,13 +202,6 @@ private fun NetworkContent(
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,
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
index f753541..e747022 100644
--- 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
@@ -20,7 +20,6 @@ class NetworkViewModel(
private val repository: NetworkRepository,
private val factory: NetworkFactory
) : ViewModel() {
-
private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
@@ -41,13 +40,11 @@ class NetworkViewModel(
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,
)
@@ -67,10 +64,6 @@ class NetworkViewModel(
this.host.value = host
}
- fun host() {
- repository.host(port = port.value)
- }
-
fun connect() {
controller.show()
_isLoading.value = true
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt
index 0171c61..202164f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt
@@ -4,7 +4,7 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
+import com.pixelized.server.lwa.protocol.RollMessage
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import kotlinx.coroutines.launch
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/JsonExt.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/JsonExt.kt
new file mode 100644
index 0000000..4c40882
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/JsonExt.kt
@@ -0,0 +1,17 @@
+package com.pixelized.desktop.lwa.utils.extention
+
+import com.pixelized.server.lwa.protocol.Message
+import io.ktor.websocket.Frame
+import io.ktor.websocket.readText
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.encodeToJsonElement
+
+fun Json.decodeFromFrame(frame: Frame.Text): Message {
+ val json = frame.readText()
+ return decodeFromString(json)
+}
+
+fun Json.encodeToFrame(message: Message): Frame {
+ val json = encodeToJsonElement(message)
+ return Frame.Text(text = json.toString())
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index ddad6aa..1097b62 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,4 +3,7 @@ kotlin.code.style=official
kotlin.daemon.jvmargs=-Xmx2048M
#Gradle
-org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
\ No newline at end of file
+org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
+
+#Ktor
+io.ktor.development=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 55ad362..b76ae76 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,14 +5,19 @@ kotlinx-json = "1.7.3"
compose-multiplatform = "1.7.0"
androidx-lifecycle = "2.8.3"
androidx-navigation = "2.8.0-alpha10"
-ktor_version = "3.0.0"
+ktor = "3.0.1"
koin = "4.0.0"
+turtle = "0.5.0"
+logback = "1.5.11"
+
[plugins]
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -29,11 +34,12 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
-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" }
+ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
+ktor-client-cio = { group = 'io.ktor', name = "ktor-client-cio", version.ref = "ktor" }
+ktor-client-websockets = { group = 'io.ktor', name = "ktor-client-websockets", version.ref = "ktor" }
+ktor-server-core = { group = 'io.ktor', name = "ktor-server-core", version.ref = "ktor" }
+ktor-server-netty = { group = 'io.ktor', name = "ktor-server-netty", version.ref = "ktor" }
+ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor" }
-lordcodes-turttle = { group="com.lordcodes.turtle", name="turtle", version="0.5.0"}
\ No newline at end of file
+turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" }
+logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
\ No newline at end of file
diff --git a/server/.gitignore b/server/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/server/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/server/build.gradle.kts b/server/build.gradle.kts
index 494178b..0dc6d93 100644
--- a/server/build.gradle.kts
+++ b/server/build.gradle.kts
@@ -1,44 +1,23 @@
plugins {
- alias(libs.plugins.androidApplication)
- alias(libs.plugins.kotlinAndroid)
+ alias(libs.plugins.kotlinJvm)
+ alias(libs.plugins.ktor)
+ alias(libs.plugins.kotlinSerialization)
+ application
}
-android {
- namespace = "com.pixelized.server"
- compileSdk = 34
+group = "com.pixelized.server.lwa"
+version = "1.0.0"
- defaultConfig {
- applicationId = "com.pixelized.server"
- minSdk = 24
- targetSdk = 34
- versionCode = 1
- versionName = "1.0"
-
- testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- isMinifyEnabled = false
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
- }
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = "1.8"
- }
+application {
+ mainClass.set("com.pixelized.server.lwa.ApplicationKt")
+ applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["io.ktor.development"] ?: "false"}")
}
dependencies {
-
- implementation(libs.appcompat.v7)
- testImplementation(libs.junit)
- androidTestImplementation(libs.runner)
- androidTestImplementation(libs.espresso.core)
+ implementation(projects.shared)
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.logback)
+ implementation(libs.ktor.server.core)
+ implementation(libs.ktor.server.netty)
+ implementation(libs.ktor.server.websockets)
}
\ No newline at end of file
diff --git a/server/proguard-rules.pro b/server/proguard-rules.pro
deleted file mode 100644
index 481bb43..0000000
--- a/server/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/server/src/androidTest/java/com/pixelized/server/ExampleInstrumentedTest.kt b/server/src/androidTest/java/com/pixelized/server/ExampleInstrumentedTest.kt
deleted file mode 100644
index 6b7d46f..0000000
--- a/server/src/androidTest/java/com/pixelized/server/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.pixelized.server
-
-import android.support.test.InstrumentationRegistry
-import android.support.test.runner.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
- @Test
- fun useAppContext() {
- // Context of the app under test.
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.pixelized.server", appContext.packageName)
- }
-}
\ No newline at end of file
diff --git a/server/src/main/AndroidManifest.xml b/server/src/main/AndroidManifest.xml
deleted file mode 100644
index a2157ec..0000000
--- a/server/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt b/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt
new file mode 100644
index 0000000..70e6e5e
--- /dev/null
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt
@@ -0,0 +1,7 @@
+package com.pixelized.server.lwa
+
+import com.pixelized.server.lwa.server.LocalServer
+
+fun main() {
+ LocalServer().create().start()
+}
\ No newline at end of file
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt b/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt
new file mode 100644
index 0000000..a3b7a7c
--- /dev/null
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt
@@ -0,0 +1,17 @@
+package com.pixelized.server.lwa.extention
+
+import com.pixelized.server.lwa.protocol.Message
+import io.ktor.websocket.Frame
+import io.ktor.websocket.readText
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.encodeToJsonElement
+
+fun Json.decodeFromFrame(frame: Frame.Text): Message {
+ val json = frame.readText()
+ return decodeFromString(json)
+}
+
+fun Json.encodeToFrame(message: Message): Frame {
+ val json = encodeToJsonElement(message)
+ return Frame.Text(text = json.toString())
+}
\ No newline at end of file
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt
new file mode 100644
index 0000000..16e39cf
--- /dev/null
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt
@@ -0,0 +1,101 @@
+package com.pixelized.server.lwa.server
+
+import com.pixelized.server.lwa.SERVER_PORT
+import com.pixelized.server.lwa.extention.decodeFromFrame
+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 io.ktor.websocket.Frame
+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.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
+import kotlin.time.Duration.Companion.seconds
+
+// https://ktor.io/docs/server-websockets.html#handle-multiple-session
+typealias Server = EmbeddedServer
+
+class LocalServer {
+ private var server: Server? = null
+ private val scope = CoroutineScope(Dispatchers.IO)
+ private var networkJob: Job? = null
+ private val outgoingMessageBuffer = MutableSharedFlow()
+
+ fun create(): LocalServer {
+ server = build {
+ println("Server launched")
+
+ val job = launch {
+ // send local message to the clients
+ outgoingMessageBuffer.collect { frame ->
+ send(frame)
+ }
+ }
+ runCatching {
+ // watching for clients incoming message
+ incoming.consumeEach { frame ->
+ if (frame is Frame.Text) {
+ val message = Json.decodeFromFrame(frame = frame)
+ println(message)
+ // broadcast to clients the message
+ outgoingMessageBuffer.emit(frame)
+ }
+ }
+ }.onFailure { exception ->
+ println("WebSocket exception: ${exception.localizedMessage}")
+ }.also {
+ job.cancel()
+ }
+ }
+ return this
+ }
+
+ fun start() {
+ runBlocking {
+ try {
+ server?.start(wait = true)
+ } catch (exception: Exception) {
+ // TODO
+ println("WebSocket exception: ${exception.localizedMessage}")
+ } finally {
+ println("Server close")
+ }
+ }
+ }
+
+ private fun build(
+ port: Int = SERVER_PORT,
+ 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/server/src/main/res/drawable/ic_launcher_background.xml b/server/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 07d5da9..0000000
--- a/server/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/server/src/main/res/drawable/ic_launcher_foreground.xml b/server/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d1..0000000
--- a/server/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/server/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/server/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 6f3b755..0000000
--- a/server/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/server/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/server/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 6f3b755..0000000
--- a/server/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/server/src/main/res/mipmap-hdpi/ic_launcher.webp b/server/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
Binary files a/server/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/server/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d..0000000
Binary files a/server/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-mdpi/ic_launcher.webp b/server/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
Binary files a/server/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/server/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611d..0000000
Binary files a/server/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-xhdpi/ic_launcher.webp b/server/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
Binary files a/server/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/server/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a695..0000000
Binary files a/server/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/server/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
Binary files a/server/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/server/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f50..0000000
Binary files a/server/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/server/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
Binary files a/server/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/server/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/server/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae3..0000000
Binary files a/server/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/server/src/main/res/values-night/themes.xml b/server/src/main/res/values-night/themes.xml
deleted file mode 100644
index b112a1c..0000000
--- a/server/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/server/src/main/res/values/colors.xml b/server/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127..0000000
--- a/server/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/server/src/main/res/values/strings.xml b/server/src/main/res/values/strings.xml
deleted file mode 100644
index 8d8a20c..0000000
--- a/server/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- server
-
\ No newline at end of file
diff --git a/server/src/main/res/values/themes.xml b/server/src/main/res/values/themes.xml
deleted file mode 100644
index 17c56b5..0000000
--- a/server/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml
new file mode 100644
index 0000000..3e11d78
--- /dev/null
+++ b/server/src/main/resources/logback.xml
@@ -0,0 +1,12 @@
+
+
+
+ %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/src/test/java/com/pixelized/server/ExampleUnitTest.kt b/server/src/test/java/com/pixelized/server/ExampleUnitTest.kt
deleted file mode 100644
index d95fbd9..0000000
--- a/server/src/test/java/com/pixelized/server/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.pixelized.server
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ec1776f..ecb7e05 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,4 +28,6 @@ dependencyResolutionManagement {
}
}
-include(":composeApp")
\ No newline at end of file
+include(":composeApp")
+include(":server")
+include(":shared")
\ No newline at end of file
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
new file mode 100644
index 0000000..7028ceb
--- /dev/null
+++ b/shared/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.kotlinSerialization)
+}
+
+kotlin {
+ jvm()
+
+ sourceSets {
+ commonMain.dependencies {
+ implementation(libs.kotlinx.serialization.json)
+ }
+ }
+}
+
diff --git a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt
new file mode 100644
index 0000000..f87f00b
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt
@@ -0,0 +1,3 @@
+package com.pixelized.server.lwa
+
+const val SERVER_PORT = 16030
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/Message.kt
similarity index 68%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt
rename to shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/Message.kt
index 20904a0..cfe94c9 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/Message.kt
@@ -1,4 +1,4 @@
-package com.pixelized.desktop.lwa.repository.network.protocol
+package com.pixelized.server.lwa.protocol
import kotlinx.serialization.Serializable
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageContent.kt
similarity index 58%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt
rename to shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageContent.kt
index bcfbb30..6c59cb9 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageContent.kt
@@ -1,4 +1,4 @@
-package com.pixelized.desktop.lwa.repository.network.protocol
+package com.pixelized.server.lwa.protocol
import kotlinx.serialization.Serializable
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/RollMessage.kt
similarity index 79%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt
rename to shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/RollMessage.kt
index fddf0a8..55a76f4 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/RollMessage.kt
@@ -1,4 +1,4 @@
-package com.pixelized.desktop.lwa.repository.network.protocol
+package com.pixelized.server.lwa.protocol
import kotlinx.serialization.Serializable