Add server & shared module and remove the serveur from the client app.
| 
						 | 
					@ -1,9 +1,7 @@
 | 
				
			||||||
plugins {
 | 
					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.composeMultiplatform) apply false
 | 
				
			||||||
 | 
					    alias(libs.plugins.kotlinSerialization) apply false
 | 
				
			||||||
    alias(libs.plugins.composeCompiler) apply false
 | 
					    alias(libs.plugins.composeCompiler) apply false
 | 
				
			||||||
 | 
					    alias(libs.plugins.kotlinJvm) apply false
 | 
				
			||||||
 | 
					    alias(libs.plugins.kotlinMultiplatform) apply false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -29,16 +29,15 @@ kotlin {
 | 
				
			||||||
            api(libs.koin.core)
 | 
					            api(libs.koin.core)
 | 
				
			||||||
            implementation(libs.koin.compose)
 | 
					            implementation(libs.koin.compose)
 | 
				
			||||||
            implementation(libs.koin.compose.viewmodel)
 | 
					            implementation(libs.koin.compose.viewmodel)
 | 
				
			||||||
            // network.
 | 
					            // common
 | 
				
			||||||
 | 
					            implementation(projects.shared)
 | 
				
			||||||
 | 
					            // network
 | 
				
			||||||
            implementation(libs.kotlinx.serialization.json)
 | 
					            implementation(libs.kotlinx.serialization.json)
 | 
				
			||||||
            implementation(libs.ktor.client.core)
 | 
					            implementation(libs.ktor.client.core)
 | 
				
			||||||
            implementation(libs.ktor.client.cio)
 | 
					            implementation(libs.ktor.client.cio)
 | 
				
			||||||
            implementation(libs.ktor.client.websockets)
 | 
					            implementation(libs.ktor.client.websockets)
 | 
				
			||||||
            implementation(libs.ktor.server.core)
 | 
					 | 
				
			||||||
            implementation(libs.ktor.server.netty)
 | 
					 | 
				
			||||||
            implementation(libs.ktor.server.websockets)
 | 
					 | 
				
			||||||
            // shell
 | 
					            // shell
 | 
				
			||||||
            implementation(libs.lordcodes.turttle)
 | 
					            implementation(libs.turtle)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        commonTest.dependencies {
 | 
					        commonTest.dependencies {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,7 +136,6 @@
 | 
				
			||||||
    <string name="network__host__label">host</string>
 | 
					    <string name="network__host__label">host</string>
 | 
				
			||||||
    <string name="network__port__label">port</string>
 | 
					    <string name="network__port__label">port</string>
 | 
				
			||||||
    <string name="network__socket__connect_action">Se connecter à la table</string>
 | 
					    <string name="network__socket__connect_action">Se connecter à la table</string>
 | 
				
			||||||
    <string name="network__socket__host_action">Héberger la table</string>
 | 
					 | 
				
			||||||
    <string name="network__socket__disconnect_action">Se déconnecter</string>
 | 
					    <string name="network__socket__disconnect_action">Se déconnecter</string>
 | 
				
			||||||
    <string name="network__socket__status_state">État de connexion : %1$s</string>
 | 
					    <string name="network__socket__status_state">État de connexion : %1$s</string>
 | 
				
			||||||
    <string name="network__socket__status_connected">Connecté</string>
 | 
					    <string name="network__socket__status_connected">Connecté</string>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,17 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa.repository.network
 | 
					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.client
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
 | 
					import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.helper.server
 | 
					import com.pixelized.desktop.lwa.utils.extention.decodeFromFrame
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.protocol.Message
 | 
					import com.pixelized.desktop.lwa.utils.extention.encodeToFrame
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.protocol.MessageContent
 | 
					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.client.HttpClient
 | 
				
			||||||
import io.ktor.server.engine.EmbeddedServer
 | 
					 | 
				
			||||||
import io.ktor.server.netty.NettyApplicationEngine
 | 
					 | 
				
			||||||
import io.ktor.websocket.Frame
 | 
					import io.ktor.websocket.Frame
 | 
				
			||||||
import io.ktor.websocket.readText
 | 
					 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
import kotlinx.coroutines.Dispatchers
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.Job
 | 
					import kotlinx.coroutines.Job
 | 
				
			||||||
| 
						 | 
					@ -21,20 +23,17 @@ import kotlinx.coroutines.flow.StateFlow
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
import kotlinx.coroutines.runBlocking
 | 
					import kotlinx.coroutines.runBlocking
 | 
				
			||||||
import kotlinx.serialization.json.Json
 | 
					import kotlinx.serialization.json.Json
 | 
				
			||||||
import kotlinx.serialization.json.encodeToJsonElement
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
typealias Server = EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration>
 | 
					 | 
				
			||||||
typealias Client = HttpClient
 | 
					typealias Client = HttpClient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NetworkRepository {
 | 
					class NetworkRepository {
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        const val DEFAULT_PORT = 16030
 | 
					        const val DEFAULT_PORT = SERVER_PORT
 | 
				
			||||||
        const val DEFAULT_HOST = "pixelized.freeboxos.fr"
 | 
					        const val DEFAULT_HOST = "pixelized.freeboxos.fr"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val scope = CoroutineScope(Dispatchers.IO)
 | 
					    private val scope = CoroutineScope(Dispatchers.IO)
 | 
				
			||||||
    private var networkJob: Job? = null
 | 
					    private var networkJob: Job? = null
 | 
				
			||||||
    private var server: Server? = null
 | 
					 | 
				
			||||||
    private var client: Client? = null
 | 
					    private var client: Client? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val outgoingMessageBuffer = MutableSharedFlow<Message>()
 | 
					    private val outgoingMessageBuffer = MutableSharedFlow<Message>()
 | 
				
			||||||
| 
						 | 
					@ -47,65 +46,14 @@ class NetworkRepository {
 | 
				
			||||||
    private val _status = MutableStateFlow(Status.DISCONNECTED)
 | 
					    private val _status = MutableStateFlow(Status.DISCONNECTED)
 | 
				
			||||||
    val status: StateFlow<Status> get() = _status
 | 
					    val status: StateFlow<Status> get() = _status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _type = MutableStateFlow(Type.NONE)
 | 
					 | 
				
			||||||
    val type: StateFlow<Type> get() = _type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun onPlayerNameChange(player: String) {
 | 
					    fun onPlayerNameChange(player: String) {
 | 
				
			||||||
        _player.value = player
 | 
					        _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(
 | 
					    fun connect(
 | 
				
			||||||
        host: String,
 | 
					        host: String,
 | 
				
			||||||
        port: Int,
 | 
					        port: Int,
 | 
				
			||||||
        onConnect: (Type) -> Unit = { },
 | 
					        onConnect: () -> Unit = { },
 | 
				
			||||||
        onFailure: (Exception) -> Unit = { },
 | 
					        onFailure: (Exception) -> Unit = { },
 | 
				
			||||||
        onClose: () -> Unit = { },
 | 
					        onClose: () -> Unit = { },
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
| 
						 | 
					@ -115,9 +63,8 @@ class NetworkRepository {
 | 
				
			||||||
        networkJob = scope.launch {
 | 
					        networkJob = scope.launch {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                client?.connectWebSocket(host = host, port = port) {
 | 
					                client?.connectWebSocket(host = host, port = port) {
 | 
				
			||||||
                    _type.value = Type.CLIENT
 | 
					 | 
				
			||||||
                    _status.value = Status.CONNECTED
 | 
					                    _status.value = Status.CONNECTED
 | 
				
			||||||
                    onConnect(Type.CLIENT)
 | 
					                    onConnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    val job = launch {
 | 
					                    val job = launch {
 | 
				
			||||||
                        // send message to the server
 | 
					                        // send message to the server
 | 
				
			||||||
| 
						 | 
					@ -140,7 +87,6 @@ class NetworkRepository {
 | 
				
			||||||
            } catch (exception: Exception) {
 | 
					            } catch (exception: Exception) {
 | 
				
			||||||
                onFailure(exception)
 | 
					                onFailure(exception)
 | 
				
			||||||
            } finally {
 | 
					            } finally {
 | 
				
			||||||
                _type.value = Type.NONE
 | 
					 | 
				
			||||||
                _status.value = Status.DISCONNECTED
 | 
					                _status.value = Status.DISCONNECTED
 | 
				
			||||||
                onClose()
 | 
					                onClose()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -150,7 +96,6 @@ class NetworkRepository {
 | 
				
			||||||
    fun disconnect() {
 | 
					    fun disconnect() {
 | 
				
			||||||
        networkJob?.cancel()
 | 
					        networkJob?.cancel()
 | 
				
			||||||
        scope.launch {
 | 
					        scope.launch {
 | 
				
			||||||
            server?.stop()
 | 
					 | 
				
			||||||
            client?.close()
 | 
					            client?.close()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -165,10 +110,6 @@ class NetworkRepository {
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            // emit the message into the outgoing buffer
 | 
					            // emit the message into the outgoing buffer
 | 
				
			||||||
            outgoingMessageBuffer.emit(message)
 | 
					            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,
 | 
					        CONNECTED,
 | 
				
			||||||
        DISCONNECTED
 | 
					        DISCONNECTED
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    enum class Type {
 | 
					 | 
				
			||||||
        CLIENT,
 | 
					 | 
				
			||||||
        SERVER,
 | 
					 | 
				
			||||||
        NONE,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private fun Json.decodeFromFrame(frame: Frame.Text): Message {
 | 
					 | 
				
			||||||
    val json = frame.readText()
 | 
					 | 
				
			||||||
    return decodeFromString<Message>(json)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private fun Json.encodeToFrame(message: Message): Frame {
 | 
					 | 
				
			||||||
    val json = encodeToJsonElement(message)
 | 
					 | 
				
			||||||
    return Frame.Text(text = json.toString())
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
 | 
					 | 
				
			||||||
    return embeddedServer(
 | 
					 | 
				
			||||||
        factory = Netty,
 | 
					 | 
				
			||||||
        port = port,
 | 
					 | 
				
			||||||
        module = {
 | 
					 | 
				
			||||||
            install(WebSockets) {
 | 
					 | 
				
			||||||
                pingPeriod = 15.seconds
 | 
					 | 
				
			||||||
                timeout = 15.seconds
 | 
					 | 
				
			||||||
                maxFrameSize = Long.MAX_VALUE
 | 
					 | 
				
			||||||
                masking = false
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            routing {
 | 
					 | 
				
			||||||
                webSocket(
 | 
					 | 
				
			||||||
                    path = "/ws",
 | 
					 | 
				
			||||||
                    handler = handler,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa.repository.roll_history
 | 
					package com.pixelized.desktop.lwa.repository.roll_history
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
					import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.protocol.Message
 | 
					import com.pixelized.server.lwa.protocol.Message
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
 | 
					import com.pixelized.server.lwa.protocol.RollMessage
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
import kotlinx.coroutines.Dispatchers
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.flow.SharedFlow
 | 
					import kotlinx.coroutines.flow.SharedFlow
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,12 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa.screen.network
 | 
					package com.pixelized.desktop.lwa.screen.network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
 | 
					import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Type
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NetworkFactory {
 | 
					class NetworkFactory {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun convertToUio(
 | 
					    fun convertToUio(
 | 
				
			||||||
        player: String,
 | 
					        player: String,
 | 
				
			||||||
        status: Status,
 | 
					        status: Status,
 | 
				
			||||||
        type: Type,
 | 
					 | 
				
			||||||
        host: String,
 | 
					        host: String,
 | 
				
			||||||
        port: Int,
 | 
					        port: Int,
 | 
				
			||||||
    ): NetworkPageUio {
 | 
					    ): NetworkPageUio {
 | 
				
			||||||
| 
						 | 
					@ -18,7 +16,7 @@ class NetworkFactory {
 | 
				
			||||||
            port = "$port",
 | 
					            port = "$port",
 | 
				
			||||||
            enableFields = status == Status.DISCONNECTED,
 | 
					            enableFields = status == Status.DISCONNECTED,
 | 
				
			||||||
            enableActions = status == Status.DISCONNECTED && player.isNotBlank() && host.isNotBlank() && port > 0,
 | 
					            enableActions = status == Status.DISCONNECTED && player.isNotBlank() && host.isNotBlank() && port > 0,
 | 
				
			||||||
            enableCancel = type != Type.NONE && status == Status.CONNECTED
 | 
					            enableCancel = status == Status.CONNECTED
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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__port__label
 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
 | 
					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__disconnect_action
 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__host_action
 | 
					 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.network__title
 | 
					import lwacharactersheet.composeapp.generated.resources.network__title
 | 
				
			||||||
import org.jetbrains.compose.resources.stringResource
 | 
					import org.jetbrains.compose.resources.stringResource
 | 
				
			||||||
import org.koin.compose.viewmodel.koinViewModel
 | 
					import org.koin.compose.viewmodel.koinViewModel
 | 
				
			||||||
import org.koin.core.annotation.KoinExperimentalAPI
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Stable
 | 
					@Stable
 | 
				
			||||||
data class NetworkPageUio(
 | 
					data class NetworkPageUio(
 | 
				
			||||||
| 
						 | 
					@ -64,7 +62,6 @@ data class NetworkPageUio(
 | 
				
			||||||
    val enableCancel: Boolean,
 | 
					    val enableCancel: Boolean,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@OptIn(KoinExperimentalAPI::class)
 | 
					 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun NetworkPage(
 | 
					fun NetworkPage(
 | 
				
			||||||
    viewModel: NetworkViewModel = koinViewModel(),
 | 
					    viewModel: NetworkViewModel = koinViewModel(),
 | 
				
			||||||
| 
						 | 
					@ -91,7 +88,6 @@ fun NetworkPage(
 | 
				
			||||||
                    onHostChange = viewModel::onHostChange,
 | 
					                    onHostChange = viewModel::onHostChange,
 | 
				
			||||||
                    onPortChange = viewModel::onPortChange,
 | 
					                    onPortChange = viewModel::onPortChange,
 | 
				
			||||||
                    onConnect = viewModel::connect,
 | 
					                    onConnect = viewModel::connect,
 | 
				
			||||||
                    onHost = viewModel::host,
 | 
					 | 
				
			||||||
                    onDisconnect = viewModel::disconnect,
 | 
					                    onDisconnect = viewModel::disconnect,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -133,7 +129,6 @@ private fun NetworkContent(
 | 
				
			||||||
    onHostChange: (String) -> Unit,
 | 
					    onHostChange: (String) -> Unit,
 | 
				
			||||||
    onPortChange: (String) -> Unit,
 | 
					    onPortChange: (String) -> Unit,
 | 
				
			||||||
    onConnect: () -> Unit,
 | 
					    onConnect: () -> Unit,
 | 
				
			||||||
    onHost: () -> Unit,
 | 
					 | 
				
			||||||
    onDisconnect: () -> Unit,
 | 
					    onDisconnect: () -> Unit,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    Scaffold(
 | 
					    Scaffold(
 | 
				
			||||||
| 
						 | 
					@ -207,13 +202,6 @@ private fun NetworkContent(
 | 
				
			||||||
                    Text(text = stringResource(Res.string.network__socket__connect_action))
 | 
					                    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(
 | 
					                TextButton(
 | 
				
			||||||
                    enabled = player.value.enableCancel,
 | 
					                    enabled = player.value.enableCancel,
 | 
				
			||||||
                    onClick = onDisconnect,
 | 
					                    onClick = onDisconnect,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,6 @@ class NetworkViewModel(
 | 
				
			||||||
    private val repository: NetworkRepository,
 | 
					    private val repository: NetworkRepository,
 | 
				
			||||||
    private val factory: NetworkFactory
 | 
					    private val factory: NetworkFactory
 | 
				
			||||||
) : ViewModel() {
 | 
					) : ViewModel() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
 | 
					    private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
 | 
				
			||||||
    private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
 | 
					    private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,13 +40,11 @@ class NetworkViewModel(
 | 
				
			||||||
        get() {
 | 
					        get() {
 | 
				
			||||||
            val player = repository.player.collectAsState()
 | 
					            val player = repository.player.collectAsState()
 | 
				
			||||||
            val status = repository.status.collectAsState()
 | 
					            val status = repository.status.collectAsState()
 | 
				
			||||||
            val type = repository.type.collectAsState()
 | 
					 | 
				
			||||||
            return remember {
 | 
					            return remember {
 | 
				
			||||||
                derivedStateOf {
 | 
					                derivedStateOf {
 | 
				
			||||||
                    factory.convertToUio(
 | 
					                    factory.convertToUio(
 | 
				
			||||||
                        player = player.value,
 | 
					                        player = player.value,
 | 
				
			||||||
                        status = status.value,
 | 
					                        status = status.value,
 | 
				
			||||||
                        type = type.value,
 | 
					 | 
				
			||||||
                        host = host.value,
 | 
					                        host = host.value,
 | 
				
			||||||
                        port = port.value,
 | 
					                        port = port.value,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
| 
						 | 
					@ -67,10 +64,6 @@ class NetworkViewModel(
 | 
				
			||||||
        this.host.value = host
 | 
					        this.host.value = host
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun host() {
 | 
					 | 
				
			||||||
        repository.host(port = port.value)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun connect() {
 | 
					    fun connect() {
 | 
				
			||||||
        controller.show()
 | 
					        controller.show()
 | 
				
			||||||
        _isLoading.value = true
 | 
					        _isLoading.value = true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
import androidx.lifecycle.viewModelScope
 | 
					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 com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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<Message>(json)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Json.encodeToFrame(message: Message): Frame {
 | 
				
			||||||
 | 
					    val json = encodeToJsonElement(message)
 | 
				
			||||||
 | 
					    return Frame.Text(text = json.toString())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,4 +3,7 @@ kotlin.code.style=official
 | 
				
			||||||
kotlin.daemon.jvmargs=-Xmx2048M
 | 
					kotlin.daemon.jvmargs=-Xmx2048M
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Gradle
 | 
					#Gradle
 | 
				
			||||||
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
 | 
					org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#Ktor
 | 
				
			||||||
 | 
					io.ktor.development=true
 | 
				
			||||||
| 
						 | 
					@ -5,14 +5,19 @@ kotlinx-json = "1.7.3"
 | 
				
			||||||
compose-multiplatform = "1.7.0"
 | 
					compose-multiplatform = "1.7.0"
 | 
				
			||||||
androidx-lifecycle = "2.8.3"
 | 
					androidx-lifecycle = "2.8.3"
 | 
				
			||||||
androidx-navigation = "2.8.0-alpha10"
 | 
					androidx-navigation = "2.8.0-alpha10"
 | 
				
			||||||
ktor_version = "3.0.0"
 | 
					ktor = "3.0.1"
 | 
				
			||||||
koin = "4.0.0"
 | 
					koin = "4.0.0"
 | 
				
			||||||
 | 
					turtle = "0.5.0"
 | 
				
			||||||
 | 
					logback = "1.5.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[plugins]
 | 
					[plugins]
 | 
				
			||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
 | 
					composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
 | 
				
			||||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
 | 
					composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
 | 
				
			||||||
 | 
					kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
				
			||||||
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
 | 
					kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
 | 
				
			||||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
 | 
					kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
 | 
				
			||||||
 | 
					ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[libraries]
 | 
					[libraries]
 | 
				
			||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
 | 
					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 = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
 | 
				
			||||||
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", 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-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
 | 
				
			||||||
ktor-client-cio = { group = 'io.ktor', name = "ktor-client-cio", version.ref = "ktor_version" }
 | 
					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_version" }
 | 
					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_version" }
 | 
					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_version" }
 | 
					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_version" }
 | 
					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"}
 | 
					turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" }
 | 
				
			||||||
 | 
					logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
 | 
				
			||||||
							
								
								
									
										1
									
								
								server/.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
/build
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,44 +1,23 @@
 | 
				
			||||||
plugins {
 | 
					plugins {
 | 
				
			||||||
    alias(libs.plugins.androidApplication)
 | 
					    alias(libs.plugins.kotlinJvm)
 | 
				
			||||||
    alias(libs.plugins.kotlinAndroid)
 | 
					    alias(libs.plugins.ktor)
 | 
				
			||||||
 | 
					    alias(libs.plugins.kotlinSerialization)
 | 
				
			||||||
 | 
					    application
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
android {
 | 
					group = "com.pixelized.server.lwa"
 | 
				
			||||||
    namespace = "com.pixelized.server"
 | 
					version = "1.0.0"
 | 
				
			||||||
    compileSdk = 34
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    defaultConfig {
 | 
					application {
 | 
				
			||||||
        applicationId = "com.pixelized.server"
 | 
					    mainClass.set("com.pixelized.server.lwa.ApplicationKt")
 | 
				
			||||||
        minSdk = 24
 | 
					    applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["io.ktor.development"] ?: "false"}")
 | 
				
			||||||
        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"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
 | 
					    implementation(projects.shared)
 | 
				
			||||||
    implementation(libs.appcompat.v7)
 | 
					    implementation(libs.kotlinx.serialization.json)
 | 
				
			||||||
    testImplementation(libs.junit)
 | 
					    implementation(libs.logback)
 | 
				
			||||||
    androidTestImplementation(libs.runner)
 | 
					    implementation(libs.ktor.server.core)
 | 
				
			||||||
    androidTestImplementation(libs.espresso.core)
 | 
					    implementation(libs.ktor.server.netty)
 | 
				
			||||||
 | 
					    implementation(libs.ktor.server.websockets)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								server/proguard-rules.pro
									
										
									
									
										vendored
									
									
								
							
							
						
						| 
						 | 
					@ -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
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <application
 | 
					 | 
				
			||||||
        android:allowBackup="true"
 | 
					 | 
				
			||||||
        android:icon="@mipmap/ic_launcher"
 | 
					 | 
				
			||||||
        android:label="@string/app_name"
 | 
					 | 
				
			||||||
        android:roundIcon="@mipmap/ic_launcher_round"
 | 
					 | 
				
			||||||
        android:supportsRtl="true"
 | 
					 | 
				
			||||||
        android:theme="@style/Theme.LwaCharacterSheet" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</manifest>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					package com.pixelized.server.lwa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.pixelized.server.lwa.server.LocalServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun main() {
 | 
				
			||||||
 | 
					    LocalServer().create().start()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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<Message>(json)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Json.encodeToFrame(message: Message): Frame {
 | 
				
			||||||
 | 
					    val json = encodeToJsonElement(message)
 | 
				
			||||||
 | 
					    return Frame.Text(text = json.toString())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										101
									
								
								server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -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<NettyApplicationEngine, NettyApplicationEngine.Configuration>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LocalServer {
 | 
				
			||||||
 | 
					    private var server: Server? = null
 | 
				
			||||||
 | 
					    private val scope = CoroutineScope(Dispatchers.IO)
 | 
				
			||||||
 | 
					    private var networkJob: Job? = null
 | 
				
			||||||
 | 
					    private val outgoingMessageBuffer = MutableSharedFlow<Frame>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
 | 
				
			||||||
 | 
					        return embeddedServer(
 | 
				
			||||||
 | 
					            factory = Netty,
 | 
				
			||||||
 | 
					            port = port,
 | 
				
			||||||
 | 
					            module = {
 | 
				
			||||||
 | 
					                install(WebSockets) {
 | 
				
			||||||
 | 
					                    pingPeriod = 15.seconds
 | 
				
			||||||
 | 
					                    timeout = 15.seconds
 | 
				
			||||||
 | 
					                    maxFrameSize = Long.MAX_VALUE
 | 
				
			||||||
 | 
					                    masking = false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                routing {
 | 
				
			||||||
 | 
					                    webSocket(
 | 
				
			||||||
 | 
					                        path = "/ws",
 | 
				
			||||||
 | 
					                        handler = handler,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,170 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    android:width="108dp"
 | 
					 | 
				
			||||||
    android:height="108dp"
 | 
					 | 
				
			||||||
    android:viewportWidth="108"
 | 
					 | 
				
			||||||
    android:viewportHeight="108">
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#3DDC84"
 | 
					 | 
				
			||||||
        android:pathData="M0,0h108v108h-108z" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M9,0L9,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,0L19,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M29,0L29,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M39,0L39,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M49,0L49,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M59,0L59,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M69,0L69,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M79,0L79,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M89,0L89,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M99,0L99,108"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,9L108,9"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,19L108,19"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,29L108,29"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,39L108,39"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,49L108,49"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,59L108,59"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,69L108,69"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,79L108,79"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,89L108,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M0,99L108,99"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,29L89,29"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,39L89,39"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,49L89,49"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,59L89,59"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,69L89,69"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M19,79L89,79"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M29,19L29,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M39,19L39,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M49,19L49,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M59,19L59,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M69,19L69,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#00000000"
 | 
					 | 
				
			||||||
        android:pathData="M79,19L79,89"
 | 
					 | 
				
			||||||
        android:strokeWidth="0.8"
 | 
					 | 
				
			||||||
        android:strokeColor="#33FFFFFF" />
 | 
					 | 
				
			||||||
</vector>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,30 +0,0 @@
 | 
				
			||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					 | 
				
			||||||
    xmlns:aapt="http://schemas.android.com/aapt"
 | 
					 | 
				
			||||||
    android:width="108dp"
 | 
					 | 
				
			||||||
    android:height="108dp"
 | 
					 | 
				
			||||||
    android:viewportWidth="108"
 | 
					 | 
				
			||||||
    android:viewportHeight="108">
 | 
					 | 
				
			||||||
    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
 | 
					 | 
				
			||||||
        <aapt:attr name="android:fillColor">
 | 
					 | 
				
			||||||
            <gradient
 | 
					 | 
				
			||||||
                android:endX="85.84757"
 | 
					 | 
				
			||||||
                android:endY="92.4963"
 | 
					 | 
				
			||||||
                android:startX="42.9492"
 | 
					 | 
				
			||||||
                android:startY="49.59793"
 | 
					 | 
				
			||||||
                android:type="linear">
 | 
					 | 
				
			||||||
                <item
 | 
					 | 
				
			||||||
                    android:color="#44000000"
 | 
					 | 
				
			||||||
                    android:offset="0.0" />
 | 
					 | 
				
			||||||
                <item
 | 
					 | 
				
			||||||
                    android:color="#00000000"
 | 
					 | 
				
			||||||
                    android:offset="1.0" />
 | 
					 | 
				
			||||||
            </gradient>
 | 
					 | 
				
			||||||
        </aapt:attr>
 | 
					 | 
				
			||||||
    </path>
 | 
					 | 
				
			||||||
    <path
 | 
					 | 
				
			||||||
        android:fillColor="#FFFFFF"
 | 
					 | 
				
			||||||
        android:fillType="nonZero"
 | 
					 | 
				
			||||||
        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
 | 
					 | 
				
			||||||
        android:strokeWidth="1"
 | 
					 | 
				
			||||||
        android:strokeColor="#00000000" />
 | 
					 | 
				
			||||||
</vector>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
					 | 
				
			||||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
					 | 
				
			||||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
					 | 
				
			||||||
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
 | 
					 | 
				
			||||||
</adaptive-icon>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
 | 
					 | 
				
			||||||
    <background android:drawable="@drawable/ic_launcher_background" />
 | 
					 | 
				
			||||||
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
 | 
					 | 
				
			||||||
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
 | 
					 | 
				
			||||||
</adaptive-icon>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 1.4 KiB  | 
| 
		 Before Width: | Height: | Size: 2.8 KiB  | 
| 
		 Before Width: | Height: | Size: 982 B  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB  | 
| 
		 Before Width: | Height: | Size: 1.9 KiB  | 
| 
		 Before Width: | Height: | Size: 3.8 KiB  | 
| 
		 Before Width: | Height: | Size: 2.8 KiB  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB  | 
| 
		 Before Width: | Height: | Size: 3.8 KiB  | 
| 
		 Before Width: | Height: | Size: 7.6 KiB  | 
| 
						 | 
					@ -1,10 +0,0 @@
 | 
				
			||||||
<resources xmlns:tools="http://schemas.android.com/tools">
 | 
					 | 
				
			||||||
    <!-- Base application theme. -->
 | 
					 | 
				
			||||||
    <style name="Theme.LwaCharacterSheet" parent="Theme.AppCompat.Light.DarkActionBar">
 | 
					 | 
				
			||||||
        <!-- Primary brand color. -->
 | 
					 | 
				
			||||||
        <item name="colorPrimary">@color/purple_200</item>
 | 
					 | 
				
			||||||
        <item name="colorPrimaryDark">@color/purple_700</item>
 | 
					 | 
				
			||||||
        <item name="colorAccent">@color/teal_200</item>
 | 
					 | 
				
			||||||
        <!-- Customize your theme here. -->
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
</resources>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,10 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					 | 
				
			||||||
<resources>
 | 
					 | 
				
			||||||
    <color name="purple_200">#FFBB86FC</color>
 | 
					 | 
				
			||||||
    <color name="purple_500">#FF6200EE</color>
 | 
					 | 
				
			||||||
    <color name="purple_700">#FF3700B3</color>
 | 
					 | 
				
			||||||
    <color name="teal_200">#FF03DAC5</color>
 | 
					 | 
				
			||||||
    <color name="teal_700">#FF018786</color>
 | 
					 | 
				
			||||||
    <color name="black">#FF000000</color>
 | 
					 | 
				
			||||||
    <color name="white">#FFFFFFFF</color>
 | 
					 | 
				
			||||||
</resources>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
<resources>
 | 
					 | 
				
			||||||
    <string name="app_name">server</string>
 | 
					 | 
				
			||||||
</resources>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,10 +0,0 @@
 | 
				
			||||||
<resources xmlns:tools="http://schemas.android.com/tools">
 | 
					 | 
				
			||||||
    <!-- Base application theme. -->
 | 
					 | 
				
			||||||
    <style name="Theme.LwaCharacterSheet" parent="Theme.AppCompat.Light.DarkActionBar">
 | 
					 | 
				
			||||||
        <!-- Primary brand color. -->
 | 
					 | 
				
			||||||
        <item name="colorPrimary">@color/purple_500</item>
 | 
					 | 
				
			||||||
        <item name="colorPrimaryDark">@color/purple_700</item>
 | 
					 | 
				
			||||||
        <item name="colorAccent">@color/teal_200</item>
 | 
					 | 
				
			||||||
        <!-- Customize your theme here. -->
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
</resources>
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								server/src/main/resources/logback.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					<configuration>
 | 
				
			||||||
 | 
					    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 | 
				
			||||||
 | 
					        <encoder>
 | 
				
			||||||
 | 
					            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
 | 
				
			||||||
 | 
					        </encoder>
 | 
				
			||||||
 | 
					    </appender>
 | 
				
			||||||
 | 
					    <root level="trace">
 | 
				
			||||||
 | 
					        <appender-ref ref="STDOUT"/>
 | 
				
			||||||
 | 
					    </root>
 | 
				
			||||||
 | 
					    <logger name="org.eclipse.jetty" level="INFO"/>
 | 
				
			||||||
 | 
					    <logger name="io.netty" level="INFO"/>
 | 
				
			||||||
 | 
					</configuration>
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -29,3 +29,5 @@ dependencyResolutionManagement {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include(":composeApp")
 | 
					include(":composeApp")
 | 
				
			||||||
 | 
					include(":server")
 | 
				
			||||||
 | 
					include(":shared")
 | 
				
			||||||
							
								
								
									
										15
									
								
								shared/build.gradle.kts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					plugins {
 | 
				
			||||||
 | 
					    alias(libs.plugins.kotlinMultiplatform)
 | 
				
			||||||
 | 
					    alias(libs.plugins.kotlinSerialization)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kotlin {
 | 
				
			||||||
 | 
					    jvm()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    sourceSets {
 | 
				
			||||||
 | 
					        commonMain.dependencies {
 | 
				
			||||||
 | 
					            implementation(libs.kotlinx.serialization.json)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					package com.pixelized.server.lwa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const val SERVER_PORT = 16030
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa.repository.network.protocol
 | 
					package com.pixelized.server.lwa.protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.serialization.Serializable
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa.repository.network.protocol
 | 
					package com.pixelized.server.lwa.protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.serialization.Serializable
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa.repository.network.protocol
 | 
					package com.pixelized.server.lwa.protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.serialization.Serializable
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||