From 3419afbe590f2ac76c6bd6bec11e794eb7b484e4 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Fri, 29 Nov 2024 18:19:54 +0100 Subject: [PATCH] Add server & shared module and remove the serveur from the client app. --- build.gradle.kts | 8 +- composeApp/build.gradle.kts | 9 +- .../composeResources/values/strings.xml | 1 - .../repository/network/NetworkRepository.kt | 97 ++-------- .../lwa/repository/network/helper/Server.kt | 39 ---- .../roll_history/RollHistoryRepository.kt | 4 +- .../lwa/screen/network/NetworkFactory.kt | 4 +- .../desktop/lwa/screen/network/NetworkPage.kt | 12 -- .../lwa/screen/network/NetworkViewModel.kt | 7 - .../rollhistory/RollHistoryViewModel.kt | 2 +- .../desktop/lwa/utils/extention/JsonExt.kt | 17 ++ gradle.properties | 5 +- gradle/libs.versions.toml | 22 ++- server/.gitignore | 1 - server/build.gradle.kts | 51 ++---- server/proguard-rules.pro | 21 --- .../server/ExampleInstrumentedTest.kt | 24 --- server/src/main/AndroidManifest.xml | 12 -- .../com/pixelized/server/lwa/Application.kt | 7 + .../pixelized/server/lwa/extention/JsonExt.kt | 17 ++ .../com/pixelized/server/lwa/server/Server.kt | 101 +++++++++++ .../res/drawable/ic_launcher_background.xml | 170 ------------------ .../res/drawable/ic_launcher_foreground.xml | 30 ---- .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 - .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1404 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 2898 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 982 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1772 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 1900 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 3918 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 2884 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 5914 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 3844 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 7778 -> 0 bytes server/src/main/res/values-night/themes.xml | 10 -- server/src/main/res/values/colors.xml | 10 -- server/src/main/res/values/strings.xml | 3 - server/src/main/res/values/themes.xml | 10 -- server/src/main/resources/logback.xml | 12 ++ .../com/pixelized/server/ExampleUnitTest.kt | 17 -- settings.gradle.kts | 4 +- shared/build.gradle.kts | 15 ++ .../com/pixelized/server/lwa/Constants.kt | 3 + .../pixelized/server/lwa}/protocol/Message.kt | 2 +- .../server/lwa}/protocol/MessageContent.kt | 2 +- .../server/lwa}/protocol/RollMessage.kt | 2 +- 47 files changed, 233 insertions(+), 530 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Server.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/JsonExt.kt delete mode 100644 server/.gitignore delete mode 100644 server/proguard-rules.pro delete mode 100644 server/src/androidTest/java/com/pixelized/server/ExampleInstrumentedTest.kt delete mode 100644 server/src/main/AndroidManifest.xml create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/Application.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt delete mode 100644 server/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 server/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 server/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 server/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 server/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 server/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 server/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 server/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 server/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 server/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 server/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 server/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 server/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 server/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp delete mode 100644 server/src/main/res/values-night/themes.xml delete mode 100644 server/src/main/res/values/colors.xml delete mode 100644 server/src/main/res/values/strings.xml delete mode 100644 server/src/main/res/values/themes.xml create mode 100644 server/src/main/resources/logback.xml delete mode 100644 server/src/test/java/com/pixelized/server/ExampleUnitTest.kt create mode 100644 shared/build.gradle.kts create mode 100644 shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network => shared/src/commonMain/kotlin/com/pixelized/server/lwa}/protocol/Message.kt (68%) rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network => shared/src/commonMain/kotlin/com/pixelized/server/lwa}/protocol/MessageContent.kt (58%) rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network => shared/src/commonMain/kotlin/com/pixelized/server/lwa}/protocol/RollMessage.kt (79%) 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 c209e78ecd372343283f4157dcfd918ec5165bb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1404 zcmWIYbaN|VWnc(*bqWXzu!!JdU|xt`5!v_jjvkEam13D%HG@k-IMc zNLB6Ts|;5bzSwJe>ULbl1cvf&ZkhaCoSq2>@AZ5-D>jj1lIT2H&dag$gn7N+e4L{u z^V=qU9`n4<|KGj+Jg1Vc;+*Ym8}@ms^PajGxhz`fUcGqB^ot%&0-(G6 ztJqa}H}n3HU3Yip`SGVeoN_0jHpk{$^4G>a6%8p%x7&Z)J5^*02Bzg(emT zh6~~ha~ZWf7(5v43`IJe7AY!6)06s|5LTRZ~a|9(#3-?A(9e&YZ4?>X2s?GW>o|33SDelz`B z`+wn|Q;!w?=CKR)$n9qK&JB=|i?B8=Za(_yby{8ahEC_FQc?|fX6jq-S#{;T*P>@x z_ZT?env#-TMm+56J zYxbSv3`wunSg8L0QM8}=O*rp?-VOhM|NEg<)7%*xHvh-siA*kcCjMS>GB?P(>_@}3 z)Ft&xoDRNt&3eFh)jsaGt6V1Y-utsGX3D{BPrGiMe#bFC=cA;8JMEoLi`oHXxI)DE^oHMbkaEEw% z-9tKtyZ;ro&wXg4x@O8`m1O~q z?MexGg12&nX4y|S@pnFV^V}u9giQ-GT$3$KnU6R18RkeC3%K&!v-YvBIofAmw>PFs zIdS)#VNole5AMDC;{Wfgucb?`mgwZOH*=)K)xFxh zvAbsbOYcV^yis0yb^(2L-iLG^EU>@ZcYlA->$iWR*2l$qNu~2Y6n&DlA@j?-eeQ>s z$tnK&Z05FhmCbjTD+dy9O%PYyrL7Vlbht#oNZ2QS;ASZ@?QSi@KIA`)Q{Plar-EBMn zOue{8S#RsoI9{I8-1%wjYL3ra-WXN>myuB|k`TLaRw0(}Q{v*)neU%|Uf=%kE$@XZ zhG9pKE&f~)a@RGze*OGS1~2BsylnCL*)X3i|99J|eC4QK#}-~?>BH&Aafd zWjZOh{NVc@|Mx8WeQ&M$PqIskKh9jN|JQv<&;0)q`yZ~E&-DGzd;15MW#|73p4FDw z7Oym+LiWGhsn`4Zdv7t_;1XiVJF+=y-{rn5k2YR9d}BwU-%Zx7^dl3uQO%=KtB6@_8OJ`?s99PuHc(Wv{Uvn57cQnzH^! z4YT&W4>v_49d0g>;5}1QsqLw*bguIK-eRRko_V#EY72aobT&L-pQO|$YAra)UWXy) z?H0HH>?%h$pNjLSyTHFTTZD1@o5kYirK&#Ospl5Bs^WBcdditu$Df4zc4P@8o+&!k z$5~=Hz27y&v^)8mahg-|JA1KhUV`UTT02EQ$+(1e7MN)IY?yFdc~#TV8Gl98qIT3X z<+w`qp4Rl)QQ*p5k{WN6_9(e-vDU^NGYy%0D%Eo$*=nm_Iu+cp7Fo9EyM*zH8S<-( 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 AkN^Mx 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 948a3070fe34c611c42c0d3ad3013a0dce358be0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1900 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

( 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 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 9287f5083623b375139afb391af71cc533a7dd37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5914 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

0vl^(ubK57i3$j`RXVdItc2Zu zTjP%3T{qqDyHu$1wf>en`}f$lI?l)hfy+`aCTxu8`Mp4Wt8~lzsk7fIKUzdbU&D@u>@#|8JoKt2mbv8|~*&2E%ct_*EIJ>)7o&PKE5_$1H zYTdgu^~m!0A4{%YZ`c&qZ)$yr|AXm!sqXjcKR?_Pn|Ao^jNS9)1$Q|wns@3F%fsvg zwSO(SHf}Wd&gY@5dZETG=E3!B4m$1-}j)^|n6tq;mOd3k8c0DH_GHdj+bC#}?wC!#j7mQS#vf_fXiT$x+$0-Ud(~5MTubmb>WqqsT;k{xpsIQhc5wmzv^leMXZ>rUpkvod!E z%4X!{CT@DfUb}1l9;OF%&cZj$=~`?izExW$`!W#MmrU&bU9x zY4?Y|ihvD`aVe?!Jd*bn>a_Ljw%A+cxQM63#5tgD>7pvIfk zXZB`A(+B?|xK;mhgAxy6j8m5a|tYYQtz-&)`wciH>w!z4Jl6EbdLNgW&t4n&Z^`r` zg&vWF3I>a?I;mM9pQdx?h+g`4^#9q@#m5&cUv`wUW67(N(f8cqnde#^*{|z4`#8&% z$(*MTuC#KwaPVof`>D`&U$yg@H74@YWB+XNzOk9HzHI)?S*96(9jadGnl+RhV?HYU zTPymOU!2^Y9Y;SYsn1QEpStG70rM^O4*D71J+(bX{#n9KLDg&XW+cq#d%E33PUWv_ zkfOTshO5hBt#1C8*W^yL{vnXQVRqiLErQMrj6U1<_5@j|Em*A4E4-te^ZzpYDI0B` zXg;x>w9{g(u&mK%{^Fz8n{xBlOZuI;!E!86S+BwIO2Xl&Fh%#ZoBUQU_O0g4%ANk> z0Jmq=?VG;}VuQcWs7ieBJAdXY(fhi`7ADvReRQ>AUivA=PwM}&`iLJJJJZ{_&k3Y2 zUvhnu->Tx7bEg!!9%6moQp;o7lU20XJ=QDEK<4}Dx`6F-nzAlROmUdnwpX%JBgyHE&EJd_^1F8lZE|Tb`4Z9o=_5<>!KzQ} zGZoDj*B>l-_{cMi-TF`Qmi1X-=QLK8d3<{luV|^Id8CphQgh85osBxngzXY9@84_h z%f`4XHqy%Sk8jJOIRCXp`#HYZ9tjb8r7sFp_47S~Y8LMZ0|ACD*T9*?vNtsq>c#FZ;4AF83!N-}7Zh7R+l= zw!4tMaFcX!P3Els8SK9LLhRyJK z+&t%0aQM50`k&8S%~Uwp?rm21|LFNWlV+G(ACydR-+6qUWz*kv3iZ{C%If{&>euW) z@@W5$w|~FeitEW*v$L+4y5z_58%Bq(G3{FX-GAnci|2Se?q;?>GnLW&F(+=#|9Z#w z-#_o;-&VrrqOwwO%B-^WHLF&}EjZj>w|%nOHqFIdHlY~LM|@kt^cB?v?aRN!JSJaHjLeW zb$GNz$NM?U8z(oNo3`r8e%lS3CEre$zVUyZ&{wym*PqV*QMTjIf~G4kJUQ0C+9_cC zLSfq{(=7&@u0G04(^)DtZ};(To{cq(j|-+htN*jEpuxFk0?Ru8XP3^z-&SNkURt^2 zTSfZcN5x^>ML|ky1rH}oViJ7+@cetDleV!hBksSwEn~4q@Xz`GmCrjHOK)GEd0p-B z(Y6SS8poDMuE(=E|4rGmcvp9MsGHU9V8)Ek>HE*irk%F9rK7RU-2N}yiz$uUO57Qx zXRAv!tdNVQd!7^~xNg;>=ojmFGC8tun)m$oP3PKhc*onS@K5?rr^}yu zUmUrRUESJJ=>F0}m;TMP>i;SjwwR~)_nQ*mc_z*-WwSXs6&fs_bqPz9s|2|0xg0$2 t@u$Pda=rYAtrOikX7%&GRsG&;Z1Lh{&yL;Ao6ROYUN$pxVgmyL003>zdZqvX 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 9126ae37cbc3587421d6889eadd1d91fbf1994d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7778 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 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