From 1e5f0d88ae3012f664456df4e834233efd387e37 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 22 Feb 2025 12:54:19 +0100 Subject: [PATCH] Refactor project data to allow server handling. --- composeApp/build.gradle.kts | 9 +- composeApp/compose-desktop.pro | 6 +- .../kotlin/com/pixelized/desktop/lwa/App.kt | 6 +- .../com/pixelized/desktop/lwa/Module.kt | 57 ++++-- .../desktop/lwa/business/ExpressionUseCase.kt | 3 +- .../repository/alteration/AlterationStore.kt | 36 ++-- .../CharacterSheetRepository.kt | 9 +- .../characterSheet/CharacterSheetStore.kt | 98 ++------- .../characterSheet/SkillDescriptionFactory.kt | 47 ----- .../repository/network/NetworkRepository.kt | 21 +- .../lwa/repository/network/helper/Client.kt | 13 +- .../roll_history/RollHistoryRepository.kt | 4 +- .../lwa/repository/settings/SettingsStore.kt | 2 +- .../lwa/ui/navigation/screen/MainNavHost.kt | 8 + .../screen/campaign/player/CampaignScreen.kt | 4 +- .../campaign/player/detail/CharacterDetail.kt | 8 +- .../campaign/player/ribbon/PlayerPortrait.kt | 23 +-- .../player/ribbon/PlayerPortraitRoll.kt | 91 ++++++--- .../campaign/player/ribbon/PlayerRibbon.kt | 24 ++- .../player/ribbon/PlayerRibbonViewModel.kt | 29 ++- .../detail/CharacterSheetFactory.kt | 8 +- .../detail/CharacterSheetViewModel.kt | 2 +- .../rememberCharacterSheetPagePreview.kt | 2 +- .../edit/CharacterSheetEditFactory.kt | 10 +- .../edit/CharacterSheetEditViewModel.kt | 2 +- .../lwa/ui/screen/main/MainPageViewModel.kt | 4 +- .../lwa/ui/screen/roll/RollViewModel.kt | 4 +- .../rollhistory/RollHistoryViewModel.kt | 5 +- .../desktop/lwa/ui/theme/LwaTheme.kt | 4 +- .../desktop/lwa/utils/extention/JsonExt.kt | 2 +- .../kotlin/com/pixelized/desktop/lwa/main.kt | 4 +- gradle/libs.versions.toml | 5 + server/build.gradle.kts | 4 +- server/src/main/kotlin/Module.kt | 38 ++++ .../com/pixelized/server/lwa/Application.kt | 3 + .../pixelized/server/lwa/extention/JsonExt.kt | 2 +- .../character/CharacterSheetRepository.kt | 27 +++ .../model/character/CharacterSheetStore.kt | 119 +++++++++++ .../com/pixelized/server/lwa/server/Server.kt | 133 +++++++----- shared/build.gradle.kts | 1 + .../com/pixelized/server/lwa/Constants.kt | 3 - .../server/lwa/protocol/MessageType.kt | 5 - .../com/pixelized/shared/lwa/Constants.kt | 3 + .../kotlin/com/pixelized/shared/lwa/Module.kt | 34 ++++ .../com/pixelized/shared/lwa}/StorePath.kt | 2 +- .../lwa/model/campaign/CampaignRepository.kt | 9 + .../lwa/model/campaign/model/Campaign.kt | 11 + .../model/campaign/model/CampaignFactory.kt | 39 ++++ .../lwa/model/campaign/model/CampaignJson.kt | 6 + .../model/campaign/model/CampaignJsonV1.kt | 15 ++ .../characterSheet/model/CharacterSheet.kt | 2 +- .../model/CharacterSheetJson.kt | 2 +- .../model}/CharacterSheetJsonFactory.kt | 190 +++++++++--------- .../model/CharacterSheetJsonV1.kt | 2 +- .../lwa/protocol/Message.kt | 2 +- .../shared/lwa/protocol/MessageType.kt | 5 + .../lwa/protocol/roll/RollMessage.kt | 2 +- .../lwa/usecase}/CharacterSheetUseCase.kt | 2 +- 58 files changed, 742 insertions(+), 469 deletions(-) delete mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/SkillDescriptionFactory.kt create mode 100644 server/src/main/kotlin/Module.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageType.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Constants.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository => shared/src/commonMain/kotlin/com/pixelized/shared/lwa}/StorePath.kt (95%) create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository => shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model}/characterSheet/model/CharacterSheet.kt (97%) rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository => shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model}/characterSheet/model/CharacterSheetJson.kt (58%) rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet => shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model}/CharacterSheetJsonFactory.kt (92%) rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository => shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model}/characterSheet/model/CharacterSheetJsonV1.kt (94%) rename shared/src/commonMain/kotlin/com/pixelized/{server => shared}/lwa/protocol/Message.kt (78%) create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt rename shared/src/commonMain/kotlin/com/pixelized/{server => shared}/lwa/protocol/roll/RollMessage.kt (84%) rename {composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business => shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase}/CharacterSheetUseCase.kt (96%) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 3ba22e2..81a0884 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -14,6 +14,8 @@ kotlin { val desktopMain by getting commonMain.dependencies { + // common + implementation(projects.shared) // compose implementation(compose.runtime) implementation(compose.foundation) @@ -26,19 +28,18 @@ kotlin { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) // injection - api(libs.koin.core) implementation(libs.koin.compose) implementation(libs.koin.compose.viewmodel) // composable component. implementation(libs.coil.compose) implementation(libs.coil.network) - // common - implementation(projects.shared) // network implementation(libs.kotlinx.serialization.json) + implementation(libs.ktor.serialization.json) implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.okhttp) implementation(libs.ktor.client.websockets) + implementation(libs.ktor.client.negotiation) // shell implementation(libs.turtle) } diff --git a/composeApp/compose-desktop.pro b/composeApp/compose-desktop.pro index ac6f5d4..7f9ed44 100644 --- a/composeApp/compose-desktop.pro +++ b/composeApp/compose-desktop.pro @@ -10,4 +10,8 @@ -keep class kotlinx.coroutines.** { *; } # OkHttp comming from COIL. --dontwarn okhttp3.internal.platform.** \ No newline at end of file +-dontwarn okhttp3.internal.platform.** + +# Serialization +-keep class io.ktor.serialization.kotlinx.json.** { *; } +-keep class com.pixelized.shared.lwa.model.** { *; } \ 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 4174cbf..02ea6c6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -44,6 +44,7 @@ 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.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 @@ -53,6 +54,7 @@ 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") @@ -125,8 +127,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 6d399e6..99240f1 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -1,21 +1,24 @@ package com.pixelized.desktop.lwa -import com.pixelized.desktop.lwa.business.CharacterSheetUseCase +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.business.ExpressionUseCase -import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetJsonFactory +import com.pixelized.desktop.lwa.parser.dice.DiceParser +import com.pixelized.desktop.lwa.parser.expression.ExpressionParser +import com.pixelized.desktop.lwa.parser.word.WordParser +import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.repository.alteration.AlterationStore import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetStore -import com.pixelized.desktop.lwa.repository.characterSheet.SkillDescriptionFactory import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.settings.SettingsFactory import com.pixelized.desktop.lwa.repository.settings.SettingsRepository -import com.pixelized.desktop.lwa.repository.alteration.AlterationStore -import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository 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.PlayerRibbonViewModel import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetFactory import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetViewModel import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditFactory @@ -26,32 +29,48 @@ import com.pixelized.desktop.lwa.ui.screen.network.NetworkFactory import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel -import com.pixelized.desktop.lwa.parser.dice.DiceParser -import com.pixelized.desktop.lwa.parser.word.WordParser -import com.pixelized.desktop.lwa.parser.expression.ExpressionParser -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.PlayerRibbonViewModel -import kotlinx.serialization.json.Json +import com.pixelized.shared.lwa.model.campaign.CampaignRepository +import com.pixelized.shared.lwa.model.campaign.model.CampaignFactory +import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase +import io.ktor.client.HttpClient +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.websocket.WebSockets +import io.ktor.serialization.kotlinx.json.json import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module -val moduleDependencies +val appModuleDependencies get() = listOf( - toolsDependencies, parserDependencies, factoryDependencies, useCaseDependencies, storeDependencies, repositoryDependencies, viewModelDependencies, + toolsDependencies, ) val toolsDependencies get() = module { - factory { Json { explicitNulls = false } } + single { + OkHttp.create() + } + single { + HttpClient( + engine = get() + ) { + install(WebSockets) { + pingIntervalMillis = 20_000 + } + install(ContentNegotiation) { + json(get()) + } + } + } } val storeDependencies @@ -68,17 +87,17 @@ val repositoryDependencies singleOf(::RollHistoryRepository) singleOf(::SettingsRepository) singleOf(::AlterationRepository) + singleOf(::CampaignRepository) } val factoryDependencies get() = module { factoryOf(::CharacterSheetFactory) factoryOf(::CharacterSheetEditFactory) - factoryOf(::CharacterSheetJsonFactory) factoryOf(::NetworkFactory) factoryOf(::SkillFieldFactory) - factoryOf(::SkillDescriptionFactory) factoryOf(::SettingsFactory) + factoryOf(::CampaignFactory) } val viewModelDependencies @@ -106,6 +125,6 @@ val useCaseDependencies factoryOf(::SkillStepUseCase) factoryOf(::RollUseCase) factoryOf(::ExpressionUseCase) - factoryOf(::CharacterSheetUseCase) factoryOf(::SettingsUseCase) + factoryOf(::CharacterSheetUseCase) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt index 21829c3..3c787dc 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/ExpressionUseCase.kt @@ -4,8 +4,7 @@ package com.pixelized.desktop.lwa.business import com.pixelized.desktop.lwa.parser.expression.Expression import com.pixelized.desktop.lwa.parser.expression.ExpressionParser import com.pixelized.desktop.lwa.parser.word.Word -import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet import kotlin.math.max import kotlin.math.min diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt index 9d28db8..4327903 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt @@ -4,24 +4,24 @@ import com.pixelized.desktop.lwa.parser.expression.Expression import com.pixelized.desktop.lwa.parser.expression.ExpressionParser import com.pixelized.desktop.lwa.repository.alteration.model.Alteration import com.pixelized.desktop.lwa.repository.alteration.model.AlterationMetadata -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId.ARMOR -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId.DEX -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId.HEI -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId.MOV -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId.STR -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.ACROBATICS_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.AID_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.ATHLETICS_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.BARGAIN_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.COMBAT_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.DISCRETION_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.INTIMIDATION_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.PERCEPTION_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.PERSUASION_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.SPIEL_ID -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CommonSkillId.THROW_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.ARMOR +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.DEX +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.HEI +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.MOV +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId.STR +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.ACROBATICS_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.AID_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.ATHLETICS_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.BARGAIN_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.COMBAT_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.DISCRETION_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.INTIMIDATION_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.PERCEPTION_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.PERSUASION_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.SPIEL_ID +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CommonSkillId.THROW_ID class AlterationStore( private val expressionParser: ExpressionParser, 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 e67648a..7aad9e7 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,6 +1,6 @@ package com.pixelized.desktop.lwa.repository.characterSheet -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -53,11 +53,11 @@ class CharacterSheetRepository( } fun save(characterSheet: CharacterSheet) { - store.save(sheet = characterSheet) +// store.save(sheet = characterSheet) } fun delete(id: String) { - store.delete(id = id) +// store.delete(id = id) } fun setDiminishedForCharacter(id: String, diminished: Int) { @@ -65,5 +65,4 @@ class CharacterSheetRepository( this[id] = diminished } } -} - +} \ 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 dd57e28..faae76a 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,24 +1,25 @@ package com.pixelized.desktop.lwa.repository.characterSheet -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJson -import com.pixelized.desktop.lwa.repository.characterStorePath +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJson +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.get +import io.ktor.serialization.kotlinx.json.json 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.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import java.io.File -import java.text.Collator class CharacterSheetStore( private val factory: CharacterSheetJsonFactory, - private val jsonFormatter: Json, + private val client: HttpClient, ) { - private val characterDirectory = File(characterStorePath()).also { it.mkdirs() } private val flow = MutableStateFlow>(value = emptyList()) init { @@ -30,85 +31,22 @@ class CharacterSheetStore( fun characterSheetFlow(): StateFlow> = flow - @Throws( - CharacterSheetStoreException::class, - FileWriteException::class, - JsonConversionException::class, - ) fun save(sheet: CharacterSheet) { - // convert the character sheet into json format. - val json = try { - factory.convertToJson(sheet = sheet).let(jsonFormatter::encodeToString) - } catch (exception: Exception) { - throw JsonConversionException(root = exception) - } - // write the character file. - try { - val file = characterSheetFile(id = sheet.id) - file.writeText( - text = json, - charset = Charsets.UTF_8, - ) - } catch (exception: Exception) { - throw FileWriteException(root = exception) - } - // Update the dataflow. - flow.value = flow.value - .toMutableList() - .also { data -> - val index = data.indexOfFirst { it.id == sheet.id } - if (index >= 0) { - data[index] = sheet - } else { - data.add(sheet) - } - } - .sortedWith(compareBy(Collator.getInstance()) { it.name }) + } fun delete(id: String): Boolean { - val file = characterSheetFile(id = id) - flow.value = flow.value.toMutableList() - .also { data -> - data.removeIf { it.id == id } - } - .sortedBy { - it.name - } - return file.delete() + return false } - @Throws( - CharacterSheetStoreException::class, - FileReadException::class, - JsonConversionException::class, - ) suspend fun load(): List { - return characterDirectory - .listFiles() - ?.mapNotNull { file -> - val json = try { - file.readText(charset = Charsets.UTF_8) - } catch (exception: Exception) { - throw FileReadException(root = exception) - } - // Guard, if the json is blank no character have been save, ignore this file. - if (json.isBlank()) { - return@mapNotNull null - } - try { - val sheet = jsonFormatter.decodeFromString(json) - factory.convertFromJson(sheet) - } catch (exception: Exception) { - throw JsonConversionException(root = exception) - } - } - ?.sortedWith(compareBy(Collator.getInstance()) { it.name }) - ?: emptyList() - } - - private fun characterSheetFile(id: String): File { - return File("${characterStorePath()}${id}.json") + val request: List = client + .get("http://pixelized.freeboxos.fr:16030/characters") + .body() + val data = request.map { + factory.convertFromJson(json = it) + } + return data } sealed class CharacterSheetStoreException(root: Exception) : Exception(root) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/SkillDescriptionFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/SkillDescriptionFactory.kt deleted file mode 100644 index bc3fdf8..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/SkillDescriptionFactory.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.pixelized.desktop.lwa.repository.characterSheet - -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet -import lwacharactersheet.composeapp.generated.resources.Res -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__acrobatics -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__aid -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__athletics -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__bargain -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__combat -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__discretion -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__dodge -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__empathy -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__grab -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__intimidation -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__perception -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__persuasion -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__search -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__sleight_of_hand -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__spiel -import lwacharactersheet.composeapp.generated.resources.tooltip__skills__throw -import org.jetbrains.compose.resources.getString - -class SkillDescriptionFactory { - - suspend fun baseSkillDescription(id: String): String? { - return when (id) { - CharacterSheet.CommonSkillId.COMBAT_ID -> getString(Res.string.tooltip__skills__combat) - CharacterSheet.CommonSkillId.DODGE_ID -> getString(Res.string.tooltip__skills__dodge) - CharacterSheet.CommonSkillId.GRAB_ID -> getString(Res.string.tooltip__skills__grab) - CharacterSheet.CommonSkillId.THROW_ID -> getString(Res.string.tooltip__skills__throw) - CharacterSheet.CommonSkillId.ATHLETICS_ID -> getString(Res.string.tooltip__skills__athletics) - CharacterSheet.CommonSkillId.ACROBATICS_ID -> getString(Res.string.tooltip__skills__acrobatics) - CharacterSheet.CommonSkillId.PERCEPTION_ID -> getString(Res.string.tooltip__skills__perception) - CharacterSheet.CommonSkillId.SEARCH_ID -> getString(Res.string.tooltip__skills__search) - CharacterSheet.CommonSkillId.EMPATHY_ID -> getString(Res.string.tooltip__skills__empathy) - CharacterSheet.CommonSkillId.PERSUASION_ID -> getString(Res.string.tooltip__skills__persuasion) - CharacterSheet.CommonSkillId.INTIMIDATION_ID -> getString(Res.string.tooltip__skills__intimidation) - CharacterSheet.CommonSkillId.SPIEL_ID -> getString(Res.string.tooltip__skills__spiel) - CharacterSheet.CommonSkillId.BARGAIN_ID -> getString(Res.string.tooltip__skills__bargain) - CharacterSheet.CommonSkillId.DISCRETION_ID -> getString(Res.string.tooltip__skills__discretion) - CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID -> getString(Res.string.tooltip__skills__sleight_of_hand) - CharacterSheet.CommonSkillId.AID_ID -> getString(Res.string.tooltip__skills__aid) - else -> null - } - } -} - 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 b5e6637..35b4abe 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt @@ -1,16 +1,12 @@ package com.pixelized.desktop.lwa.repository.network -//import com.pixelized.desktop.lwa.repository.network.helper.server -//import io.ktor.server.engine.EmbeddedServer -//import io.ktor.server.netty.NettyApplicationEngine -import com.pixelized.desktop.lwa.repository.network.helper.client import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.utils.extention.decodeFromFrame import com.pixelized.desktop.lwa.utils.extention.encodeToFrame -import com.pixelized.server.lwa.SERVER_PORT -import com.pixelized.server.lwa.protocol.Message -import com.pixelized.server.lwa.protocol.MessageType +import com.pixelized.shared.lwa.SERVER_PORT +import com.pixelized.shared.lwa.protocol.Message +import com.pixelized.shared.lwa.protocol.MessageType import io.ktor.client.HttpClient import io.ktor.websocket.Frame import kotlinx.coroutines.CoroutineScope @@ -25,10 +21,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json -typealias Client = HttpClient - class NetworkRepository( private val settingsRepository: SettingsRepository, + private val client: HttpClient, ) { companion object { const val DEFAULT_PORT = SERVER_PORT @@ -37,7 +32,6 @@ class NetworkRepository( private val scope = CoroutineScope(Dispatchers.IO) private var networkJob: Job? = null - private var client: Client? = null private val outgoingMessageBuffer = MutableSharedFlow() private val incomingMessageBuffer = MutableSharedFlow() @@ -46,7 +40,6 @@ class NetworkRepository( private val _status = MutableStateFlow(Status.DISCONNECTED) val status: StateFlow get() = _status - fun connect( host: String, port: Int, @@ -54,12 +47,10 @@ class NetworkRepository( onFailure: (Exception) -> Unit = { }, onClose: () -> Unit = { }, ) { - client = client() - networkJob?.cancel() networkJob = scope.launch { try { - client?.connectWebSocket(host = host, port = port) { + client.connectWebSocket(host = host, port = port) { _status.value = Status.CONNECTED onConnect() @@ -93,7 +84,7 @@ class NetworkRepository( fun disconnect() { networkJob?.cancel() scope.launch { - client?.close() + client.close() } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt index 76884b1..80f4259 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt @@ -1,26 +1,15 @@ package com.pixelized.desktop.lwa.repository.network.helper import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession -import io.ktor.client.plugins.websocket.WebSockets import io.ktor.client.plugins.websocket.webSocket import io.ktor.http.HttpMethod // https://ktor.io/docs/client-websockets.html#handle-session -fun client(): HttpClient { - val client = HttpClient(CIO) { - install(WebSockets) { - pingIntervalMillis = 20_000 - } - } - return client -} - suspend fun HttpClient.connectWebSocket( host: String, port: Int, - block: suspend DefaultClientWebSocketSession.() -> Unit + block: suspend DefaultClientWebSocketSession.() -> Unit, ) { webSocket( method = HttpMethod.Get, 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 64399d7..c9dc47c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll_history/RollHistoryRepository.kt @@ -1,8 +1,8 @@ package com.pixelized.desktop.lwa.repository.roll_history import com.pixelized.desktop.lwa.repository.network.NetworkRepository -import com.pixelized.server.lwa.protocol.MessageType -import com.pixelized.server.lwa.protocol.roll.RollMessage +import com.pixelized.shared.lwa.protocol.MessageType +import com.pixelized.shared.lwa.protocol.roll.RollMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharedFlow diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsStore.kt index 4e04927..fb8da8a 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 @@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.repository.settings import com.pixelized.desktop.lwa.business.SettingsUseCase import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson -import com.pixelized.desktop.lwa.repository.storePath +import com.pixelized.shared.lwa.storePath import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt index 24ea220..4fed814 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt @@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -9,6 +10,8 @@ import androidx.navigation.compose.rememberNavController import com.pixelized.desktop.lwa.ui.navigation.screen.destination.MainDestination import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableMainPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableNetworkPage +import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel +import org.koin.compose.viewmodel.koinViewModel val LocalScreenController = compositionLocalOf { error("MainNavHost controller is not yet ready") @@ -18,7 +21,12 @@ val LocalScreenController = compositionLocalOf { fun MainNavHost( controller: NavHostController = rememberNavController(), startDestination: String = MainDestination.navigationRoute(), + networkViewModel: NetworkViewModel = koinViewModel(), ) { + LaunchedEffect(Unit) { + networkViewModel.connect() + } + CompositionLocalProvider( LocalScreenController provides controller, ) { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/CampaignScreen.kt index b260c91..27d6a6a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/CampaignScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/CampaignScreen.kt @@ -83,9 +83,7 @@ fun CampaignScreen( }, leftOverlay = { PlayerRibbon( - modifier = Modifier - .padding(all = 8.dp) - .fillMaxHeight(), + modifier = Modifier.fillMaxHeight(), onCharacter = { characterDetailViewModel.showCharacter(id = it) }, 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 10ed9cb..212f79c 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 @@ -141,7 +141,7 @@ private fun Background( ) { Surface( modifier = modifier.fillMaxSize(), - color = MaterialTheme.lwa.color.elevatedSurface, + color = MaterialTheme.lwa.colorScheme.elevatedSurface, ) { // Image( // modifier = Modifier.fillMaxSize().drawWithContent { @@ -200,7 +200,7 @@ private fun CharacterHeader( ) { Icon( painter = painterResource(Res.drawable.ic_close_24dp), - tint = MaterialTheme.lwa.color.base.primary, + tint = MaterialTheme.lwa.colorScheme.base.primary, contentDescription = null, ) } @@ -220,7 +220,7 @@ private fun CharacterHeader( Text( modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.h6, - color = MaterialTheme.lwa.color.base.primary, + color = MaterialTheme.lwa.colorScheme.base.primary, fontWeight = FontWeight.Bold, text = dynDetail.value.hp, ) @@ -243,7 +243,7 @@ private fun CharacterHeader( Text( modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.h6, - color = MaterialTheme.lwa.color.base.primary, + color = MaterialTheme.lwa.colorScheme.base.primary, fontWeight = FontWeight.Bold, text = dynDetail.value.pp, ) 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 6231938..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 @@ -2,7 +2,6 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -20,13 +19,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox +import com.pixelized.desktop.lwa.ui.theme.lwa import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp @@ -42,19 +41,15 @@ data class PlayerPortraitUio( val maxPp: Int, ) -object PlayerPortrait { - object Default { - val size = DpSize(96.dp, 128.dp) - } -} - @Composable fun PlayerPortrait( modifier: Modifier = Modifier, - size: DpSize = PlayerPortrait.Default.size, + size: DpSize, character: PlayerPortraitUio, onCharacter: (id: String) -> Unit, ) { + val colorScheme = MaterialTheme.lwa.colorScheme + DecoratedBox( modifier = modifier .size(size = size) @@ -76,11 +71,11 @@ fun PlayerPortrait( drawRect( brush = Brush.verticalGradient( listOf( - Color.Black.copy(alpha = 0.0f), - Color.Black.copy(alpha = 0.0f), - Color.Black.copy(alpha = 0.0f), - Color.Black.copy(alpha = 0.5f), - Color.Black.copy(alpha = 0.8f), + colorScheme.elevatedSurface.copy(alpha = 0.0f), + colorScheme.elevatedSurface.copy(alpha = 0.0f), + colorScheme.elevatedSurface.copy(alpha = 0.0f), + colorScheme.elevatedSurface.copy(alpha = 0.5f), + colorScheme.elevatedSurface.copy(alpha = 0.8f), ) ) ) 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 df6d9b8..0203720 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 @@ -9,28 +9,45 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.PointerMatcher +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.onClick +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable -import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.PointerButton +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp import org.jetbrains.compose.resources.painterResource +@Stable +data class PlayerPortraitRollUio( + val characterId: String, + val value: Int?, +) + @Stable data class PlayerPortraitRollAnimation( val alpha: Animatable = Animatable(0f), @@ -38,13 +55,19 @@ data class PlayerPortraitRollAnimation( val scale: Animatable = Animatable(1f), ) +@OptIn(ExperimentalFoundationApi::class) @Composable fun PlayerPortraitRoll( modifier: Modifier = Modifier, - value: Int?, + size: DpSize, + value: PlayerPortraitRollUio?, + onLeftClick: (PlayerPortraitRollUio) -> Unit, + onRightClick: (PlayerPortraitRollUio) -> Unit, ) { AnimatedContent( - modifier = modifier.graphicsLayer { clip = false }, + modifier = modifier + .size(size = size) + .graphicsLayer { clip = false }, targetState = value, transitionSpec = { val enter = fadeIn() @@ -61,33 +84,41 @@ fun PlayerPortraitRoll( }, contentAlignment = Alignment.Center, ) { - when (it) { - null -> Unit - else -> { - Icon( - modifier = Modifier - .graphicsLayer { - this.alpha = 0.8f - this.rotationZ = animation.rotation.value - } - .size(48.dp), - painter = painterResource(Res.drawable.ic_d20_24dp), - tint = MaterialTheme.colors.primary, - contentDescription = null, - ) - Text( - style = MaterialTheme.typography.h5.copy( - shadow = Shadow( - color = MaterialTheme.colors.surface, - offset = Offset.Zero, - blurRadius = 8f, - ) - ), - textAlign = TextAlign.Center, - color = MaterialTheme.colors.onSurface, - text = it.toString() - ) - } + if (it != null) { + Icon( + modifier = Modifier + .graphicsLayer { + this.alpha = 0.8f + this.rotationZ = animation.rotation.value + } + .fillMaxWidth() + .aspectRatio(1f) + .padding(all = 8.dp) + .clip(shape = CircleShape) + .onClick( + matcher = PointerMatcher.mouse(PointerButton.Secondary), + onClick = { onRightClick(it) }, + ).clickable { + onLeftClick(it) + } + .padding(all = 8.dp), + painter = painterResource(Res.drawable.ic_d20_24dp), + tint = MaterialTheme.colors.primary, + contentDescription = null, + ) + Text( + style = MaterialTheme.typography.h5.copy( + shadow = Shadow( + color = MaterialTheme.colors.surface, + offset = Offset.Zero, + blurRadius = 8f, + ) + ), + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.onSurface, + text = it.value.toString() + ) } } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbon.kt index 7e60c2c..cda5d3d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbon.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbon.kt @@ -1,26 +1,36 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import org.koin.compose.viewmodel.koinViewModel +object PlayerRibbon { + object Default { + val size = DpSize(96.dp, 128.dp) + } +} + @Composable fun PlayerRibbon( modifier: Modifier = Modifier, playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(), + padding: PaddingValues = PaddingValues(all = 8.dp), onCharacter: (id: String) -> Unit, ) { val characters = playerRibbonViewModel.characters.collectAsState() LazyColumn( modifier = modifier, + contentPadding = padding, verticalArrangement = Arrangement.spacedBy(space = 8.dp) ) { items( @@ -29,15 +39,19 @@ fun PlayerRibbon( ) { Row { PlayerPortrait( + size = PlayerRibbon.Default.size, character = it, onCharacter = onCharacter, ) PlayerPortraitRoll( - modifier = Modifier.size( - width = 64.dp, - height = PlayerPortrait.Default.size.height - ), + size = PlayerRibbon.Default.size, value = playerRibbonViewModel.roll(characterId = it.id).value, + onRightClick = { + playerRibbonViewModel.onPortraitRollRightClick(characterId = it.characterId) + }, + onLeftClick = { + + }, ) } } 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 45127a5..20dbb53 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 @@ -2,11 +2,10 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository @@ -38,30 +37,26 @@ class PlayerRibbonViewModel( initialValue = emptyList() ) - private val _rolls: HashMap = hashMapOf() - val rolls: StateFlow> = rollHistoryRepository.rolls - .map { - _rolls[it.characterId] = it.rollValue - _rolls - }.stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = emptyMap() - ) + private val rolls = hashMapOf>() @Composable @Stable - fun roll(characterId: String): State { - val state = rememberSaveable(characterId) { - mutableStateOf(null) - } + fun roll(characterId: String): State { + val state = rolls.getOrPut(characterId) { mutableStateOf(null) } LaunchedEffect(characterId) { rollHistoryRepository.rolls.collect { if (it.characterId == characterId) { - state.value = it.rollValue + state.value = PlayerPortraitRollUio( + characterId = characterId, + value = it.rollValue, + ) } } } return state } + + fun onPortraitRollRightClick(characterId: String) { + rolls[characterId]?.value = null + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt index 982beda..77c07fe 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetFactory.kt @@ -3,9 +3,8 @@ package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail import com.pixelized.desktop.lwa.business.ExpressionUseCase import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio import com.pixelized.desktop.lwa.repository.alteration.model.FieldAlteration -import com.pixelized.desktop.lwa.repository.characterSheet.SkillDescriptionFactory -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Node import lwacharactersheet.composeapp.generated.resources.Res @@ -42,7 +41,6 @@ import org.jetbrains.compose.resources.getString class CharacterSheetFactory( private val skillUseCase: ExpressionUseCase, private val expressionUseCase: ExpressionUseCase, - private val skillDescriptionFactory: SkillDescriptionFactory, ) { suspend fun convertToUio( @@ -213,7 +211,7 @@ class CharacterSheetFactory( skill = skill, alterations = alterations[skill.id].sum(), ), - tooltips = skillDescriptionFactory.baseSkillDescription(id = skill.id)?.let { + tooltips = skill.description?.let { TooltipUio( title = skill.label, description = 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 42dabd5..6534a1b 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 @@ -12,7 +12,7 @@ import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.model.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 diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt index 6bac149..774e4da 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/preview/rememberCharacterSheetPagePreview.kt @@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.preview import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet.CharacteristicId +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet.CharacteristicId import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt index d39338b..ddc3ac3 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/edit/CharacterSheetEditFactory.kt @@ -3,14 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.characterSheet.edit import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf -import com.pixelized.desktop.lwa.business.CharacterSheetUseCase -import com.pixelized.desktop.lwa.repository.characterSheet.SkillDescriptionFactory -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.occupation import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionFieldUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.BaseSkillFieldUio import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.SimpleFieldUio +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__action_label import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__actions__name_label @@ -54,9 +53,8 @@ import java.util.UUID import kotlin.math.max class CharacterSheetEditFactory( - private val characterSheetUseCase: CharacterSheetUseCase, private val skillFieldFactory: SkillFieldFactory, - private val skillDescriptionFactory: SkillDescriptionFactory, + private val characterSheetUseCase: CharacterSheetUseCase, ) { suspend fun updateCharacterSheet( currentSheet: CharacterSheet?, @@ -119,7 +117,7 @@ class CharacterSheetEditFactory( CharacterSheet.Skill( id = editedSkill.id, label = editedSkill.label, - description = skillDescriptionFactory.baseSkillDescription(editedSkill.id), + description = currentSkill?.description, base = "${editedSkill.base.value}", bonus = editedSkill.bonus.value.value.takeIf { it.isNotBlank() }, level = editedSkill.level.value.value.takeIf { it.isNotBlank() }, 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 121ae4a..0cc4551 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 @@ -4,8 +4,8 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.composable.ActionFieldUio import kotlinx.coroutines.runBlocking 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 cd9136a..30bfd45 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 @@ -4,11 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.lifecycle.ViewModel import com.lordcodes.turtle.shellRun -import com.pixelized.desktop.lwa.repository.OperatingSystem import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository -import com.pixelized.desktop.lwa.repository.storePath import com.pixelized.desktop.lwa.utils.extention.collectAsState +import com.pixelized.shared.lwa.OperatingSystem +import com.pixelized.shared.lwa.storePath class MainPageViewModel( repository: CharacterSheetRepository, 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 b2fab6d..254cbe1 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 @@ -6,13 +6,13 @@ import androidx.compose.animation.core.spring import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel -import com.pixelized.desktop.lwa.business.SkillStepUseCase import com.pixelized.desktop.lwa.business.ExpressionUseCase +import com.pixelized.desktop.lwa.business.SkillStepUseCase import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio import com.pixelized.desktop.lwa.ui.screen.roll.DifficultyUio.Difficulty +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay 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 be39d02..7e605f1 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 @@ -5,8 +5,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -28,7 +28,8 @@ class RollHistoryViewModel( add( index = 0, element = RollHistoryItemUio( - character = sheets.firstOrNull { it.id == content.characterId }?.name ?: "", + character = sheets.firstOrNull { it.id == content.characterId }?.name + ?: "", skillLabel = content.skillLabel, rollDifficulty = content.rollDifficulty, resultLabel = content.resultLabel, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt index 08333d9..8337488 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt @@ -20,7 +20,7 @@ val MaterialTheme.lwa: LwaTheme @Stable data class LwaTheme( - val color: LwaColorTheme, + val colorScheme: LwaColorTheme, ) @Composable @@ -30,7 +30,7 @@ fun LwaTheme( val lwaColorTheme = darkLwaColorTheme() val theme = remember { LwaTheme( - color = lwaColorTheme, + colorScheme = lwaColorTheme, ) } 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 4c40882..cf5f056 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.server.lwa.protocol.Message +import com.pixelized.shared.lwa.protocol.Message import io.ktor.websocket.Frame import io.ktor.websocket.readText import kotlinx.serialization.json.Json diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt index 1d884f9..b7a6159 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt @@ -1,14 +1,16 @@ package com.pixelized.desktop.lwa import androidx.compose.ui.window.application +import com.pixelized.shared.lwa.sharedModuleDependencies import org.koin.compose.KoinContext +import org.koin.core.context.loadKoinModules import org.koin.core.context.startKoin import javax.swing.UIManager fun main() { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) startKoin { - modules(modules = moduleDependencies) + modules(modules = sharedModuleDependencies + appModuleDependencies) } application { KoinContext { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60b1b57..e0cb737 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,15 +31,20 @@ kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-co kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-json" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } +ktor-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" } ktor-server-core = { group = 'io.ktor', name = "ktor-server-core", version.ref = "ktor" } ktor-server-netty = { group = 'io.ktor', name = "ktor-server-netty", version.ref = "ktor" } ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor" } +ktor-server-negotiation = { group = 'io.ktor', name = 'ktor-server-content-negotiation', version.ref = "ktor" } turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 0dc6d93..0c3d4d3 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -15,9 +15,11 @@ application { dependencies { implementation(projects.shared) - implementation(libs.kotlinx.serialization.json) implementation(libs.logback) + implementation(libs.koin.ktor) implementation(libs.ktor.server.core) implementation(libs.ktor.server.netty) implementation(libs.ktor.server.websockets) + implementation(libs.ktor.server.negotiation) + implementation(libs.ktor.serialization.json) } \ No newline at end of file diff --git a/server/src/main/kotlin/Module.kt b/server/src/main/kotlin/Module.kt new file mode 100644 index 0000000..ee43783 --- /dev/null +++ b/server/src/main/kotlin/Module.kt @@ -0,0 +1,38 @@ +import com.pixelized.server.lwa.model.character.CharacterSheetRepository +import com.pixelized.server.lwa.model.character.CharacterSheetStore +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val serverModuleDependencies + get() = listOf( + parserDependencies, + factoryDependencies, + useCaseDependencies, + storeDependencies, + repositoryDependencies, + ) + +val storeDependencies + get() = module { + singleOf(::CharacterSheetStore) + } + +val repositoryDependencies + get() = module { + singleOf(::CharacterSheetRepository) + } + +val factoryDependencies + get() = module { + + } + +val parserDependencies + get() = module { + + } + +val useCaseDependencies + get() = module { + + } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt b/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt index 70e6e5e..cff6e7c 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt @@ -1,6 +1,9 @@ package com.pixelized.server.lwa import com.pixelized.server.lwa.server.LocalServer +import com.pixelized.shared.lwa.sharedModuleDependencies +import org.koin.core.context.startKoin +import org.koin.java.KoinJavaComponent.inject fun main() { LocalServer().create().start() diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt b/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt index a3b7a7c..b46610e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/extention/JsonExt.kt @@ -1,6 +1,6 @@ package com.pixelized.server.lwa.extention -import com.pixelized.server.lwa.protocol.Message +import com.pixelized.shared.lwa.protocol.Message import io.ktor.websocket.Frame import io.ktor.websocket.readText import kotlinx.serialization.json.Json diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt new file mode 100644 index 0000000..73d65bb --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetRepository.kt @@ -0,0 +1,27 @@ +package com.pixelized.server.lwa.model.character + +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +class CharacterSheetRepository( + store: CharacterSheetStore, +) { + private val scope = CoroutineScope(Dispatchers.IO + Job()) + + private val sheets = store.characterSheetFlow() + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + + fun characterSheetFlow(): StateFlow> { + return sheets + } +} + diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt new file mode 100644 index 0000000..c81d1e8 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt @@ -0,0 +1,119 @@ +package com.pixelized.server.lwa.model.character + +import com.pixelized.shared.lwa.characterStorePath +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheet +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJson +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +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.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File +import java.text.Collator + +class CharacterSheetStore( + private val factory: CharacterSheetJsonFactory, + private val jsonFormatter: Json, +) { + private val characterDirectory = File(characterStorePath()).also { it.mkdirs() } + private val flow = MutableStateFlow>(value = emptyList()) + + init { + val scope = CoroutineScope(Dispatchers.IO + Job()) + scope.launch { + flow.value = load() + } + } + + fun characterSheetFlow(): StateFlow> = flow + + @Throws( + CharacterSheetStoreException::class, + FileWriteException::class, + JsonConversionException::class, + ) + fun save(sheet: CharacterSheet) { + // convert the character sheet into json format. + val json = try { + factory.convertToJson(sheet = sheet).let(jsonFormatter::encodeToString) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + // write the character file. + try { + val file = characterSheetFile(id = sheet.id) + file.writeText( + text = json, + charset = Charsets.UTF_8, + ) + } catch (exception: Exception) { + throw FileWriteException(root = exception) + } + // Update the dataflow. + flow.value = flow.value + .toMutableList() + .also { data -> + val index = data.indexOfFirst { it.id == sheet.id } + if (index >= 0) { + data[index] = sheet + } else { + data.add(sheet) + } + } + .sortedWith(compareBy(Collator.getInstance()) { it.name }) + } + + fun delete(id: String): Boolean { + val file = characterSheetFile(id = id) + flow.value = flow.value.toMutableList() + .also { data -> + data.removeIf { it.id == id } + } + .sortedBy { + it.name + } + return file.delete() + } + + @Throws( + CharacterSheetStoreException::class, + FileReadException::class, + JsonConversionException::class, + ) + suspend fun load(): List { + return characterDirectory + .listFiles() + ?.mapNotNull { file -> + val json = try { + file.readText(charset = Charsets.UTF_8) + } catch (exception: Exception) { + throw FileReadException(root = exception) + } + // Guard, if the json is blank no character have been save, ignore this file. + if (json.isBlank()) { + return@mapNotNull null + } + try { + val sheet = jsonFormatter.decodeFromString(json) + factory.convertFromJson(sheet) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + } + ?.sortedWith(compareBy(Collator.getInstance()) { it.name }) + ?: emptyList() + } + + private fun characterSheetFile(id: String): File { + return File("${characterStorePath()}${id}.json") + } + + sealed class CharacterSheetStoreException(root: Exception) : Exception(root) + class JsonConversionException(root: Exception) : CharacterSheetStoreException(root) + class FileWriteException(root: Exception) : CharacterSheetStoreException(root) + class FileReadException(root: Exception) : CharacterSheetStoreException(root) +} \ 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 ff1bc62..aa36092 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 @@ -1,16 +1,25 @@ package com.pixelized.server.lwa.server -import com.pixelized.server.lwa.SERVER_PORT + import com.pixelized.server.lwa.extention.decodeFromFrame import com.pixelized.server.lwa.extention.encodeToFrame -import com.pixelized.server.lwa.protocol.Message +import com.pixelized.server.lwa.model.character.CharacterSheetRepository +import com.pixelized.shared.lwa.SERVER_PORT +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.protocol.Message +import com.pixelized.shared.lwa.sharedModuleDependencies +import io.ktor.http.ContentType +import io.ktor.serialization.kotlinx.json.json 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.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.routing.get 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 @@ -21,6 +30,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json +import org.koin.ktor.ext.inject +import org.koin.ktor.plugin.Koin +import serverModuleDependencies import kotlin.time.Duration.Companion.seconds // https://ktor.io/docs/server-websockets.html#handle-multiple-session @@ -28,33 +40,78 @@ typealias Server = EmbeddedServer() - fun create(): LocalServer { - server = build { - val job = launch { - // send local message to the clients - outgoingMessageBuffer.collect { message -> - send(json.encodeToFrame(message)) + fun create( + port: Int = SERVER_PORT, // 16030 + ): LocalServer { + server = embeddedServer( + factory = Netty, + port = port, + module = { + install(Koin) { + modules(sharedModuleDependencies + serverModuleDependencies) + } + + val json by inject() + install(ContentNegotiation) { + json(json) + } + + install(WebSockets) { + pingPeriod = 15.seconds + timeout = 15.seconds + maxFrameSize = Long.MAX_VALUE + masking = false + } + + val repository by inject() + val factory by inject() + routing { + get( + path = "/", + body = { + call.respondText(contentType = ContentType.Text.Html) { + "characters" + } + } + ) + get( + path = "/characters", + body = { + val body = repository.characterSheetFlow().value.map(factory::convertToJson) + call.respond(body) + }, + ) + webSocket( + path = "/ws", + handler = { + val job = launch { + // send local message to the clients + outgoingMessageBuffer.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) + println(message) + // broadcast to clients the message + outgoingMessageBuffer.emit(message) + } + } + }.onFailure { exception -> + println("WebSocket exception: ${exception.localizedMessage}") + }.also { + job.cancel() + } + } + ) } } - 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(message) - } - } - }.onFailure { exception -> - println("WebSocket exception: ${exception.localizedMessage}") - }.also { - job.cancel() - } - } + ) return this } @@ -70,28 +127,4 @@ class LocalServer { } } } - - private fun build( - port: Int = SERVER_PORT, - handler: suspend DefaultWebSocketServerSession.() -> Unit, - ): EmbeddedServer { - return embeddedServer( - factory = Netty, - port = port, - module = { - install(WebSockets) { - pingPeriod = 15.seconds - timeout = 15.seconds - maxFrameSize = Long.MAX_VALUE - masking = false - } - routing { - webSocket( - path = "/ws", - handler = handler, - ) - } - }, - ) - } } \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 7028ceb..f0fc2a1 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -8,6 +8,7 @@ kotlin { sourceSets { commonMain.dependencies { + implementation(libs.koin.core) implementation(libs.kotlinx.serialization.json) } } diff --git a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt deleted file mode 100644 index f87f00b..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/Constants.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.pixelized.server.lwa - -const val SERVER_PORT = 16030 \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageType.kt b/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageType.kt deleted file mode 100644 index cca0da3..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/MessageType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.pixelized.server.lwa.protocol - -enum class MessageType { - Roll -} diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Constants.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Constants.kt new file mode 100644 index 0000000..44a59db --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Constants.kt @@ -0,0 +1,3 @@ +package com.pixelized.shared.lwa + +const val SERVER_PORT = 16030 \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt new file mode 100644 index 0000000..15f7676 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt @@ -0,0 +1,34 @@ +package com.pixelized.shared.lwa + +import com.pixelized.shared.lwa.model.characterSheet.model.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase +import kotlinx.serialization.json.Json +import org.koin.core.module.dsl.factoryOf +import org.koin.dsl.module + +val sharedModuleDependencies + get() = listOf( + toolsDependencies, + factoryDependencies, + useCaseDependencies, + ) + +val toolsDependencies + get() = module { + factory { + Json { + explicitNulls = false + prettyPrint = true + } + } + } + +val factoryDependencies + get() = module { + factoryOf(::CharacterSheetJsonFactory) + } + +val useCaseDependencies + get() = module { + factoryOf(::CharacterSheetUseCase) + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/StorePath.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/StorePath.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt index 1d56535..aec84c2 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/StorePath.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/StorePath.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.repository +package com.pixelized.shared.lwa enum class OperatingSystem( val home: String = System.getProperty("user.home"), diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt new file mode 100644 index 0000000..e2212c3 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignRepository.kt @@ -0,0 +1,9 @@ +package com.pixelized.shared.lwa.model.campaign + +import com.pixelized.shared.lwa.model.campaign.model.CampaignFactory + +class CampaignRepository( + private val factory: CampaignFactory, +) { + +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt new file mode 100644 index 0000000..14f03e2 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/Campaign.kt @@ -0,0 +1,11 @@ +package com.pixelized.shared.lwa.model.campaign.model + +data class Campaign( + val characters: List, +) { + data class CharacterInstance( + val damage: Int, + val usedPower: Int, + val usedMovement: Int, + ) +} diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt new file mode 100644 index 0000000..d4bb8d8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignFactory.kt @@ -0,0 +1,39 @@ +package com.pixelized.shared.lwa.model.campaign.model + +class CampaignFactory { + fun convertFromJson( + json: CampaignJson, + ): Campaign { + return when (json) { + is CampaignJsonV1 -> convertFromV1(json = json) + } + } + + private fun convertFromV1( + json: CampaignJsonV1, + ): Campaign { + return Campaign( + characters = json.characters.map { + Campaign.CharacterInstance( + damage = it.damage, + usedPower = it.usedPower, + usedMovement = it.usedMovement, + ) + } + ) + } + + private fun convertToJson( + data: Campaign, + ): CampaignJson { + return CampaignJsonV1( + characters = data.characters.map { + CampaignJsonV1.CharacterInstanceJson( + damage = it.damage, + usedPower = it.usedPower, + usedMovement = it.usedMovement, + ) + } + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt new file mode 100644 index 0000000..0f13766 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJson.kt @@ -0,0 +1,6 @@ +package com.pixelized.shared.lwa.model.campaign.model + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface CampaignJson \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt new file mode 100644 index 0000000..bb40815 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/model/CampaignJsonV1.kt @@ -0,0 +1,15 @@ +package com.pixelized.shared.lwa.model.campaign.model + +import kotlinx.serialization.Serializable + +@Serializable +data class CampaignJsonV1( + val characters: List, +) : CampaignJson { + @Serializable + data class CharacterInstanceJson( + val damage: Int, + val usedPower: Int, + val usedMovement: Int, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheet.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheet.kt index 21bc30b..7aeb164 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheet.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheet.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.repository.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet.model data class CharacterSheet( val id: String, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJson.kt similarity index 58% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJson.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJson.kt index db11792..1cc7899 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJson.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJson.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.repository.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet.model import kotlinx.serialization.Serializable diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonFactory.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonFactory.kt index 2fd178c..2257342 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetJsonFactory.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonFactory.kt @@ -1,14 +1,101 @@ -package com.pixelized.desktop.lwa.repository.characterSheet +package com.pixelized.shared.lwa.model.characterSheet.model + +import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase -import com.pixelized.desktop.lwa.business.CharacterSheetUseCase -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJson -import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJsonV1 class CharacterSheetJsonFactory( - private val skillDescriptionFactory: SkillDescriptionFactory, private val characterSheetUseCase: CharacterSheetUseCase, ) { + suspend fun convertFromJson( + json: CharacterSheetJson, + ): CharacterSheet { + return when (json) { + is CharacterSheetJsonV1 -> convertFromV1(json = json) + } + } + + private suspend fun convertFromV1( + json: CharacterSheetJsonV1, + ): CharacterSheet = characterSheetUseCase.run { + CharacterSheet( + id = json.id, + name = json.name, + portrait = json.portrait, + thumbnail = json.thumbnail, + strength = json.strength, + dexterity = json.dexterity, + constitution = json.constitution, + height = json.height, + intelligence = json.intelligence, + power = json.power, + charisma = json.charisma, + overrideMovement = json.movement != null, + movement = json.movement ?: defaultMovement(), + currentHp = json.currentHp, + overrideMaxHp = json.maxHp != null, + maxHp = json.maxHp ?: defaultMaxHp( + constitution = json.constitution, + height = json.height, + ), + currentPp = json.currentPP, + overrideMaxPP = json.maxPP != null, + maxPp = json.maxPP ?: defaultMaxPower(power = json.power), + overrideDamageBonus = json.damageBonus != null, + damageBonus = json.damageBonus ?: defaultDamageBonus( + strength = json.strength, + height = json.height, + ), + overrideArmor = json.armor != null, + armor = json.armor ?: defaultArmor(), + overrideLearning = json.learning != null, + learning = json.learning ?: defaultLearning(intelligence = json.intelligence), + overrideHpGrow = json.hpGrowf != null, + hpGrow = json.hpGrowf ?: defaultHpGrow(constitution = json.constitution), + commonSkills = json.skills.map { + CharacterSheet.Skill( + id = it.id, + label = it.label, + description = it.description, + base = it.base, + bonus = it.bonus, + level = it.level, + occupation = it.occupation, + used = it.used, + ) + }, + specialSkills = json.occupations.map { + CharacterSheet.Skill( + id = it.id, + label = it.label, + description = it.description, + base = it.base, + bonus = it.bonus, + level = it.level, + occupation = it.occupation, + used = it.used, + ) + }, + magicSkills = json.magics.map { + CharacterSheet.Skill( + id = it.id, + label = it.label, + description = it.description, + base = it.base, + bonus = it.bonus, + level = it.level, + occupation = it.occupation, + used = it.used, + ) + }, + actions = json.rolls.map { + CharacterSheet.Roll( + id = it.id, + label = it.label, + roll = it.roll, + ) + }, + ) + } fun convertToJson( sheet: CharacterSheet, @@ -80,95 +167,4 @@ class CharacterSheetJsonFactory( ) return json } - - suspend fun convertFromJson( - json: CharacterSheetJson, - ): CharacterSheet { - return when (json) { - is CharacterSheetJsonV1 -> convertFromV1(json = json) - } - } - - private suspend fun convertFromV1( - json: CharacterSheetJsonV1, - ): CharacterSheet = characterSheetUseCase.run { - CharacterSheet( - id = json.id, - name = json.name, - portrait = json.portrait, - thumbnail = json.thumbnail, - strength = json.strength, - dexterity = json.dexterity, - constitution = json.constitution, - height = json.height, - intelligence = json.intelligence, - power = json.power, - charisma = json.charisma, - overrideMovement = json.movement != null, - movement = json.movement ?: defaultMovement(), - currentHp = json.currentHp, - overrideMaxHp = json.maxHp != null, - maxHp = json.maxHp ?: defaultMaxHp( - constitution = json.constitution, - height = json.height, - ), - currentPp = json.currentPP, - overrideMaxPP = json.maxPP != null, - maxPp = json.maxPP ?: defaultMaxPower(power = json.power), - overrideDamageBonus = json.damageBonus != null, - damageBonus = json.damageBonus ?: defaultDamageBonus( - strength = json.strength, - height = json.height, - ), - overrideArmor = json.armor != null, - armor = json.armor ?: defaultArmor(), - overrideLearning = json.learning != null, - learning = json.learning ?: defaultLearning(intelligence = json.intelligence), - overrideHpGrow = json.hpGrowf != null, - hpGrow = json.hpGrowf ?: defaultHpGrow(constitution = json.constitution), - commonSkills = json.skills.map { - CharacterSheet.Skill( - id = it.id, - label = it.label, - description = skillDescriptionFactory.baseSkillDescription(id = json.id), - base = it.base, - bonus = it.bonus, - level = it.level, - occupation = it.occupation, - used = it.used, - ) - }, - specialSkills = json.occupations.map { - CharacterSheet.Skill( - id = it.id, - label = it.label, - description = it.description, - base = it.base, - bonus = it.bonus, - level = it.level, - occupation = it.occupation, - used = it.used, - ) - }, - magicSkills = json.magics.map { - CharacterSheet.Skill( - id = it.id, - label = it.label, - description = it.description, - base = it.base, - bonus = it.bonus, - level = it.level, - occupation = it.occupation, - used = it.used, - ) - }, - actions = json.rolls.map { - CharacterSheet.Roll( - id = it.id, - label = it.label, - roll = it.roll, - ) - }, - ) - } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonV1.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonV1.kt index c50034e..0cc6b85 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/model/CharacterSheetJsonV1.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/model/CharacterSheetJsonV1.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.repository.characterSheet.model +package com.pixelized.shared.lwa.model.characterSheet.model import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/Message.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/Message.kt similarity index 78% rename from shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/Message.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/Message.kt index 5518940..e1ddaa5 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/Message.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/Message.kt @@ -1,4 +1,4 @@ -package com.pixelized.server.lwa.protocol +package com.pixelized.shared.lwa.protocol import kotlinx.serialization.Serializable diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt new file mode 100644 index 0000000..55361ad --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/MessageType.kt @@ -0,0 +1,5 @@ +package com.pixelized.shared.lwa.protocol + +enum class MessageType { + Roll +} diff --git a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/roll/RollMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/roll/RollMessage.kt similarity index 84% rename from shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/roll/RollMessage.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/roll/RollMessage.kt index fda452c..b1a503f 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/server/lwa/protocol/roll/RollMessage.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/roll/RollMessage.kt @@ -1,4 +1,4 @@ -package com.pixelized.server.lwa.protocol.roll +package com.pixelized.shared.lwa.protocol.roll import kotlinx.serialization.Serializable diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/CharacterSheetUseCase.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/CharacterSheetUseCase.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt index f948904..92cae41 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/CharacterSheetUseCase.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/CharacterSheetUseCase.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.business +package com.pixelized.shared.lwa.usecase import kotlin.math.ceil import kotlin.math.max