diff --git a/build.gradle.kts b/build.gradle.kts index c255cba..691b562 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { 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.kotlinSerialization) 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 81a0884..820bc2e 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,10 +1,10 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { + alias(libs.plugins.composeCompiler) + alias(libs.plugins.composeMultiplatform) alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.composeMultiplatform) - alias(libs.plugins.composeCompiler) } kotlin { diff --git a/composeApp/compose-desktop.pro b/composeApp/compose-desktop.pro index 7f9ed44..13f0767 100644 --- a/composeApp/compose-desktop.pro +++ b/composeApp/compose-desktop.pro @@ -14,4 +14,7 @@ # Serialization -keep class io.ktor.serialization.kotlinx.json.** { *; } --keep class com.pixelized.shared.lwa.model.** { *; } \ No newline at end of file +-keep class com.pixelized.shared.lwa.model.** { *; } +-keep class com.pixelized.shared.lwa.protocol.rest.** { *; } +-keep class com.pixelized.desktop.lwa.repository.settings.model.** { *; } +-keep @kotlinx.serialization.Serializable class * { *; } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index 02ea6c6..7d63c8f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -29,8 +29,11 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.Window +import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers +import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination import com.pixelized.desktop.lwa.ui.navigation.window.WindowController @@ -39,12 +42,8 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheet import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight -import com.pixelized.desktop.lwa.repository.network.NetworkRepository -import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status -import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost -import com.pixelized.desktop.lwa.ui.screen.campaign.player.CampaignScreen +import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignScreen import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost -import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage import com.pixelized.desktop.lwa.ui.theme.LwaTheme import kotlinx.coroutines.launch @@ -54,7 +53,6 @@ import lwacharactersheet.composeapp.generated.resources.network__disconnect__mes import org.jetbrains.compose.resources.getString import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.koinInject -import org.koin.compose.viewmodel.koinViewModel val LocalWindowController = compositionLocalOf { error("Local Window Controller is not yet ready") @@ -127,8 +125,8 @@ fun ApplicationScope.App() { } }, content = { - MainNavHost() -// CampaignScreen() +// MainNavHost() + CampaignScreen() } ) NetworkSnackHandler( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt index f9eb8ea..e183690 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -4,6 +4,8 @@ import com.pixelized.desktop.lwa.business.ExpressionUseCase import com.pixelized.desktop.lwa.business.RollUseCase import com.pixelized.desktop.lwa.business.SettingsUseCase import com.pixelized.desktop.lwa.business.SkillStepUseCase +import com.pixelized.desktop.lwa.network.LwaClient +import com.pixelized.desktop.lwa.network.LwaClientImpl import com.pixelized.desktop.lwa.parser.dice.DiceParser import com.pixelized.desktop.lwa.parser.expression.ExpressionParser import com.pixelized.desktop.lwa.parser.word.WordParser @@ -20,6 +22,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsStore import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetFactory import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetViewModel @@ -72,6 +75,7 @@ val toolsDependencies } } } + single { LwaClientImpl(get(), get()) } } val storeDependencies @@ -100,6 +104,7 @@ val factoryDependencies factoryOf(::SkillFieldFactory) factoryOf(::SettingsFactory) factoryOf(::CampaignJsonFactory) + factoryOf(::PlayerRibbonFactory) } val viewModelDependencies diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SettingsUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SettingsUseCase.kt index ead140d..219a68e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SettingsUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SettingsUseCase.kt @@ -5,6 +5,13 @@ import com.pixelized.desktop.lwa.repository.settings.model.Settings class SettingsUseCase { fun defaultSettings(): Settings = Settings( + host = DEFAULT_HOST, + port = DEFAULT_PORT, playerName = "", ) + + companion object { + private const val DEFAULT_HOST = "http://pixelized.freeboxos.fr" + private const val DEFAULT_PORT = 16030 + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt new file mode 100644 index 0000000..fdc2354 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt @@ -0,0 +1,26 @@ +package com.pixelized.desktop.lwa.network + +import com.pixelized.shared.lwa.model.campaign.CampaignJson +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson +import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson + +interface LwaClient { + + suspend fun characters(): List + + suspend fun character(id: String): CharacterSheetJson + + suspend fun updateCharacter(sheet: CharacterSheetJson) + + suspend fun deleteCharacter(id: String) + + suspend fun campaign(): CampaignJson + + suspend fun campaignAddCharacter(id: String) + + suspend fun campaignDeleteCharacter(id: String) + + suspend fun campaignAddNpc(id: String) + + suspend fun campaignDeleteNpc(id: String) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt new file mode 100644 index 0000000..5c52c57 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt @@ -0,0 +1,64 @@ +package com.pixelized.desktop.lwa.network + +import com.pixelized.desktop.lwa.repository.settings.SettingsRepository +import com.pixelized.shared.lwa.model.campaign.CampaignJson +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson +import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.delete +import io.ktor.client.request.get +import io.ktor.client.request.put +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.contentType +import io.ktor.http.path + +class LwaClientImpl( + private val client: HttpClient, + setting: SettingsRepository, +) : LwaClient { + private val root = setting.settings().root + + override suspend fun characters(): List = client + .get("$root/characters") + .body() + + override suspend fun character(id: String): CharacterSheetJson = client + .get("$root/character/detail?id=$id") + .body() + + override suspend fun updateCharacter(sheet: CharacterSheetJson) = client + .put { + url { + path("$host/character/update?id=") + contentType(ContentType.Application.Json) + setBody(sheet) + } + } + .body() + + override suspend fun deleteCharacter(id: String) = client + .delete("$root/character/delete?id=$id") + .body() + + override suspend fun campaign(): CampaignJson = client + .get("$root/campaign") + .body() + + override suspend fun campaignAddCharacter(id: String) = client + .put("$root/campaign/character/update?id=$id") + .body() + + override suspend fun campaignDeleteCharacter(id: String) = client + .delete("$root/campaign/character/delete?id=$id") + .body() + + override suspend fun campaignAddNpc(id: String) = client + .put("$root/campaign/npc/update?id=$id") + .body() + + override suspend fun campaignDeleteNpc(id: String) = client + .delete("$root/campaign/npc/delete?id=$id") + .body() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt index e80d6fb..2168f5d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt @@ -1,6 +1,7 @@ package com.pixelized.desktop.lwa.repository.campaign import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.character import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -10,31 +11,21 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn class CampaignRepository( - store: CampaignStore, + private val store: CampaignStore, ) { private val scope = CoroutineScope(Dispatchers.IO + Job()) - private val campaign = store.campaignFlow() - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = Campaign.EMPTY, - ) + val campaignFlow get() = store.campaignFlow - fun campaignFlow(): StateFlow = campaign - - fun characterInstance(id: String): StateFlow { - return campaign + fun characterInstanceFlow(id: String): StateFlow { + return campaignFlow .mapNotNull { it.characters[id] } .stateIn( scope = scope, started = SharingStarted.Eagerly, - initialValue = campaign.value.characters[id] ?: Campaign.CharacterInstance( - characteristic = emptyMap(), - usedSkill = emptyList(), - ) + initialValue = campaignFlow.value.character(id = id), ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt index 58f386c..ee72d71 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt @@ -1,72 +1,82 @@ package com.pixelized.desktop.lwa.repository.campaign +import com.pixelized.desktop.lwa.network.LwaClient import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.model.campaign.Campaign -import com.pixelized.shared.lwa.model.campaign.CampaignJson import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory -import com.pixelized.shared.lwa.protocol.MessageType -import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.model.campaign.character +import com.pixelized.shared.lwa.protocol.websocket.Message +import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation +import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage import com.pixelized.shared.lwa.usecase.CampaignUseCase -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json +import kotlin.collections.set class CampaignStore( + private val client: LwaClient, private val network: NetworkRepository, private val factory: CampaignJsonFactory, private val useCase: CampaignUseCase, - private val client: HttpClient, - private val json: Json, ) { - private val flow = MutableStateFlow(value = Campaign.EMPTY) + private val _campaignFlow = MutableStateFlow(value = Campaign.EMPTY) + val campaignFlow: StateFlow get() = _campaignFlow init { val scope = CoroutineScope(Dispatchers.IO + Job()) scope.launch { - flow.value = load() + update() } scope.launch { - network.data - .mapNotNull { it.takeIf { it.type == MessageType.UpdatePlayerCharacteristic } } - .map { json.decodeFromString(it.value) } - .collect { - updateCharacteristic(it) - } + network.data.collect(::handleMessage) } } - fun campaignFlow(): StateFlow = flow + private suspend fun update() { + _campaignFlow.value = load() + } private suspend fun load(): Campaign { - val request: CampaignJson = client - .get("http://pixelized.freeboxos.fr:16030/campaign") // TODO - .body() + val request = client.campaign() val data = factory.convertFromJson(json = request) return data } private fun updateCharacteristic( - message: UpdatePlayerCharacteristicMessage, + characterId: String, + characteristic: Campaign.CharacterInstance.Characteristic, + value: Int, ) { - val characters = flow.value.characters.toMutableMap() - val character = characters[message.characterId] ?: Campaign.CharacterInstance( - characteristic = emptyMap(), - usedSkill = emptyList(), - ) - characters[message.characterId] = useCase.updateCharacteristic( - character = character, - characteristic = message.characteristic, - value = message.value - ) - flow.value = flow.value.copy(characters = characters) + val campaign = _campaignFlow.value + val characters = campaign.characters.toMutableMap().also { + it[characterId] = useCase.updateCharacteristic( + character = campaign.character(id = characterId), + characteristic = characteristic, + value = value + ) + } + _campaignFlow.value = _campaignFlow.value.copy(characters = characters) + } + + private suspend fun handleMessage(message: Message) { + when (val payload = message.value) { + is RestSynchronisation.Campaign -> { + update() + } + + is UpdatePlayerCharacteristicMessage -> { + updateCharacteristic( + characterId = payload.characterId, + characteristic = payload.characteristic, + value = payload.value, + ) + } + + else -> Unit + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/model/CharacterSheetPreview.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/model/CharacterSheetPreview.kt new file mode 100644 index 0000000..7c9f403 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/model/CharacterSheetPreview.kt @@ -0,0 +1,7 @@ +package com.pixelized.desktop.lwa.repository.campaign.model + +data class CharacterSheetPreview( + val id: String, + val name: String, + val level: Int, +) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt index 2d1cee5..0dd4b0b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt @@ -1,68 +1,67 @@ package com.pixelized.desktop.lwa.repository.characterSheet +import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch class CharacterSheetRepository( private val store: CharacterSheetStore, ) { private val scope = CoroutineScope(Dispatchers.IO + Job()) - private val sheets = store.characterSheetFlow() - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = emptyList() - ) + val characterSheetPreviewFlow get() = store.previewFlow - private val diminished = MutableStateFlow>(emptyMap()) - - fun characterSheetFlow(): StateFlow> { - return sheets + fun characterPreview(characterId: String?): CharacterSheetPreview? { + return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId } } - fun characterSheetFlow(id: String?): StateFlow { - return sheets - .map { sheets -> - sheets.firstOrNull { sheet -> sheet.id == id } - } - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = sheets.value.firstOrNull { it.id == id } - ) - } - - fun characterDiminishedFlow(id: String?): StateFlow { - return diminished - .map { - it[id] ?: 0 - } - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = diminished.value[id] ?: 0 - ) - } - - fun save(characterSheet: CharacterSheet) { -// store.save(sheet = characterSheet) - } - - fun delete(id: String) { -// store.delete(id = id) - } - - fun setDiminishedForCharacter(id: String, diminished: Int) { - this.diminished.value = this.diminished.value.toMutableMap().apply { - this[id] = diminished + suspend fun characterDetail( + characterId: String?, + forceUpdate: Boolean = false, + ): CharacterSheet? { + return try { + characterId?.let { store.characterDetail(characterId = it, forceUpdate = forceUpdate) } + } catch (exception: Exception) { + null } } + + fun characterDetailFlow( + characterId: String?, + forceUpdate: Boolean = false, + ): StateFlow { + val initial = store.detailFlow.value[characterId] + if (forceUpdate || initial == null) { + scope.launch { + characterDetail( + characterId = characterId, + forceUpdate = forceUpdate, + ) + } + } + return store.detailFlow + .map { sheets -> + sheets[characterId] + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = initial, + ) + } + + suspend fun updateCharacter(characterSheet: CharacterSheet) { + store.updateCharacter(sheet = characterSheet) + } + + suspend fun deleteCharacter(characterId: String) { + store.deleteCharacter(characterId = characterId) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt index 6b9aaa7..a9c5f49 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt @@ -1,77 +1,146 @@ package com.pixelized.desktop.lwa.repository.characterSheet +import com.pixelized.desktop.lwa.network.LwaClient +import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet -import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory -import com.pixelized.shared.lwa.protocol.MessageType -import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage +import com.pixelized.shared.lwa.protocol.websocket.Message +import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation +import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json class CharacterSheetStore( + private val client: LwaClient, private val network: NetworkRepository, private val factory: CharacterSheetJsonFactory, private val useCase: CharacterSheetUseCase, - private val client: HttpClient, - private val json: Json, ) { - private val flow = MutableStateFlow>(value = emptyList()) + private val _previewFlow = MutableStateFlow>(value = emptyList()) + val previewFlow: StateFlow> get() = _previewFlow + private val _detailFlow = MutableStateFlow>(value = emptyMap()) + val detailFlow: StateFlow> get() = _detailFlow init { val scope = CoroutineScope(Dispatchers.IO + Job()) + // initial data loading. scope.launch { - flow.value = load() + _previewFlow.value = charactersPreview() } + // data update through WebSocket. scope.launch { - network.data - .mapNotNull { it.takeIf { it.type == MessageType.UpdateSkillUsage } } - .map { json.decodeFromString(it.value) } - .collect { - updateCharacterSkillChange( - characterId = it.characterId, - skillId = it.skillId, - ) - } + network.data.collect(::handleMessage) } } - fun characterSheetFlow(): StateFlow> = flow + // region Rest - suspend fun load(): List { - val request: List = client - .get("http://pixelized.freeboxos.fr:16030/characters") // TODO - .body() + suspend fun charactersPreview(): List { + val request = client.characters() val data = request.map { - factory.convertFromJson(json = it) + CharacterSheetPreview( + id = it.id, + name = it.name, + level = it.level, + ) } return data } - private fun updateCharacterSkillChange( + suspend fun characterDetail( + characterId: String, + forceUpdate: Boolean = false, + ): CharacterSheet { + val character = _detailFlow.value[characterId] + if (forceUpdate || character == null) { + val request = client.character(id = characterId) + val data = factory.convertFromJson(json = request) + // update the local detail flow. + return _detailFlow.update(data) + } else { + return character + } + } + + suspend fun updateCharacter( + sheet: CharacterSheet, + ) { + val json = factory.convertToJson(sheet = sheet) + try { + client.updateCharacter(sheet = json) + } catch (exception: Exception) { + + } + _detailFlow.update(sheet = sheet) + } + + suspend fun deleteCharacter( + characterId: String, + ) { + try { + client.deleteCharacter(id = characterId) + } catch (exception: Exception) { + + } + _detailFlow.delete(characterId = characterId) + } + + // endregion + // region: WebSocket & data update. + + private suspend fun handleMessage(message: Message) { + when (val payload = message.value) { + is RestSynchronisation.CharacterUpdate -> characterDetail( + characterId = payload.id, + forceUpdate = true + ) + + is RestSynchronisation.CharacterDelete -> { + _previewFlow.value = previewFlow.value.toMutableList() + .also { sheets -> sheets.removeIf { it.id == payload.characterId } } + _detailFlow.delete(payload.characterId) + } + + is UpdateSkillUsageMessage -> { + updateCharacterSkillChange( + characterId = payload.characterId, + skillId = payload.skillId, + ) + } + + else -> Unit + } + } + + private suspend fun updateCharacterSkillChange( characterId: String, skillId: String, ) { - val characters = flow.value.toMutableList() - val index = characters.indexOfFirst { it.id == characterId } + val character = useCase.updateSkillUsage( + character = characterDetail(characterId = characterId), + skillId = skillId, + ) + _detailFlow.update(character) + } - if (index > -1) { - characters[index] = useCase.updateSkillUsage( - character = characters[index], - skillId = skillId, - ) - flow.value = characters + // endregion + + private fun MutableStateFlow>.update(sheet: CharacterSheet): CharacterSheet { + value = value.toMutableMap().also { + it[sheet.id] = sheet + } + return sheet + } + + private fun MutableStateFlow>.delete(characterId: String) { + value = value.toMutableMap().also { + it.remove(characterId) } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt index 71061ad..b91b069 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 @@ -4,13 +4,8 @@ import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.utils.extention.decodeFromFrame import com.pixelized.desktop.lwa.utils.extention.encodeToFrame -import com.pixelized.shared.lwa.SERVER_PORT -import com.pixelized.shared.lwa.protocol.Message -import com.pixelized.shared.lwa.protocol.MessageType -import com.pixelized.shared.lwa.protocol.payload.MessagePayload -import com.pixelized.shared.lwa.protocol.payload.RollMessage -import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage -import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage +import com.pixelized.shared.lwa.protocol.websocket.Message +import com.pixelized.shared.lwa.protocol.websocket.payload.MessagePayload import io.ktor.client.HttpClient import io.ktor.websocket.Frame import kotlinx.coroutines.CoroutineScope @@ -23,19 +18,12 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class NetworkRepository( private val settingsRepository: SettingsRepository, private val client: HttpClient, - private val json: Json, ) { - companion object { - const val DEFAULT_PORT = SERVER_PORT - const val DEFAULT_HOST = "pixelized.freeboxos.fr" - } - private val scope = CoroutineScope(Dispatchers.IO) private var networkJob: Job? = null @@ -97,40 +85,11 @@ class NetworkRepository( suspend fun share( playerName: String = settingsRepository.settings().playerName, payload: MessagePayload, - ) { - if (status.value == Status.CONNECTED) { - when (payload) { - is RollMessage -> share( - playerName = playerName, - type = MessageType.Roll, - content = json.encodeToString(payload), - ) - - is UpdateSkillUsageMessage -> share( - playerName = playerName, - type = MessageType.UpdateSkillUsage, - content = json.encodeToString(payload), - ) - - is UpdatePlayerCharacteristicMessage -> share( - playerName = playerName, - type = MessageType.UpdatePlayerCharacteristic, - content = json.encodeToString(payload), - ) - } - } - } - - suspend fun share( - playerName: String = settingsRepository.settings().playerName, - type: MessageType, - content: String, ) { if (status.value == Status.CONNECTED) { val message = Message( from = playerName, - type = type, - value = content, + value = payload, ) // emit the message into the outgoing buffer outgoingMessageBuffer.emit(message) 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 7b9af9a..4b20e77 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,28 +1,22 @@ package com.pixelized.desktop.lwa.repository.roll_history import com.pixelized.desktop.lwa.repository.network.NetworkRepository -import com.pixelized.shared.lwa.protocol.MessageType -import com.pixelized.shared.lwa.protocol.payload.RollMessage +import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class RollHistoryRepository( private val network: NetworkRepository, - private val jsonFormatter: Json, ) { private val scope = CoroutineScope(Dispatchers.IO) val rolls: SharedFlow = network.data - .mapNotNull { it.takeIf { it.type == MessageType.Roll } } - .map { jsonFormatter.decodeFromString(it.value) } + .mapNotNull { it.value as? RollMessage } .shareIn( scope = scope, started = SharingStarted.Eagerly, @@ -44,9 +38,6 @@ class RollHistoryRepository( resultLabel = resultLabel, rollSuccessLimit = rollSuccessLimit, ) - network.share( - type = MessageType.Roll, - content = jsonFormatter.encodeToString(content), - ) + network.share(payload = content) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt index d5654b5..e6eb986 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt @@ -14,6 +14,8 @@ class SettingsFactory( settings: Settings, ): SettingsJson { return SettingsJsonV1( + host = settings.host, + port = settings.port, playerName = settings.playerName, ) } @@ -29,10 +31,11 @@ class SettingsFactory( private fun convertFromJsonV1( json: SettingsJsonV1, ): Settings { - return with(useCase.defaultSettings()) { - Settings( - playerName = json.playerName ?: playerName - ) - } + val default = useCase.defaultSettings() + return Settings( + host = json.host ?: default.host, + port = json.port ?: default.port, + playerName = json.playerName ?: default.playerName + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsStore.kt index fb8da8a..d2b6cfb 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsStore.kt @@ -19,12 +19,14 @@ class SettingsStore( private val useCase: SettingsUseCase, private val jsonFormatter: Json, ) { - private val settingsDirectory = File(storePath()).also { it.mkdirs() } private val flow = MutableStateFlow(value = useCase.defaultSettings()) fun settingsFlow(): StateFlow = flow init { + // create the directory + File(storePath()).also { it.mkdirs() } + // load the data. val scope = CoroutineScope(Dispatchers.IO + Job()) scope.launch { flow.value = load() diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt index 20d82b5..8ac2ce4 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt @@ -1,5 +1,9 @@ package com.pixelized.desktop.lwa.repository.settings.model data class Settings( + val host: String, + val port: Int, val playerName: String, -) \ No newline at end of file +) { + val root: String get() = "http://${host}:${port}" +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV1.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV1.kt index 45657c8..4aee3e5 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV1.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV1.kt @@ -4,5 +4,7 @@ import kotlinx.serialization.Serializable @Serializable data class SettingsJsonV1( + val host: String?, + val port: Int?, val playerName: String?, ) : SettingsJson \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt index 27d6a6a..1fbfa5e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.ui.screen.campaign.player +package com.pixelized.desktop.lwa.ui.screen.campaign import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt index 7c54f05..36c6837 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt @@ -90,7 +90,9 @@ fun CharacterDetail( .width(width = 128.dp * 4), character = it, dynDetail = dynDetail, - onDismissRequest = { viewModel.hideCharacter() }, + onDismissRequest = { + viewModel.hideCharacter() + }, onDiminished = { scope.launch { dismissedViewModel.showDiminishedDialog(id = it.id) @@ -108,7 +110,7 @@ fun CharacterDetail( fun CharacterDetailContent( modifier: Modifier = Modifier, character: CharacterDetailUio, - dynDetail: State, + dynDetail: State, onDismissRequest: () -> Unit, onDiminished: () -> Unit, onHp: () -> Unit, @@ -167,7 +169,7 @@ private fun Background( private fun CharacterHeader( modifier: Modifier = Modifier, character: CharacterDetailUio, - dynDetail: State, + dynDetail: State, onDismissRequest: () -> Unit, onDiminished: () -> Unit, onHp: () -> Unit, @@ -222,7 +224,7 @@ private fun CharacterHeader( style = MaterialTheme.typography.h6, color = MaterialTheme.lwa.colorScheme.base.primary, fontWeight = FontWeight.Bold, - text = dynDetail.value.hp, + text = dynDetail.value?.hp ?: character.hp, ) Text( modifier = Modifier.alignByBaseline(), @@ -245,7 +247,7 @@ private fun CharacterHeader( style = MaterialTheme.typography.h6, color = MaterialTheme.lwa.colorScheme.base.primary, fontWeight = FontWeight.Bold, - text = dynDetail.value.pp, + text = dynDetail.value?.pp ?: character.pp, ) Text( modifier = Modifier.alignByBaseline(), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt index 1d74e55..49f6404 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailViewModel.kt @@ -5,19 +5,22 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic.Damage -import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic.Power +import com.pixelized.shared.lwa.model.campaign.damage +import com.pixelized.shared.lwa.model.campaign.power import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn +import androidx.compose.runtime.collectAsState +import kotlinx.coroutines.flow.map class CharacterDetailViewModel( private val characterRepository: CharacterSheetRepository, @@ -27,19 +30,14 @@ class CharacterDetailViewModel( private val displayedCharacterId = MutableStateFlow(null) - val detail: StateFlow = combine( - displayedCharacterId, - characterRepository.characterSheetFlow(), - campaignRepository.campaignFlow(), - ) { id, sheets, campaign -> - val sheet = sheets.firstOrNull { it.id == id } - if (sheet == null) return@combine null + val detail: StateFlow = displayedCharacterId.map { id -> + val sheet = characterRepository.characterDetail(id) ?: return@map null CharacterDetailUio( id = sheet.id, portrait = sheet.portrait, name = sheet.name, - hp = "${sheet.hp - (campaign.characters[id]?.characteristic?.get(Damage) ?: 0)}", - pp = "${sheet.pp - (campaign.characters[id]?.characteristic?.get(Power) ?: 0)}", + hp = "${sheet.hp}", + pp = "${sheet.pp}", mov = "${sheet.movement}" ) }.stateIn( @@ -50,23 +48,25 @@ class CharacterDetailViewModel( @Composable @Stable - fun collectDynamicDetailAsState(id: String): State { - val flow = remember(id) { - campaignRepository.characterInstance(id = id) - } - return remember(id) { - flow.mapNotNull { sheet -> + fun collectDynamicDetailAsState(id: String): State { + val scope = rememberCoroutineScope() + val flow: StateFlow = remember(id) { + combine( + characterRepository.characterDetailFlow(id), + campaignRepository.characterInstanceFlow(id = id), + ) { sheet, instance -> + if (sheet == null) return@combine null CharacterDynDetailUio( - hp = sheet.characteristic[Damage].toString(), - pp = sheet.characteristic[Power].toString(), + hp = "${sheet.hp - instance.damage}", + pp = "${sheet.power - instance.power}", ) - } - }.collectAsState( - CharacterDynDetailUio( - hp = flow.value.characteristic[Damage].toString(), - pp = flow.value.characteristic[Power].toString(), + }.stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = null, ) - ) + } + return flow.collectAsState() } fun showCharacter(id: String) { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt index deb3f8f..92a0056 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDiminishedViewModel.kt @@ -19,9 +19,10 @@ class CharacterDiminishedViewModel( val diminishedDialog: State get() = _diminishedDialog suspend fun showDiminishedDialog(id: String) { - val diminished = repository.characterDiminishedFlow(id = id).value - val textFieldValue = - mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0))) + val diminished = 0 // TODO repository.characterDiminishedFlow(id = id).value + val textFieldValue = mutableStateOf( + TextFieldValue("$diminished", selection = TextRange(index = 0)) + ) _diminishedDialog.value = DiminishedStatDialogUio( id = id, label = getString(resource = Res.string.character_sheet__diminished__label), @@ -41,9 +42,10 @@ class CharacterDiminishedViewModel( fun changeDiminished(dialog: DiminishedStatDialogUio) { val value = dialog.value().text.toIntOrNull() ?: 0 - repository.setDiminishedForCharacter( - id = dialog.id, - diminished = value, - ) + // TODO +// repository.setDiminishedForCharacter( +// id = dialog.id, +// diminished = value, +// ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt index f8741c6..29ec973 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt @@ -35,9 +35,9 @@ import org.jetbrains.compose.resources.painterResource data class PlayerPortraitUio( val id: String, val portrait: String?, - val damage: Int, + val hp: Int, val maxHp: Int, - val usedPp: Int, + val pp: Int, val maxPp: Int, ) @@ -96,7 +96,7 @@ fun PlayerPortrait( Text( modifier = Modifier.padding(bottom = 2.dp), style = MaterialTheme.typography.caption, - text = "${character.maxHp - character.damage}/${character.maxHp}", + text = "${character.hp}/${character.maxHp}", ) } Row( @@ -111,7 +111,7 @@ fun PlayerPortrait( Text( modifier = Modifier.padding(bottom = 2.dp), style = MaterialTheme.typography.caption, - text = "${character.maxPp - character.usedPp}/${character.maxPp}", + text = "${character.pp}/${character.maxPp}", ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt index 0203720..56e4199 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt @@ -46,6 +46,7 @@ import org.jetbrains.compose.resources.painterResource data class PlayerPortraitRollUio( val characterId: String, val value: Int?, + val label: String?, ) @Stable @@ -119,6 +120,12 @@ fun PlayerPortraitRoll( color = MaterialTheme.colors.onSurface, text = it.value.toString() ) + Text( + modifier = Modifier.padding(top = 84.dp), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.caption, + text = it.label ?: "", + ) } } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonFactory.kt new file mode 100644 index 0000000..0bf52c9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonFactory.kt @@ -0,0 +1,24 @@ +package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon + +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.damage +import com.pixelized.shared.lwa.model.campaign.power +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet + +class PlayerRibbonFactory { + + fun convertToPlayerPortraitUio( + characterSheet: CharacterSheet?, + characterInstance: Campaign.CharacterInstance, + ): PlayerPortraitUio? { + if (characterSheet == null) return null + return PlayerPortraitUio( + id = characterSheet.id, + portrait = characterSheet.thumbnail, + hp = characterSheet.hp - characterInstance.damage, + maxHp = characterSheet.hp, + pp = characterSheet.pp - characterInstance.power, + maxPp = characterSheet.pp, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt index f3faa22..bacbef7 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt @@ -11,39 +11,31 @@ import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository -import com.pixelized.shared.lwa.model.campaign.character -import com.pixelized.shared.lwa.model.campaign.damage -import com.pixelized.shared.lwa.model.campaign.power import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn class PlayerRibbonViewModel( private val rollHistoryRepository: RollHistoryRepository, characterRepository: CharacterSheetRepository, + private val ribbonFactory: PlayerRibbonFactory, campaignRepository: CampaignRepository, ) : ViewModel() { - val characters: StateFlow> = combine( - characterRepository.characterSheetFlow(), - campaignRepository.campaignFlow(), - ) { sheets, campaign -> - sheets.map { sheet -> - val instance = campaign.character(id = sheet.id) - PlayerPortraitUio( - id = sheet.id, - portrait = sheet.thumbnail, - damage = instance.damage, - maxHp = sheet.hp, - usedPp = instance.power, - maxPp = sheet.pp, - ) - } - }.stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = emptyList() - ) + + val characters: StateFlow> = campaignRepository.campaignFlow + .map { campaign -> + campaign.characters.mapNotNull { entry -> + ribbonFactory.convertToPlayerPortraitUio( + characterSheet = characterRepository.characterDetail(characterId = entry.key), + characterInstance = entry.value, + ) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) private val rolls = hashMapOf>() @@ -52,11 +44,12 @@ class PlayerRibbonViewModel( fun roll(characterId: String): State { val state = rolls.getOrPut(characterId) { mutableStateOf(null) } LaunchedEffect(characterId) { - rollHistoryRepository.rolls.collect { - if (it.characterId == characterId) { + rollHistoryRepository.rolls.collect { roll -> + if (roll.characterId == characterId) { state.value = PlayerPortraitRollUio( characterId = characterId, - value = it.rollValue, + value = roll.rollValue, + label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it } ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt index d0ef784..eb5dc05 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.SavedStateHandle @@ -17,21 +18,19 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheet import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialogUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialogUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.StatChangeDialogUio -import com.pixelized.desktop.lwa.utils.extention.collectAsState import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic import com.pixelized.shared.lwa.model.campaign.damage import com.pixelized.shared.lwa.model.campaign.power import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet -import com.pixelized.shared.lwa.protocol.payload.UpdatePlayerCharacteristicMessage +import com.pixelized.shared.lwa.protocol.websocket.payload.UpdatePlayerCharacteristicMessage -import com.pixelized.shared.lwa.protocol.payload.UpdateSkillUsageMessage +import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import lwacharactersheet.composeapp.generated.resources.Res -import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hit_point import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__power_point import org.jetbrains.compose.resources.getString @@ -62,16 +61,18 @@ class CharacterSheetViewModel( private val _diminishedDialog = mutableStateOf(null) val diminishedDialog: State get() = _diminishedDialog - private val diminishedValueFlow = characterRepository.characterDiminishedFlow(id = argument.id) + // TODO +// private val diminishedValueFlow = characterRepository.characterDiminishedFlow(id = argument.id) val diminishedValue: State @Composable - get() = diminishedValueFlow.collectAsState { it -> - it.takeIf { it > 0 } - } +// get() = diminishedValueFlow.collectAsState { it -> +// it.takeIf { it > 0 } +// } + get() = remember { mutableStateOf(null) } private val sheetFlow = combine( - characterRepository.characterSheetFlow(id = argument.id), - campaignRepository.campaignFlow(), + characterRepository.characterDetailFlow(characterId = argument.id), + campaignRepository.campaignFlow, alteration.alterations(characterId = argument.id), transform = { sheet, campaign, alterations -> factory.convertToUio( @@ -93,8 +94,8 @@ class CharacterSheetViewModel( alteration.toggle(argument.id, "65e37d32-3031-4bf8-9369-d2c45d2efac0") } - fun deleteCharacter(id: String) { - characterRepository.delete(id = id) + suspend fun deleteCharacter(id: String) { + characterRepository.deleteCharacter(characterId = id) } fun onUseSkill(skill: CharacterSheetPageUio.Node) { @@ -109,10 +110,12 @@ class CharacterSheetViewModel( } fun showConfirmCharacterDeletionDialog() { - characterRepository.characterSheetFlow(id = argument.id).value?.let { sheet -> + characterRepository.characterPreview( + characterId = argument.id + )?.let { preview -> _displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio( - id = sheet.id, - name = sheet.name, + id = preview.id, + name = preview.name, ) } } @@ -122,8 +125,10 @@ class CharacterSheetViewModel( } suspend fun showSubCharacteristicDialog(id: String) { - characterRepository.characterSheetFlow(id = argument.id).value?.let { sheet -> - val instance = campaignRepository.characterInstance(id = argument.id).value + characterRepository.characterDetail( + characterId = argument.id, + )?.let { sheet -> + val instance = campaignRepository.characterInstanceFlow(id = argument.id).value _statChangeDialog.value = when (id) { CharacterSheet.CharacteristicId.HP -> { val value = mutableStateOf( @@ -169,8 +174,9 @@ class CharacterSheetViewModel( value: Int, ) { viewModelScope.launch { - val sheet = characterRepository.characterSheetFlow(id = argument.id).value - if (sheet != null) { + characterRepository.characterDetail( + characterId = argument.id, + )?.let { sheet -> network.share( payload = UpdatePlayerCharacteristicMessage( characterId = argument.id, @@ -195,20 +201,20 @@ class CharacterSheetViewModel( } suspend fun showDiminishedDialog() { - val diminished = characterRepository.characterDiminishedFlow(id = argument.id).value - val textFieldValue = - mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0))) - _diminishedDialog.value = DiminishedStatDialogUio( - id = argument.id, - label = getString(resource = Res.string.character_sheet__diminished__label), - value = { textFieldValue.value }, - onValueChange = { value -> - textFieldValue.value = when (value.text.toIntOrNull()?.takeIf { it >= 0 }) { - null -> TextFieldValue("0", selection = TextRange(index = 0)) - else -> value - } - }, - ) +// val diminished = characterRepository.characterDiminishedFlow(id = argument.id).value +// val textFieldValue = +// mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0))) +// _diminishedDialog.value = DiminishedStatDialogUio( +// id = argument.id, +// label = getString(resource = Res.string.character_sheet__diminished__label), +// value = { textFieldValue.value }, +// onValueChange = { value -> +// textFieldValue.value = when (value.text.toIntOrNull()?.takeIf { it >= 0 }) { +// null -> TextFieldValue("0", selection = TextRange(index = 0)) +// else -> value +// } +// }, +// ) } fun hideDiminishedDialog() { @@ -216,10 +222,10 @@ class CharacterSheetViewModel( } fun changeDiminished(dialog: DiminishedStatDialogUio) { - val value = dialog.value().text.toIntOrNull() ?: 0 - characterRepository.setDiminishedForCharacter( - id = dialog.id, - diminished = value, - ) +// val value = dialog.value().text.toIntOrNull() ?: 0 +// characterRepository.setDiminishedForCharacter( +// id = dialog.id, +// diminished = value, +// ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditViewModel.kt index 0cc4551..08be8c2 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditViewModel.kt @@ -27,13 +27,11 @@ class CharacterSheetEditViewModel( private val argument = CharacterSheetEditDestination.Argument(savedStateHandle) private val _characterSheet = mutableStateOf( - characterSheetRepository.characterSheetFlow(id = argument.id).value.let { - runBlocking { - sheetFactory.convertToUio( - sheet = it, - onDeleteSkill = ::deleteSkill, - ) - } + runBlocking { + sheetFactory.convertToUio( + sheet = characterSheetRepository.characterDetail(characterId = argument.id), + onDeleteSkill = ::deleteSkill, + ) } ) val characterSheet: State get() = _characterSheet @@ -110,10 +108,10 @@ class CharacterSheetEditViewModel( suspend fun save() { val updatedSheet = sheetFactory.updateCharacterSheet( - currentSheet = characterSheetRepository.characterSheetFlow(id = _characterSheet.value.id).value, + currentSheet = characterSheetRepository.characterDetail(characterId = _characterSheet.value.id), editedSheet = _characterSheet.value, ) - characterSheetRepository.save( + characterSheetRepository.updateCharacter( characterSheet = updatedSheet, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt index 30bfd45..358b8ff 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt @@ -11,21 +11,22 @@ import com.pixelized.shared.lwa.OperatingSystem import com.pixelized.shared.lwa.storePath class MainPageViewModel( - repository: CharacterSheetRepository, + private val repository: CharacterSheetRepository, networkRepository: NetworkRepository, ) : ViewModel() { - private val charactersFlow = repository.characterSheetFlow() val characters: State> @Composable - get() = charactersFlow.collectAsState { sheets -> - sheets.map { sheet -> - CharacterUio( - id = sheet.id, - name = sheet.name, - ) + get() = repository + .characterSheetPreviewFlow + .collectAsState { sheets -> + sheets.map { sheet -> + CharacterUio( + id = sheet.id, + name = sheet.name, + ) + } } - } private val networkStatus = networkRepository.status val enableRollHistory: State @@ -33,7 +34,7 @@ class MainPageViewModel( get() = networkStatus.collectAsState { it == NetworkRepository.Status.CONNECTED } fun openSaveDirectory( - os: OperatingSystem = OperatingSystem.current + os: OperatingSystem = OperatingSystem.current, ) { when (os) { OperatingSystem.Windows -> shellRun("explorer.exe", listOf(storePath(os = os))) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt index b76e514..80d1dec 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt @@ -9,10 +9,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController -import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository +import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController +import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import com.pixelized.desktop.lwa.utils.extention.collectAsState import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -23,8 +23,9 @@ class NetworkViewModel( private val networkRepository: NetworkRepository, private val factory: NetworkFactory, ) : ViewModel() { - private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST) - private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT) + private val settings = settingsRepository.settings() + private val host = mutableStateOf(settings.host) + private val port = mutableStateOf(settings.port) private val _networkError = MutableSharedFlow() val networkError: SharedFlow get() = _networkError @@ -64,7 +65,7 @@ class NetworkViewModel( } fun onPortChange(port: String) { - this.port.value = port.toIntOrNull() ?: 0 + this.port.value = port.toIntOrNull() ?: settings.port } fun onHostChange(host: String) { @@ -75,6 +76,15 @@ class NetworkViewModel( controller.show() _isLoading.value = true + if (settings.host != host.value || settings.port != port.value) { + settingsRepository.update( + settings = settings.copy( + host = host.value, + port = port.value + ) + ) + } + networkRepository.connect( host = host.value, port = port.value, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt index 80a153a..864140e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt @@ -59,7 +59,7 @@ class RollViewModel( sheet: CharacterSheetPageUio, characteristic: CharacterSheetPageUio.Characteristic, ) { - val diminished = characterSheetRepository.characterDiminishedFlow(id = sheet.id).value + val diminished = 0 // TODO characterSheetRepository.characterDiminishedFlow(id = sheet.id).value prepareRoll( sheet = sheet, label = characteristic.label, @@ -98,12 +98,12 @@ class RollViewModel( rollAction: String, rollSuccessValue: Int?, ) { - runBlocking { + this.sheet = runBlocking { rollRotation.snapTo(0f) rollScale.snapTo(1f) + characterSheetRepository.characterDetail(characterId = sheet.id)!! } - this.sheet = characterSheetRepository.characterSheetFlow(id = sheet.id).value!! this.rollAction = rollAction this.rollSuccessValue = rollSuccessValue diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt index 4c18538..3bcd53b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt @@ -4,6 +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.campaign.model.CharacterSheetPreview import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet @@ -21,15 +22,15 @@ class RollHistoryViewModel( init { viewModelScope.launch { combine( - characterRepository.characterSheetFlow(), + characterRepository.characterSheetPreviewFlow, rollRepository.rolls, - ) { sheets: List, content -> + ) { sheets: List, content -> _rolls.value.toMutableList().apply { + val name = sheets.firstOrNull { it.id == content.characterId }?.name ?: "" add( index = 0, element = RollHistoryItemUio( - character = sheets.firstOrNull { it.id == content.characterId }?.name - ?: "", + character = name, skillLabel = content.skillLabel, rollDifficulty = content.rollDifficulty, resultLabel = content.resultLabel, 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 index cf5f056..55fd8ee 100644 --- 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 @@ -1,6 +1,6 @@ package com.pixelized.desktop.lwa.utils.extention -import com.pixelized.shared.lwa.protocol.Message +import com.pixelized.shared.lwa.protocol.websocket.Message import io.ktor.websocket.Frame import io.ktor.websocket.readText import kotlinx.serialization.json.Json diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 63f7f30..09d2ba3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] kotlin = "2.0.21" kotlinx-coroutines = "1.9.0" -kotlinx-json = "1.7.3" -compose-multiplatform = "1.7.0" +kotlinx-json = "1.8.0" +compose-multiplatform = "1.7.3" androidx-lifecycle = "2.8.3" androidx-navigation = "2.8.0-alpha10" ktor = "3.0.1" @@ -10,7 +10,6 @@ koin = "4.0.0" turtle = "0.5.0" logback = "1.5.11" coil = "3.1.0" -filament-android = "1.17.1" [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } @@ -38,7 +37,6 @@ koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", ver ktor-serialization-json = { group = 'io.ktor', name = 'ktor-serialization-kotlinx-json', version.ref = "ktor" } 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-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } ktor-client-websockets = { group = 'io.ktor', name = "ktor-client-websockets", version.ref = "ktor" } ktor-client-negotiation = { group = 'io.ktor', name = 'ktor-client-content-negotiation', version.ref = "ktor" } @@ -51,5 +49,4 @@ turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtl logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } -coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } -filament-android = { group = "com.google.ar.sceneform", name = "filament-android", version.ref = "filament-android" } \ No newline at end of file +coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } \ No newline at end of file diff --git a/server/build.gradle.kts b/server/build.gradle.kts index fe1793b..0c3d4d3 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -22,5 +22,4 @@ dependencies { implementation(libs.ktor.server.websockets) implementation(libs.ktor.server.negotiation) implementation(libs.ktor.serialization.json) - implementation(libs.filament.android) } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt index b346260..7a776fd 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt @@ -97,7 +97,7 @@ class CampaignService( return true } - // Data manipulation threw WebSocket. + // Data manipulation through WebSocket. suspend fun updateCharacteristic( characterId: String, diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt index 8bd0312..4175faf 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt @@ -43,7 +43,7 @@ class CharacterSheetService( return store.delete(id = characterId) } - // Data manipulation threw WebSocket. + // Data manipulation through WebSocket. fun updateCharacterLevel( characterId: String, diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt index 68be7d8..6f3432e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt @@ -17,9 +17,6 @@ class Engine( suspend fun handle(message: Message) { when (val data = message.value) { - RestSynchronisation.Campaign -> Unit // TODO - - is RestSynchronisation.CharacterUpdate -> Unit // TODO is RollMessage -> Unit // Nothing to do here. @@ -34,9 +31,11 @@ class Engine( skillId = data.skillId ) - is RestSynchronisation.CharacterDelete -> characterService.deleteCharacter( - characterId = data.characterId, - ) + RestSynchronisation.Campaign -> Unit // Handle in the Rest + + is RestSynchronisation.CharacterUpdate -> Unit // Handle in the Rest + + is RestSynchronisation.CharacterDelete -> Unit // Handle in the Rest } } } \ 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 index f6a8cb9..d15cea8 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt @@ -72,6 +72,33 @@ class LocalServer { } routing { + webSocket( + path = "/ws", + handler = { + val job = launch { + // send local message to the clients + engine.webSocket.collect { message -> + send(json.encodeToFrame(message)) + } + } + runCatching { + // watching for clients incoming message + incoming.consumeEach { frame -> + if (frame is Frame.Text) { + val message = Json.decodeFromFrame(frame = frame) + // log the message + engine.handle(message) + // broadcast to clients the message + engine.webSocket.emit(message) + } + } + }.onFailure { exception -> + println("WebSocket exception: ${exception.localizedMessage}") + }.also { + job.cancel() + } + } + ) get( path = "/characters", body = engine.getCharacters(), @@ -116,33 +143,6 @@ class LocalServer { ) } } - webSocket( - path = "/ws", - handler = { - val job = launch { - // send local message to the clients - engine.webSocket.collect { message -> - send(json.encodeToFrame(message)) - } - } - runCatching { - // watching for clients incoming message - incoming.consumeEach { frame -> - if (frame is Frame.Text) { - val message = Json.decodeFromFrame(frame = frame) - // log the message - engine.handle(message) - // broadcast to clients the message - engine.webSocket.emit(message) - } - } - }.onFailure { exception -> - println("WebSocket exception: ${exception.localizedMessage}") - }.also { - job.cancel() - } - } - ) } } ) @@ -154,7 +154,6 @@ class LocalServer { try { server?.start(wait = true) } catch (exception: Exception) { - // TODO println("WebSocket exception: ${exception.localizedMessage}") } finally { println("Server close")