Merge branch 'feature/errorManagement' into 'main'
Feature/error management See merge request pixelized/LwaCharacterSheet!6
This commit is contained in:
commit
2eabb4f5e4
74 changed files with 1705 additions and 1128 deletions
|
|
@ -242,6 +242,7 @@
|
|||
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
|
||||
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
||||
<string name="game_master__create_character_sheet">Créer un personnage</string>
|
||||
<string name="game_master__alteration__title">Édition d'Altération</string>
|
||||
<string name="game_master__alteration__filter">Filtrer par nom :</string>
|
||||
<string name="game_master__alteration__create">Créer une altération</string>
|
||||
<string name="game_master__alteration__delete">Supprimer l'altération</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.pixelized.desktop.lwa
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.MaterialTheme
|
||||
|
|
@ -10,11 +13,13 @@ import androidx.compose.material.SnackbarDefaults
|
|||
import androidx.compose.material.SnackbarDuration
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -47,6 +52,7 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWin
|
|||
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterNavHost
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
|
||||
|
|
@ -146,7 +152,7 @@ private fun MainWindowScreen(
|
|||
}
|
||||
|
||||
LwaScaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
MainNavHost()
|
||||
|
||||
|
|
@ -179,7 +185,7 @@ private fun WindowsHandler(
|
|||
is RollHistoryWindow -> RollHistoryPage()
|
||||
|
||||
is GameMasterWindow -> LwaScaffold {
|
||||
GameMasterScreen()
|
||||
GameMasterNavHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
|||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
|
@ -19,6 +20,7 @@ class DataSyncViewModel(
|
|||
private val characterRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val campaignRepository: CampaignRepository,
|
||||
private val tagRepository: TagRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val networkRepository: NetworkRepository,
|
||||
) : ViewModel() {
|
||||
|
|
@ -40,10 +42,10 @@ class DataSyncViewModel(
|
|||
networkRepository.status
|
||||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||
.onEach {
|
||||
tagRepository.updateAlterationTags()
|
||||
alterationRepository.updateAlterations()
|
||||
alterationRepository.updateTags()
|
||||
tagRepository.updateCharacterTags()
|
||||
characterRepository.updateCharacterPreviews()
|
||||
characterRepository.updateTags()
|
||||
campaignRepository.updateCampaign()
|
||||
}
|
||||
.launchIn(this)
|
||||
|
|
@ -54,9 +56,8 @@ class DataSyncViewModel(
|
|||
.distinctUntilChanged()
|
||||
.onEach { instances ->
|
||||
instances.forEach { characterSheetId ->
|
||||
characterRepository.characterDetail(
|
||||
characterRepository.updateCharacterSheet(
|
||||
characterSheetId = characterSheetId,
|
||||
forceUpdate = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ 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.settings.SettingsStore
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagStore
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogFactory
|
||||
|
|
@ -37,9 +39,9 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillField
|
|||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
|
|
@ -100,6 +102,7 @@ val storeDependencies
|
|||
singleOf(::SettingsStore)
|
||||
singleOf(::AlterationStore)
|
||||
singleOf(::CampaignStore)
|
||||
singleOf(::TagStore)
|
||||
}
|
||||
|
||||
val repositoryDependencies
|
||||
|
|
@ -110,6 +113,7 @@ val repositoryDependencies
|
|||
singleOf(::SettingsRepository)
|
||||
singleOf(::AlterationRepository)
|
||||
singleOf(::CampaignRepository)
|
||||
singleOf(::TagRepository)
|
||||
}
|
||||
|
||||
val factoryDependencies
|
||||
|
|
|
|||
|
|
@ -5,83 +5,89 @@ import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
|||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
|
||||
interface LwaClient {
|
||||
|
||||
// Alteration
|
||||
|
||||
suspend fun alterations(): List<AlterationJson>
|
||||
suspend fun getAlterations(): APIResponse<List<AlterationJson>>
|
||||
|
||||
suspend fun alterations(
|
||||
suspend fun getAlteration(
|
||||
alterationId: String,
|
||||
): AlterationJson?
|
||||
): APIResponse<AlterationJson?>
|
||||
|
||||
suspend fun updateAlteration(
|
||||
alterationJson: AlterationJson
|
||||
)
|
||||
suspend fun putAlteration(
|
||||
alterationJson: AlterationJson,
|
||||
create: Boolean,
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun deleteAlteration(
|
||||
alterationId: String,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun alterationTags(): List<TagJson>
|
||||
suspend fun getAlterationTags(): APIResponse<List<TagJson>>
|
||||
|
||||
// Campaign
|
||||
|
||||
suspend fun campaign(): CampaignJson
|
||||
suspend fun getCampaign(): APIResponse<CampaignJson>
|
||||
|
||||
suspend fun campaignAddCharacter(
|
||||
suspend fun putCampaignCharacter(
|
||||
characterSheetId: String,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun campaignRemoveCharacter(
|
||||
suspend fun removeCampaignCharacter(
|
||||
characterSheetId: String,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun campaignAddNpc(
|
||||
suspend fun putCampaignNpc(
|
||||
characterSheetId: String,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun campaignRemoveNpc(
|
||||
suspend fun removeCampaignNpc(
|
||||
characterSheetId: String,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
// Character
|
||||
|
||||
suspend fun characters(): List<CharacterPreviewJson>
|
||||
suspend fun getCharacters(): APIResponse<List<CharacterPreviewJson>>
|
||||
|
||||
suspend fun characterTags(): List<TagJson>
|
||||
suspend fun getCharacterTags(): APIResponse<List<TagJson>>
|
||||
|
||||
suspend fun character(
|
||||
suspend fun getCharacter(
|
||||
characterSheetId: String,
|
||||
): CharacterSheetJson
|
||||
): APIResponse<CharacterSheetJson>
|
||||
|
||||
suspend fun updateCharacter(
|
||||
suspend fun putCharacter(
|
||||
sheet: CharacterSheetJson,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun updateCharacterDamage(
|
||||
suspend fun putCharacterDamage(
|
||||
characterSheetId: String,
|
||||
damage: Int,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun updateCharacterFatigue(
|
||||
suspend fun putCharacterFatigue(
|
||||
characterSheetId: String,
|
||||
fatigue: Int,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun updateCharacterDiminished(
|
||||
suspend fun putCharacterDiminished(
|
||||
characterSheetId: String,
|
||||
diminished: Int,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun updateCharacterAlteration(
|
||||
suspend fun putCharacterAlteration(
|
||||
characterSheetId: String,
|
||||
alterationId: Int,
|
||||
active: Boolean,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun deleteCharacterSheet(
|
||||
characterSheetId: String,
|
||||
)
|
||||
): APIResponse<Unit>
|
||||
|
||||
companion object {
|
||||
fun error(error: APIResponse<*>): Nothing = throw LwaNetworkException(error)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
|||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
|
|
@ -21,114 +22,134 @@ class LwaClientImpl(
|
|||
) : LwaClient {
|
||||
private val root = setting.settings().network.root
|
||||
|
||||
override suspend fun alterations(): List<AlterationJson> = client
|
||||
@Throws
|
||||
override suspend fun getAlterations(): APIResponse<List<AlterationJson>> = client
|
||||
.get("$root/alteration/all")
|
||||
.body()
|
||||
|
||||
override suspend fun alterations(
|
||||
@Throws
|
||||
override suspend fun getAlteration(
|
||||
alterationId: String,
|
||||
): AlterationJson? = client
|
||||
): APIResponse<AlterationJson?> = client
|
||||
.get("$root/alteration/detail?alterationId=$alterationId")
|
||||
.body()
|
||||
|
||||
override suspend fun updateAlteration(
|
||||
alterationJson: AlterationJson
|
||||
@Throws
|
||||
override suspend fun putAlteration(
|
||||
alterationJson: AlterationJson,
|
||||
create: Boolean,
|
||||
) = client
|
||||
.put("$root/alteration/update") {
|
||||
.put("$root/alteration/update?create=$create") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(alterationJson)
|
||||
}
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
@Throws
|
||||
override suspend fun deleteAlteration(alterationId: String) = client
|
||||
.delete("$root/alteration/delete?alterationId=$alterationId")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun alterationTags(): List<TagJson> = client
|
||||
@Throws
|
||||
override suspend fun getAlterationTags(): APIResponse<List<TagJson>> = client
|
||||
.get("$root/alteration/tags")
|
||||
.body()
|
||||
|
||||
override suspend fun campaign(): CampaignJson = client
|
||||
@Throws
|
||||
override suspend fun getCampaign(): APIResponse<CampaignJson> = client
|
||||
.get("$root/campaign")
|
||||
.body()
|
||||
|
||||
override suspend fun campaignAddCharacter(
|
||||
@Throws
|
||||
override suspend fun putCampaignCharacter(
|
||||
characterSheetId: String,
|
||||
) = client
|
||||
.put("$root/campaign/character/add?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun campaignRemoveCharacter(
|
||||
@Throws
|
||||
override suspend fun removeCampaignCharacter(
|
||||
characterSheetId: String,
|
||||
) = client
|
||||
.delete("$root/campaign/character/delete?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun campaignAddNpc(
|
||||
@Throws
|
||||
override suspend fun putCampaignNpc(
|
||||
characterSheetId: String,
|
||||
) = client
|
||||
.put("$root/campaign/npc/add?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun campaignRemoveNpc(
|
||||
@Throws
|
||||
override suspend fun removeCampaignNpc(
|
||||
characterSheetId: String,
|
||||
) = client
|
||||
.delete("$root/campaign/npc/delete?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun characters(): List<CharacterPreviewJson> = client
|
||||
@Throws
|
||||
override suspend fun getCharacters(): APIResponse<List<CharacterPreviewJson>> = client
|
||||
.get("$root/character/all")
|
||||
.body()
|
||||
|
||||
override suspend fun characterTags(): List<TagJson> = client
|
||||
@Throws
|
||||
override suspend fun getCharacterTags(): APIResponse<List<TagJson>> = client
|
||||
.get("$root/character/tags")
|
||||
.body()
|
||||
|
||||
override suspend fun character(
|
||||
@Throws
|
||||
override suspend fun getCharacter(
|
||||
characterSheetId: String,
|
||||
): CharacterSheetJson = client
|
||||
): APIResponse<CharacterSheetJson> = client
|
||||
.get("$root/character/detail?characterSheetId=$characterSheetId")
|
||||
.body()
|
||||
|
||||
override suspend fun updateCharacter(
|
||||
@Throws
|
||||
override suspend fun putCharacter(
|
||||
sheet: CharacterSheetJson,
|
||||
) = client
|
||||
.put("$root/character/update/sheet") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(sheet)
|
||||
}
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun updateCharacterDamage(
|
||||
@Throws
|
||||
override suspend fun putCharacterDamage(
|
||||
characterSheetId: String,
|
||||
damage: Int,
|
||||
) = client
|
||||
.put("$root/character/update/damage?characterSheetId=$characterSheetId&damage=$damage")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun updateCharacterFatigue(
|
||||
@Throws
|
||||
override suspend fun putCharacterFatigue(
|
||||
characterSheetId: String,
|
||||
fatigue: Int,
|
||||
) = client
|
||||
.put("$root/character/update/fatigue?characterSheetId=$characterSheetId&fatigue=$fatigue")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun updateCharacterDiminished(
|
||||
@Throws
|
||||
override suspend fun putCharacterDiminished(
|
||||
characterSheetId: String,
|
||||
diminished: Int,
|
||||
) = client
|
||||
.put("$root/character/update/diminished?characterSheetId=$characterSheetId&diminished=$diminished")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
override suspend fun updateCharacterAlteration(
|
||||
@Throws
|
||||
override suspend fun putCharacterAlteration(
|
||||
characterSheetId: String,
|
||||
alterationId: Int,
|
||||
active: Boolean,
|
||||
) = client
|
||||
.put("$root/character/update/alteration?characterSheetId=$characterSheetId&alterationId=$alterationId&active=$active")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
@Throws
|
||||
override suspend fun deleteCharacterSheet(characterSheetId: String) = client
|
||||
.delete("$root/character/delete?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
.body<APIResponse<Unit>>()
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.pixelized.desktop.lwa.network
|
||||
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
|
||||
class LwaNetworkException(
|
||||
val status: Int,
|
||||
val code: APIResponse.ErrorCode?,
|
||||
message: String,
|
||||
) : Exception(message) {
|
||||
constructor(error: APIResponse<*>) : this(
|
||||
status = error.status,
|
||||
code = error.code,
|
||||
message = error.message ?: "An unknown error occurred"
|
||||
)
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||
|
||||
class AlterationRepository(
|
||||
private val alterationStore: AlterationStore,
|
||||
|
||||
campaignRepository: CampaignRepository,
|
||||
characterRepository: CharacterSheetRepository,
|
||||
) {
|
||||
|
|
@ -26,8 +27,6 @@ class AlterationRepository(
|
|||
|
||||
val alterationFlow get() = alterationStore.alterationsFlow
|
||||
|
||||
val tagsFlow get() = alterationStore.tagsFlow
|
||||
|
||||
/**
|
||||
* This flow transform the campaign instance (player + npc) into a
|
||||
* Map<CharacterSheetId, List<AlterationId>>.
|
||||
|
|
@ -66,11 +65,7 @@ class AlterationRepository(
|
|||
)
|
||||
|
||||
suspend fun updateAlterations() {
|
||||
alterationStore.updateAlterations()
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
alterationStore.updateTags()
|
||||
alterationStore.updateAlterationsFlow()
|
||||
}
|
||||
|
||||
fun alteration(
|
||||
|
|
@ -91,10 +86,15 @@ class AlterationRepository(
|
|||
return activeAlterationMapFlow.map { it[characterSheetId] ?: emptyMap() }
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun updateAlteration(
|
||||
alteration: Alteration
|
||||
alteration: Alteration,
|
||||
create: Boolean,
|
||||
) {
|
||||
alterationStore.putAlteration(alteration)
|
||||
alterationStore.putAlteration(
|
||||
alteration = alteration,
|
||||
create = create,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deleteAlteration(
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import com.pixelized.desktop.lwa.network.LwaClient
|
|||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -17,26 +15,18 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
class AlterationStore(
|
||||
private val networkRepository: NetworkRepository,
|
||||
private val alterationFactory: AlterationJsonFactory,
|
||||
private val tagFactory: TagJsonFactory,
|
||||
private val network: NetworkRepository,
|
||||
private val factory: AlterationJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val _alterationsFlow = MutableStateFlow<Map<String, Alteration>>(emptyMap())
|
||||
val alterationsFlow: StateFlow<Map<String, Alteration>> = _alterationsFlow
|
||||
|
||||
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
scope.launch {
|
||||
updateAlterations()
|
||||
updateTags()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
networkRepository.data.collect(::handleMessage)
|
||||
network.data.collect(::handleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,19 +34,11 @@ class AlterationStore(
|
|||
return alterationsFlow.value.values
|
||||
}
|
||||
|
||||
fun tags(): Collection<Tag> {
|
||||
return tagsFlow.value.values
|
||||
}
|
||||
|
||||
fun alteration(alterationId: String): Alteration? {
|
||||
return alterationsFlow.value[alterationId]
|
||||
}
|
||||
|
||||
fun tag(tagId: String): Tag? {
|
||||
return tagsFlow.value[tagId]
|
||||
}
|
||||
|
||||
suspend fun updateAlterations() {
|
||||
suspend fun updateAlterationsFlow() {
|
||||
_alterationsFlow.value = try {
|
||||
getAlteration()
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -65,7 +47,7 @@ class AlterationStore(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updateAlteration(
|
||||
private suspend fun updateAlterationFlow(
|
||||
alterationId: String,
|
||||
) {
|
||||
val alteration = try {
|
||||
|
|
@ -74,9 +56,9 @@ class AlterationStore(
|
|||
println(exception.message) // TODO proper exception handling
|
||||
null
|
||||
}
|
||||
|
||||
// guard case if getAlteration failed
|
||||
if (alteration == null) return
|
||||
|
||||
// update the flow with the alteration.
|
||||
_alterationsFlow.update { alterations ->
|
||||
alterations.toMutableMap().also {
|
||||
it[alterationId] = alteration
|
||||
|
|
@ -84,60 +66,59 @@ class AlterationStore(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
_tagsFlow.value = try {
|
||||
getAlterationTag()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getAlteration(): Map<String, Alteration> {
|
||||
val request = client.alterations()
|
||||
val data = request.map { alterationFactory.convertFromJson(json = it) }
|
||||
return data.associateBy { it.id }
|
||||
val request = client.getAlterations()
|
||||
return when (request.success) {
|
||||
true -> request.data
|
||||
?.map { factory.convertFromJson(json = it) }
|
||||
?.associateBy { it.id }
|
||||
?: emptyMap()
|
||||
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getAlteration(
|
||||
alterationId: String,
|
||||
): Alteration? {
|
||||
val request = client.alterations(alterationId = alterationId)
|
||||
return request?.let { alterationFactory.convertFromJson(json = it) }
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getAlterationTag(): Map<String, Tag> {
|
||||
val request = client.alterationTags()
|
||||
val data = request.map { tagFactory.convertFromJson(json = it) }
|
||||
return data.associateBy { it.id }
|
||||
val request = client.getAlteration(alterationId = alterationId)
|
||||
return when (request.success) {
|
||||
true -> request.data?.let { factory.convertFromJson(json = it) }
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun putAlteration(
|
||||
alteration: Alteration,
|
||||
create: Boolean,
|
||||
) {
|
||||
client.updateAlteration(
|
||||
alterationJson = alterationFactory.convertToJson(data = alteration)
|
||||
val request = client.putAlteration(
|
||||
alterationJson = factory.convertToJson(data = alteration),
|
||||
create = create,
|
||||
)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteAlteration(
|
||||
alterationId: String
|
||||
alterationId: String,
|
||||
) {
|
||||
client.deleteAlteration(
|
||||
alterationId = alterationId
|
||||
)
|
||||
val request = client.deleteAlteration(alterationId = alterationId)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
// region: WebSocket & data update.
|
||||
|
||||
private suspend fun handleMessage(message: SocketMessage) {
|
||||
when (message) {
|
||||
is ApiSynchronisation.AlterationUpdate -> updateAlteration(
|
||||
is ApiSynchronisation.AlterationUpdate -> updateAlterationFlow(
|
||||
alterationId = message.alterationId,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,33 +3,45 @@ package com.pixelized.desktop.lwa.repository.campaign
|
|||
class CampaignRepository(
|
||||
private val store: CampaignStore,
|
||||
) {
|
||||
val campaignFlow get() = store.campaignFlow
|
||||
val campaignFlow = store.campaignFlow()
|
||||
|
||||
suspend fun updateCampaign() {
|
||||
store.campaign(update = true)
|
||||
store.updateCampaignFlow()
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun addCharacter(
|
||||
characterSheetId: String,
|
||||
) = store.addCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
) {
|
||||
store.addCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun removeCharacter(
|
||||
characterSheetId: String,
|
||||
) = store.removeCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
) {
|
||||
store.removeCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun addNpc(
|
||||
characterSheetId: String,
|
||||
) = store.addNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
) {
|
||||
store.addNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun removeNpc(
|
||||
characterSheetId: String,
|
||||
) = store.removeNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
) {
|
||||
store.removeNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,84 +16,79 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
class CampaignStore(
|
||||
private val client: LwaClient,
|
||||
private val network: NetworkRepository,
|
||||
private val factory: CampaignJsonFactory,
|
||||
|
||||
) {
|
||||
private val _campaignFlow = MutableStateFlow(value = Campaign.empty())
|
||||
val campaignFlow: StateFlow<Campaign> get() = _campaignFlow
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val campaignFlow = MutableStateFlow(value = Campaign.empty())
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
scope.launch {
|
||||
campaign(update = true)
|
||||
}
|
||||
scope.launch {
|
||||
network.data.collect(::handleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun campaign(update: Boolean): Campaign {
|
||||
val campaign = _campaignFlow.value
|
||||
if (update || campaign == Campaign.empty()) {
|
||||
val data = try {
|
||||
val request = client.campaign()
|
||||
factory.convertFromJson(json = request)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
Campaign.empty()
|
||||
}
|
||||
return _campaignFlow.update(data)
|
||||
fun campaignFlow(): StateFlow<Campaign> = campaignFlow
|
||||
|
||||
suspend fun updateCampaignFlow() {
|
||||
campaignFlow.value = try {
|
||||
getCampaign()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
Campaign.empty()
|
||||
}
|
||||
return campaign
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun addCharacter(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
try {
|
||||
client.campaignAddCharacter(
|
||||
characterSheetId = characterSheetId
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
val request = client.putCampaignCharacter(characterSheetId = characterSheetId)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun removeCharacter(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
try {
|
||||
client.campaignRemoveCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
val request = client.removeCampaignCharacter(characterSheetId = characterSheetId)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun addNpc(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
try {
|
||||
client.campaignAddNpc(
|
||||
characterSheetId = characterSheetId
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
val request = client.putCampaignNpc(characterSheetId = characterSheetId)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun removeNpc(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
try {
|
||||
client.campaignRemoveNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
val request = client.removeCampaignNpc(characterSheetId = characterSheetId)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getCampaign(): Campaign {
|
||||
val request = client.getCampaign()
|
||||
return when (request.success) {
|
||||
true -> request.data
|
||||
?.let { factory.convertFromJson(json = it) }
|
||||
?: Campaign.empty()
|
||||
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +98,7 @@ class CampaignStore(
|
|||
when (message) {
|
||||
is CampaignEvent -> {
|
||||
when (message) {
|
||||
is CampaignEvent.CharacterAdded -> _campaignFlow.update { campaign ->
|
||||
is CampaignEvent.CharacterAdded -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
characters = campaign.characters.toMutableSet().also {
|
||||
it.add(message.characterSheetId)
|
||||
|
|
@ -111,7 +106,7 @@ class CampaignStore(
|
|||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.CharacterRemoved -> _campaignFlow.update { campaign ->
|
||||
is CampaignEvent.CharacterRemoved -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
characters = campaign.characters.toMutableSet().also {
|
||||
it.remove(message.characterSheetId)
|
||||
|
|
@ -119,7 +114,7 @@ class CampaignStore(
|
|||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.NpcAdded -> _campaignFlow.update { campaign ->
|
||||
is CampaignEvent.NpcAdded -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
npcs = campaign.npcs.toMutableSet().also {
|
||||
it.add(message.characterSheetId)
|
||||
|
|
@ -127,7 +122,7 @@ class CampaignStore(
|
|||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.NpcRemoved -> _campaignFlow.update { campaign ->
|
||||
is CampaignEvent.NpcRemoved -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
npcs = campaign.npcs.toMutableSet().also {
|
||||
it.remove(message.characterSheetId)
|
||||
|
|
@ -135,7 +130,7 @@ class CampaignStore(
|
|||
)
|
||||
}
|
||||
|
||||
is CampaignEvent.UpdateScene -> _campaignFlow.update { campaign ->
|
||||
is CampaignEvent.UpdateScene -> campaignFlow.update { campaign ->
|
||||
campaign.copy(
|
||||
scene = Campaign.Scene(name = message.name)
|
||||
)
|
||||
|
|
@ -145,17 +140,17 @@ class CampaignStore(
|
|||
|
||||
is GameMasterEvent -> when (message) {
|
||||
is GameMasterEvent.ToggleNpc -> {
|
||||
_campaignFlow.value = _campaignFlow.value.copy(
|
||||
options = _campaignFlow.value.options.copy(
|
||||
showNpcs = _campaignFlow.value.options.showNpcs.not()
|
||||
campaignFlow.value = campaignFlow.value.copy(
|
||||
options = campaignFlow.value.options.copy(
|
||||
showNpcs = campaignFlow.value.options.showNpcs.not()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is GameMasterEvent.TogglePlayer -> {
|
||||
_campaignFlow.value = _campaignFlow.value.copy(
|
||||
options = _campaignFlow.value.options.copy(
|
||||
showParty = _campaignFlow.value.options.showParty.not()
|
||||
campaignFlow.value = campaignFlow.value.copy(
|
||||
options = campaignFlow.value.options.copy(
|
||||
showParty = campaignFlow.value.options.showParty.not()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -168,9 +163,4 @@ class CampaignStore(
|
|||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private fun MutableStateFlow<Campaign>.update(campaign: Campaign): Campaign {
|
||||
value = campaign
|
||||
return campaign
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,46 +18,27 @@ class CharacterSheetRepository(
|
|||
|
||||
val characterSheetPreviewFlow get() = store.previewFlow
|
||||
|
||||
val characterDetailFlow get() = store.detailFlow
|
||||
val tagsFlow get() = store.tagsFlow
|
||||
|
||||
suspend fun updateCharacterPreviews() {
|
||||
store.updateCharactersPreview()
|
||||
store.updateCharactersPreviewFlow()
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
store.updateTags()
|
||||
suspend fun updateCharacterSheet(characterSheetId: String) {
|
||||
store.updateCharacterSheetDetailFlow(characterSheetId = characterSheetId)
|
||||
}
|
||||
|
||||
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
||||
return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId }
|
||||
}
|
||||
|
||||
suspend fun characterDetail(
|
||||
fun characterDetail(
|
||||
characterSheetId: String?,
|
||||
forceUpdate: Boolean = false,
|
||||
): CharacterSheet? {
|
||||
return try {
|
||||
characterSheetId?.let {
|
||||
store.getCharacterSheet(
|
||||
characterSheetId = it,
|
||||
forceUpdate = forceUpdate
|
||||
)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
null
|
||||
}
|
||||
return store.detailFlow.value[characterSheetId]
|
||||
}
|
||||
|
||||
fun characterDetailFlow(
|
||||
characterSheetId: String?,
|
||||
): StateFlow<CharacterSheet?> {
|
||||
val initial = store.detailFlow.value[characterSheetId]
|
||||
|
||||
if (initial == null) {
|
||||
scope.launch { characterDetail(characterSheetId = characterSheetId) }
|
||||
}
|
||||
|
||||
return store.detailFlow
|
||||
.map { sheets ->
|
||||
sheets[characterSheetId]
|
||||
|
|
@ -65,16 +46,18 @@ class CharacterSheetRepository(
|
|||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = initial,
|
||||
initialValue = store.detailFlow.value[characterSheetId],
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun updateCharacter(
|
||||
sheet: CharacterSheet,
|
||||
) {
|
||||
store.updateCharacterSheet(sheet = sheet)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteCharacter(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,9 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
|||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -22,10 +19,9 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class CharacterSheetStore(
|
||||
private val alterationStore: AlterationStore,
|
||||
private val client: LwaClient,
|
||||
private val network: NetworkRepository,
|
||||
private val factory: CharacterSheetJsonFactory,
|
||||
private val tagFactory: TagJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
|
||||
val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow
|
||||
|
|
@ -33,16 +29,8 @@ class CharacterSheetStore(
|
|||
private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
|
||||
val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow
|
||||
|
||||
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// initial data loading.
|
||||
scope.launch {
|
||||
updateCharactersPreview()
|
||||
updateTags()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
network.data.collect(::handleMessage)
|
||||
|
|
@ -51,71 +39,73 @@ class CharacterSheetStore(
|
|||
|
||||
// region Rest
|
||||
|
||||
suspend fun updateCharactersPreview(): List<CharacterSheetPreview> {
|
||||
val request = try {
|
||||
client.characters()
|
||||
suspend fun updateCharactersPreviewFlow() {
|
||||
_previewFlow.value = try {
|
||||
getCharacters()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyList()
|
||||
}
|
||||
val characters = request.map {
|
||||
factory.convertFromJson(it)
|
||||
}
|
||||
return _previewFlow.update(characters)
|
||||
}
|
||||
|
||||
suspend fun updateTags(): Map<String, Tag> {
|
||||
val request = try {
|
||||
client.characterTags()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
emptyList()
|
||||
}
|
||||
val tags = request.map {
|
||||
tagFactory.convertFromJson(json = it)
|
||||
}.associateBy { it.id }
|
||||
|
||||
_tagsFlow.value = tags
|
||||
return tags
|
||||
}
|
||||
|
||||
suspend fun getCharacterSheet(
|
||||
suspend fun updateCharacterSheetDetailFlow(
|
||||
characterSheetId: String,
|
||||
forceUpdate: Boolean = false,
|
||||
): CharacterSheet? {
|
||||
val character = _detailFlow.value[characterSheetId]
|
||||
if (forceUpdate || character == null) {
|
||||
try {
|
||||
val request = client.character(characterSheetId = characterSheetId)
|
||||
val data = factory.convertFromJson(json = request)
|
||||
return _detailFlow.update(data)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
return null
|
||||
) {
|
||||
val characterSheet = try {
|
||||
getCharacterSheet(characterSheetId = characterSheetId)
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
null
|
||||
}
|
||||
if (characterSheet == null) return
|
||||
_detailFlow.update { data ->
|
||||
data.toMutableMap().also {
|
||||
it[characterSheetId] = characterSheet
|
||||
}
|
||||
}
|
||||
return character
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun getCharacters(): List<CharacterSheetPreview> {
|
||||
val request = client.getCharacters()
|
||||
return when (request.success) {
|
||||
true -> request.data
|
||||
?.map { factory.convertFromJson(it) }
|
||||
?: emptyList()
|
||||
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun getCharacterSheet(
|
||||
characterSheetId: String,
|
||||
): CharacterSheet {
|
||||
val request = client.getCharacter(characterSheetId = characterSheetId)
|
||||
return when (request.success) {
|
||||
true -> request.data!!.let { factory.convertFromJson(json = it) }
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun updateCharacterSheet(
|
||||
sheet: CharacterSheet,
|
||||
) {
|
||||
val json = factory.convertToJson(sheet = sheet)
|
||||
try {
|
||||
client.updateCharacter(sheet = json)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO
|
||||
val request = client.putCharacter(sheet = json)
|
||||
if (request.success) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
_detailFlow.update(sheet = sheet)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteCharacterSheet(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
try {
|
||||
client.deleteCharacterSheet(characterSheetId = characterSheetId)
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO
|
||||
val request = client.deleteCharacterSheet(characterSheetId = characterSheetId)
|
||||
if (request.success) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,32 +114,35 @@ class CharacterSheetStore(
|
|||
|
||||
private suspend fun handleMessage(message: SocketMessage) {
|
||||
when (message) {
|
||||
is ApiSynchronisation -> when (message) {
|
||||
is ApiSynchronisation.CharacterSheetUpdate -> {
|
||||
getCharacterSheet(
|
||||
characterSheetId = message.characterSheetId,
|
||||
forceUpdate = true,
|
||||
)
|
||||
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
|
||||
updateCharactersPreview()
|
||||
}
|
||||
}
|
||||
|
||||
is ApiSynchronisation.CharacterSheetDelete -> {
|
||||
_previewFlow.update { previews ->
|
||||
previews.toMutableList().also { sheet ->
|
||||
sheet.removeIf {
|
||||
it.characterSheetId == message.characterSheetId
|
||||
}
|
||||
is ApiSynchronisation -> try {
|
||||
when (message) {
|
||||
is ApiSynchronisation.CharacterSheetUpdate -> {
|
||||
_detailFlow.update(
|
||||
sheet = getCharacterSheet(characterSheetId = message.characterSheetId)
|
||||
)
|
||||
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
|
||||
_previewFlow.value = getCharacters()
|
||||
}
|
||||
}
|
||||
_detailFlow.update { sheets ->
|
||||
sheets.toMutableMap().also { it.remove(message.characterSheetId) }
|
||||
}
|
||||
}
|
||||
|
||||
is ApiSynchronisation.AlterationUpdate -> Unit
|
||||
is ApiSynchronisation.AlterationDelete -> Unit
|
||||
is ApiSynchronisation.CharacterSheetDelete -> {
|
||||
_previewFlow.update { previews ->
|
||||
previews.toMutableList().also { sheet ->
|
||||
sheet.removeIf {
|
||||
it.characterSheetId == message.characterSheetId
|
||||
}
|
||||
}
|
||||
}
|
||||
_detailFlow.update { sheets ->
|
||||
sheets.toMutableMap().also { it.remove(message.characterSheetId) }
|
||||
}
|
||||
}
|
||||
|
||||
is ApiSynchronisation.AlterationUpdate -> Unit
|
||||
is ApiSynchronisation.AlterationDelete -> Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
}
|
||||
|
||||
is CharacterSheetEvent -> when (message) {
|
||||
|
|
@ -190,7 +183,7 @@ class CharacterSheetStore(
|
|||
skillId: String,
|
||||
used: Boolean,
|
||||
) {
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId)
|
||||
val character = sheet.copy(
|
||||
commonSkills = sheet.commonSkills.map { skill ->
|
||||
skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
|
||||
|
|
@ -211,7 +204,7 @@ class CharacterSheetStore(
|
|||
active: Boolean,
|
||||
) {
|
||||
if (alterationStore.alteration(alterationId = alterationId) == null) return
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId)
|
||||
|
||||
val containAlteration = sheet.alterations.contains(alterationId)
|
||||
|
||||
|
|
@ -233,7 +226,7 @@ class CharacterSheetStore(
|
|||
characterSheetId: String,
|
||||
damage: Int,
|
||||
) {
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId)
|
||||
_detailFlow.update(sheet.copy(damage = damage))
|
||||
}
|
||||
|
||||
|
|
@ -241,7 +234,7 @@ class CharacterSheetStore(
|
|||
characterSheetId: String,
|
||||
diminished: Int,
|
||||
) {
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId)
|
||||
_detailFlow.update(sheet.copy(diminished = diminished))
|
||||
}
|
||||
|
||||
|
|
@ -249,18 +242,12 @@ class CharacterSheetStore(
|
|||
characterSheetId: String,
|
||||
fatigue: Int,
|
||||
) {
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return
|
||||
val sheet = getCharacterSheet(characterSheetId = characterSheetId)
|
||||
_detailFlow.update(sheet.copy(fatigue = fatigue))
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private fun MutableStateFlow<List<CharacterSheetPreview>>.update(
|
||||
previews: List<CharacterSheetPreview>,
|
||||
): List<CharacterSheetPreview> {
|
||||
value = previews
|
||||
return previews
|
||||
}
|
||||
|
||||
private fun MutableStateFlow<Map<String, CharacterSheet>>.update(
|
||||
sheet: CharacterSheet,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.pixelized.desktop.lwa.repository.tag
|
||||
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class TagRepository(
|
||||
private val store: TagStore,
|
||||
) {
|
||||
suspend fun updateCharacterTags() = store.updateCharacterTagsFlow()
|
||||
|
||||
suspend fun updateAlterationTags() = store.updateAlterationTagsFlow()
|
||||
|
||||
fun charactersTagFlow(): StateFlow<Map<String, Tag>> = store.charactersTagFlow()
|
||||
|
||||
fun alterationsTagFlow(): StateFlow<Map<String, Tag>> = store.alterationsTagFlow()
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.pixelized.desktop.lwa.repository.tag
|
||||
|
||||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class TagStore(
|
||||
private val factory: TagJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val characterTagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
private val alterationTagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
|
||||
fun charactersTagFlow(): StateFlow<Map<String, Tag>> = characterTagsFlow
|
||||
|
||||
fun characters(): Collection<Tag> {
|
||||
return characterTagsFlow.value.values
|
||||
}
|
||||
|
||||
fun character(tagId: String): Tag? {
|
||||
return characterTagsFlow.value[tagId]
|
||||
}
|
||||
|
||||
fun alterationsTagFlow(): StateFlow<Map<String, Tag>> = alterationTagsFlow
|
||||
|
||||
fun alterations(): Collection<Tag> {
|
||||
return alterationTagsFlow.value.values
|
||||
}
|
||||
|
||||
fun alteration(tagId: String): Tag? {
|
||||
return alterationTagsFlow.value[tagId]
|
||||
}
|
||||
|
||||
suspend fun updateCharacterTagsFlow() {
|
||||
characterTagsFlow.value = try {
|
||||
getCharacterTag()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateAlterationTagsFlow() {
|
||||
alterationTagsFlow.value = try {
|
||||
getAlterationTag()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getCharacterTag(): Map<String, Tag> {
|
||||
val request = client.getCharacterTags()
|
||||
return when (request.success) {
|
||||
true -> request.data
|
||||
?.map { factory.convertFromJson(json = it) }
|
||||
?.associateBy { it.id }
|
||||
?: emptyMap()
|
||||
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun getAlterationTag(): Map<String, Tag> {
|
||||
val request = client.getAlterationTags()
|
||||
return when (request.success) {
|
||||
true -> request.data
|
||||
?.map { factory.convertFromJson(json = it) }
|
||||
?.associateBy { it.id }
|
||||
?: emptyMap()
|
||||
|
||||
else -> LwaClient.error(error = request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,10 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.pixelized.desktop.lwa.LocalErrorSnackHost
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.error__default__action
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
@Stable
|
||||
class ErrorSnackUio(
|
||||
|
|
@ -15,21 +19,21 @@ class ErrorSnackUio(
|
|||
val duration: SnackbarDuration,
|
||||
) {
|
||||
companion object {
|
||||
fun from(exception: Exception) = ErrorSnackUio(
|
||||
suspend fun from(exception: Exception) = ErrorSnackUio(
|
||||
message = exception.localizedMessage,
|
||||
action = "Ok",
|
||||
duration = SnackbarDuration.Indefinite
|
||||
action = getString(Res.string.error__default__action),
|
||||
duration = SnackbarDuration.Long,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorSnack(
|
||||
fun ErrorSnackHandler(
|
||||
snack: SnackbarHostState = LocalErrorSnackHost.current,
|
||||
error: SharedFlow<ErrorSnackUio>,
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
error.collect {
|
||||
error.collectLatest {
|
||||
snack.showSnackbar(
|
||||
message = it.message,
|
||||
actionLabel = it.action,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.textfield
|
||||
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.TextFieldColors
|
||||
|
|
@ -9,20 +10,21 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
|
||||
import com.pixelized.desktop.lwa.utils.rememberKeyboardActions
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@Stable
|
||||
data class LwaTextFieldUio(
|
||||
val enable: Boolean = true,
|
||||
val isError: StateFlow<Boolean>,
|
||||
val labelFlow: StateFlow<String?>?,
|
||||
val valueFlow: StateFlow<String>,
|
||||
val placeHolderFlow: StateFlow<String?>?,
|
||||
val isError: MutableStateFlow<Boolean>,
|
||||
val labelFlow: MutableStateFlow<String?>?,
|
||||
val valueFlow: MutableStateFlow<String>,
|
||||
val placeHolderFlow: MutableStateFlow<String?>?,
|
||||
val onValueChange: (String) -> Unit,
|
||||
)
|
||||
|
||||
|
|
@ -30,6 +32,7 @@ data class LwaTextFieldUio(
|
|||
fun LwaTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
colors: TextFieldColors = LwaTextFieldColors(),
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
singleLine: Boolean = true,
|
||||
|
|
@ -51,6 +54,7 @@ fun LwaTextField(
|
|||
TextField(
|
||||
modifier = localModifier.then(other = modifier),
|
||||
colors = colors,
|
||||
shape = shape,
|
||||
keyboardActions = rememberKeyboardActions {
|
||||
focus.moveFocus(FocusDirection.Next)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditScreen
|
||||
import com.pixelized.desktop.lwa.utils.extention.ARG
|
||||
|
||||
@Stable
|
||||
|
|
@ -44,7 +44,7 @@ fun NavGraphBuilder.composableGameMasterAlterationEditPage() {
|
|||
route = GMAlterationEditDestination.baseRoute(),
|
||||
arguments = GMAlterationEditDestination.arguments(),
|
||||
) {
|
||||
GMAlterationEditPage()
|
||||
GMAlterationEditScreen()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
||||
|
||||
@Stable
|
||||
object GameMasterDestination {
|
||||
private const val ROUTE = "GameMasterMain"
|
||||
|
||||
fun baseRoute() = ROUTE
|
||||
|
||||
@Stable
|
||||
fun navigationRoute() = ROUTE
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableGameMasterMainPage() {
|
||||
composable(
|
||||
route = GameMasterDestination.baseRoute(),
|
||||
) {
|
||||
GameMasterScreen()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToGameMasterMainPage() {
|
||||
val route = GameMasterDestination.navigationRoute()
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.LocalSnackHost
|
||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnack
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -148,7 +148,7 @@ fun NetworkDialog(
|
|||
}
|
||||
}
|
||||
|
||||
ErrorSnack(
|
||||
ErrorSnackHandler(
|
||||
error = viewModel.networkError,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GameMasterDestination
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterMainPage
|
||||
|
||||
val LocalGMScreenController = compositionLocalOf<NavHostController> {
|
||||
error("GameMaster NavHost controller is not yet ready")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GameMasterNavHost() {
|
||||
val controller = rememberNavController()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalScreenController provides controller,
|
||||
LocalGMScreenController provides rememberNavController(),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
NavHost(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
navController = controller,
|
||||
startDestination = GameMasterDestination.navigationRoute(),
|
||||
) {
|
||||
composableGameMasterMainPage()
|
||||
composableGameMasterAlterationEditPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ import androidx.compose.material.Switch
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -28,7 +27,6 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMActionDestination
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage
|
||||
|
|
@ -52,35 +50,24 @@ import org.koin.compose.viewmodel.koinViewModel
|
|||
@Composable
|
||||
fun GameMasterScreen(
|
||||
gameMasterViewModel: GameMasterViewModel = koinViewModel(),
|
||||
|
||||
controller: NavHostController = LocalGMScreenController.current,
|
||||
) {
|
||||
val screen = rememberNavController()
|
||||
val gameMaster = gameMasterViewModel.isGameMaster.collectAsState()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalScreenController provides screen,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Box {
|
||||
GameMasterContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
controller = screen,
|
||||
gameMaster = gameMaster,
|
||||
onGameMaster = gameMasterViewModel::onGameMaster,
|
||||
onTab = {
|
||||
when (it) {
|
||||
GMTabUio.Actions -> screen.navigateToGameMasterActionPage()
|
||||
GMTabUio.Characters -> screen.navigateToGameMasterCharacterPage()
|
||||
GMTabUio.Alterations -> screen.navigateToGameMasterAlterationPage()
|
||||
GMTabUio.Objects -> screen.navigateToGameMasterObjectPage()
|
||||
}
|
||||
},
|
||||
)
|
||||
GameMasterContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
controller = controller,
|
||||
gameMaster = gameMaster,
|
||||
onGameMaster = gameMasterViewModel::onGameMaster,
|
||||
onTab = {
|
||||
when (it) {
|
||||
GMTabUio.Actions -> controller.navigateToGameMasterActionPage()
|
||||
GMTabUio.Characters -> controller.navigateToGameMasterCharacterPage()
|
||||
GMTabUio.Alterations -> controller.navigateToGameMasterAlterationPage()
|
||||
GMTabUio.Objects -> controller.navigateToGameMasterObjectPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,34 +1,39 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_expression
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_id
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_id
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_label
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_tags
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import java.util.UUID
|
||||
|
||||
class GMAlterationEditFactory(
|
||||
private val expressionParser: ExpressionParser,
|
||||
private val tagFactory: GMTagFactory,
|
||||
) {
|
||||
suspend fun createForm(
|
||||
originId: String?,
|
||||
alteration: Alteration?,
|
||||
tags: Collection<Tag>,
|
||||
): GMAlterationEditPageUio {
|
||||
val id = MutableStateFlow(alteration?.id ?: "")
|
||||
val label = MutableStateFlow(alteration?.metadata?.name ?: "")
|
||||
val description = MutableStateFlow(alteration?.metadata?.description ?: "")
|
||||
val tags = MutableStateFlow(alteration?.tags?.joinToString(", ") { it } ?: "")
|
||||
val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(createField(null)))
|
||||
val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(
|
||||
createField(null)
|
||||
))
|
||||
|
||||
return GMAlterationEditPageUio(
|
||||
id = LwaTextFieldUio(
|
||||
enable = true,
|
||||
enable = originId == null,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)),
|
||||
valueFlow = id,
|
||||
|
|
@ -51,13 +56,9 @@ class GMAlterationEditFactory(
|
|||
placeHolderFlow = null,
|
||||
onValueChange = { description.value = it },
|
||||
),
|
||||
tags = LwaTextFieldUio(
|
||||
enable = true,
|
||||
isError = MutableStateFlow(false),
|
||||
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_tags)),
|
||||
valueFlow = tags,
|
||||
placeHolderFlow = null,
|
||||
onValueChange = { tags.value = it },
|
||||
tags = tagFactory.convertToGMTagItemUio(
|
||||
tags = tags,
|
||||
selectedTagIds = alteration?.tags ?: emptyList(),
|
||||
),
|
||||
fields = fields,
|
||||
)
|
||||
|
|
@ -101,7 +102,7 @@ class GMAlterationEditFactory(
|
|||
name = form.label.valueFlow.value,
|
||||
description = form.description.valueFlow.value,
|
||||
),
|
||||
tags = form.tags.valueFlow.value.split(","),
|
||||
tags = form.tags.filter { it.highlight }.map { it.id },
|
||||
fields = form.fields.value.mapNotNull { field ->
|
||||
expressionParser.parse(input = field.expression.valueFlow.value)?.let {
|
||||
Alteration.Field(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -11,21 +18,31 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -34,19 +51,27 @@ import androidx.compose.ui.input.key.Key
|
|||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagButton
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_add_field
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_cancel
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_save
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__title
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_save_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -57,7 +82,7 @@ data class GMAlterationEditPageUio(
|
|||
val id: LwaTextFieldUio,
|
||||
val label: LwaTextFieldUio,
|
||||
val description: LwaTextFieldUio,
|
||||
val tags: LwaTextFieldUio,
|
||||
val tags: List<GMTagUio>,
|
||||
val fields: MutableStateFlow<List<SkillUio>>,
|
||||
) {
|
||||
@Stable
|
||||
|
|
@ -74,44 +99,45 @@ object GMAlterationEditPageDefault {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun GMAlterationEditPage(
|
||||
fun GMAlterationEditScreen(
|
||||
viewModel: GMAlterationEditViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val form = viewModel.form.collectAsState()
|
||||
|
||||
AnimatedContent(
|
||||
targetState = form.value,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
when (it) {
|
||||
null -> Box(modifier = Modifier.fillMaxSize())
|
||||
else -> GMAlterationEditContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
form = it,
|
||||
paddings = GMAlterationEditPageDefault.paddings,
|
||||
addField = {
|
||||
scope.launch {
|
||||
viewModel.addField()
|
||||
}
|
||||
},
|
||||
removeField = viewModel::removeField,
|
||||
onSave = {
|
||||
scope.launch {
|
||||
viewModel.save()
|
||||
}
|
||||
},
|
||||
onCancel = {
|
||||
screen.popBackStack()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
GMAlterationEditContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
form = form,
|
||||
paddings = GMAlterationEditPageDefault.paddings,
|
||||
onBack = {
|
||||
screen.navigateBack()
|
||||
},
|
||||
addField = {
|
||||
scope.launch {
|
||||
viewModel.addField()
|
||||
}
|
||||
},
|
||||
removeField = viewModel::removeField,
|
||||
onSave = {
|
||||
scope.launch {
|
||||
if (viewModel.save()) {
|
||||
screen.navigateBack()
|
||||
}
|
||||
}
|
||||
},
|
||||
onTag = { tag ->
|
||||
viewModel.addTag(tag = tag)
|
||||
},
|
||||
)
|
||||
|
||||
ErrorSnackHandler(
|
||||
error = viewModel.error,
|
||||
)
|
||||
|
||||
AlterationEditKeyHandler(
|
||||
onDismissRequest = {
|
||||
screen.popBackStack()
|
||||
screen.navigateBack()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -119,148 +145,218 @@ fun GMAlterationEditPage(
|
|||
@Composable
|
||||
private fun GMAlterationEditContent(
|
||||
modifier: Modifier = Modifier,
|
||||
form: GMAlterationEditPageUio,
|
||||
scope: CoroutineScope = rememberCoroutineScope(),
|
||||
tagsState: LazyListState = rememberLazyListState(),
|
||||
form: State<GMAlterationEditPageUio?>,
|
||||
paddings: PaddingValues,
|
||||
onBack: () -> Unit,
|
||||
addField: () -> Unit,
|
||||
removeField: (index: Int) -> Unit,
|
||||
onSave: () -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
onTag: (GMTagUio) -> Unit,
|
||||
) {
|
||||
val fields = form.fields.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
contentPadding = paddings,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
item(
|
||||
key = "Id",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.id,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Name",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.label,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Description",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.description,
|
||||
singleLine = false,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Tags",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = form.tags,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
itemsIndexed(
|
||||
items = fields.value,
|
||||
key = { _, item -> item.key },
|
||||
) { index, item ->
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.id,
|
||||
singleLine = true,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.expression,
|
||||
singleLine = true,
|
||||
)
|
||||
IconButton(
|
||||
onClick = { removeField(index) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(Res.string.game_master__alteration__title),
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
TextButton(
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = {
|
||||
AnimatedContent(
|
||||
targetState = form.value,
|
||||
transitionSpec = {
|
||||
if (initialState?.id == targetState?.id) {
|
||||
EnterTransition.None togetherWith ExitTransition.None
|
||||
} else {
|
||||
fadeIn() togetherWith fadeOut()
|
||||
}
|
||||
}
|
||||
) {
|
||||
when (it) {
|
||||
null -> Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
else -> {
|
||||
val fields = it.fields.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = paddings,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
item(
|
||||
key = "Id",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = it.id,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Name",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = it.label,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Description",
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
field = it.description,
|
||||
singleLine = false,
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Tags",
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
tagsState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = tagsState,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
items(
|
||||
items = it.tags,
|
||||
) { tag ->
|
||||
GMTagButton(
|
||||
modifier = Modifier.height(48.dp),
|
||||
tag = tag,
|
||||
onTag = { onTag(tag) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
itemsIndexed(
|
||||
items = fields.value,
|
||||
key = { _, item -> item.key },
|
||||
) { index, item ->
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.id,
|
||||
singleLine = true,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.weight(1f),
|
||||
field = item.expression,
|
||||
singleLine = true,
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.size(size = 56.dp)
|
||||
.background(
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
),
|
||||
onClick = { removeField(index) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item(
|
||||
key = "Actions",
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = addField,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_add_field),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item(
|
||||
key = "Actions",
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = addField,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_add_field),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_save),
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_save_24dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onCancel,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__alteration__edit_field_cancel),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -277,4 +373,9 @@ private fun AlterationEditKeyHandler(
|
|||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavHostController.navigateBack() = popBackStack(
|
||||
route = GMAlterationEditDestination.baseRoute(),
|
||||
inclusive = true,
|
||||
)
|
||||
|
|
@ -3,15 +3,23 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.network.LwaNetworkException
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse.ErrorCode
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GMAlterationEditViewModel(
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val tagRepository: TagRepository,
|
||||
private val factory: GMAlterationEditFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
|
@ -20,24 +28,40 @@ class GMAlterationEditViewModel(
|
|||
private val _form = MutableStateFlow<GMAlterationEditPageUio?>(null)
|
||||
val form: StateFlow<GMAlterationEditPageUio?> get() = _form
|
||||
|
||||
private val _error = MutableSharedFlow<ErrorSnackUio>()
|
||||
val error: SharedFlow<ErrorSnackUio> get() = _error
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
_form.value = factory.createForm(
|
||||
alteration = alterationRepository.alteration(alterationId = argument.id)
|
||||
originId = argument.id,
|
||||
alteration = alterationRepository.alteration(alterationId = argument.id),
|
||||
tags = tagRepository.alterationsTagFlow().value.values,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save() {
|
||||
val edited = factory.createAlteration(form = form.value)
|
||||
suspend fun save(): Boolean {
|
||||
val edited = factory.createAlteration(form = form.value) ?: return false
|
||||
|
||||
val actual = alterationRepository.alterationFlow.value[edited?.id]
|
||||
try {
|
||||
alterationRepository.updateAlteration(
|
||||
alteration = edited,
|
||||
create = argument.id == null,
|
||||
)
|
||||
return true
|
||||
} catch (exception: LwaNetworkException) {
|
||||
_form.value?.id?.isError?.value = exception.code == ErrorCode.AlterationId
|
||||
_form.value?.label?.isError?.value = exception.code == ErrorCode.AlterationName
|
||||
|
||||
// TODO if argument.id == null et actual?.id != null on créer et on écrase existant !!!
|
||||
|
||||
if (edited != null)
|
||||
|
||||
alterationRepository.updateAlteration(edited)
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(message)
|
||||
return false
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addField() {
|
||||
|
|
@ -55,4 +79,16 @@ class GMAlterationEditViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTag(tag: GMTagUio) {
|
||||
_form.update {
|
||||
it?.copy(
|
||||
tags = it.tags.toMutableList().also { tags ->
|
||||
val index = tags.indexOf(tag)
|
||||
if (index > -1)
|
||||
tags[index] = tag.copy(highlight = tag.highlight.not())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
|
|
@ -22,6 +23,7 @@ import org.jetbrains.compose.resources.getString
|
|||
class GMAlterationViewModel(
|
||||
private val alterationRepository: AlterationRepository,
|
||||
alterationFactory: GMAlterationFactory,
|
||||
tagRepository: TagRepository,
|
||||
tagFactory: GMTagFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -38,7 +40,7 @@ class GMAlterationViewModel(
|
|||
)
|
||||
|
||||
val tags: StateFlow<List<GMTagUio>> = combine(
|
||||
alterationRepository.tagsFlow,
|
||||
tagRepository.alterationsTagFlow(),
|
||||
selectedTagId,
|
||||
) { tags, selectedTagId ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
|
|
@ -53,7 +55,7 @@ class GMAlterationViewModel(
|
|||
|
||||
val alterations: StateFlow<List<GMAlterationUio>> = combine(
|
||||
alterationRepository.alterationFlow,
|
||||
alterationRepository.tagsFlow,
|
||||
tagRepository.alterationsTagFlow(),
|
||||
filter.valueFlow.map { it.unAccent() },
|
||||
selectedTagId,
|
||||
) { alterations, tags, unAccentFilter, selectedTagId ->
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
|
|
@ -27,6 +28,7 @@ class GMCharacterViewModel(
|
|||
private val networkRepository: NetworkRepository,
|
||||
private val campaignRepository: CampaignRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
tagRepository: TagRepository,
|
||||
private val factory: GMCharacterFactory,
|
||||
private val tagFactory: GMTagFactory,
|
||||
) : ViewModel() {
|
||||
|
|
@ -44,7 +46,7 @@ class GMCharacterViewModel(
|
|||
)
|
||||
|
||||
val tags = combine(
|
||||
characterSheetRepository.tagsFlow,
|
||||
tagRepository.charactersTagFlow(),
|
||||
selectedTagId,
|
||||
) { tags, selectedTagId ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.graphics.Shape
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
||||
|
||||
|
|
@ -61,4 +63,36 @@ fun GMTag(
|
|||
text = tag.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun GMTagButton(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = GmTagDefault.padding,
|
||||
shape: Shape = MaterialTheme.shapes.small,
|
||||
tag: GMTagUio,
|
||||
onTag: (() -> Unit)? = null,
|
||||
) {
|
||||
val animatedColor = animateColorAsState(
|
||||
when (tag.highlight) {
|
||||
true -> MaterialTheme.lwa.colorScheme.base.secondary
|
||||
else -> MaterialTheme.lwa.colorScheme.base.onSurface
|
||||
}
|
||||
)
|
||||
Button(
|
||||
modifier = modifier,
|
||||
colors = LwaButtonColors(contentColor = animatedColor.value),
|
||||
shape = shape,
|
||||
enabled = onTag != null,
|
||||
onClick = { onTag?.invoke() },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
color = animatedColor.value,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = tag.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,23 @@ import java.text.Collator
|
|||
|
||||
class GMTagFactory {
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
tags: Collection<Tag>,
|
||||
selectedTagIds: List<String>,
|
||||
): List<GMTagUio> {
|
||||
return tags
|
||||
.map { tag ->
|
||||
GMTagUio(
|
||||
id = tag.id,
|
||||
label = tag.label,
|
||||
highlight = selectedTagIds.contains(tag.id),
|
||||
)
|
||||
}
|
||||
.sortedWith(
|
||||
comparator = compareBy(Collator.getInstance()) { it.label }
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToGMTagItemUio(
|
||||
tags: Collection<Tag>,
|
||||
selectedTagId: String?,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ fun LwaTheme(
|
|||
MaterialTheme(
|
||||
colors = lwaColors.base,
|
||||
typography = MaterialTheme.typography,
|
||||
shapes = MaterialTheme.shapes,
|
||||
shapes = lwaShapes.base,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import androidx.compose.material.ContentAlpha
|
|||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun LwaButtonColors(): ButtonColors = ButtonDefaults.buttonColors(
|
||||
fun LwaButtonColors(
|
||||
contentColor: Color = MaterialTheme.colors.primary,
|
||||
): ButtonColors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.surface,
|
||||
contentColor = MaterialTheme.colors.primary,
|
||||
contentColor = contentColor,
|
||||
disabledContentColor = MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled),
|
||||
)
|
||||
|
||||
)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.ui.theme.shapes
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -9,6 +10,7 @@ import androidx.compose.ui.unit.dp
|
|||
|
||||
@Stable
|
||||
data class LwaShapes(
|
||||
val base: Shapes,
|
||||
val portrait: Shape,
|
||||
val panel: Shape,
|
||||
val settings: Shape,
|
||||
|
|
@ -18,12 +20,16 @@ data class LwaShapes(
|
|||
@Stable
|
||||
@Composable
|
||||
fun lwaShapes(
|
||||
base: Shapes = Shapes(
|
||||
small = RoundedCornerShape(4.dp),
|
||||
),
|
||||
portrait: Shape = RoundedCornerShape(8.dp),
|
||||
panel: Shape = RoundedCornerShape(8.dp),
|
||||
settings: Shape = RoundedCornerShape(8.dp),
|
||||
gameMaster: Shape = RoundedCornerShape(8.dp),
|
||||
): LwaShapes = remember {
|
||||
LwaShapes(
|
||||
base = base,
|
||||
portrait = portrait,
|
||||
panel = panel,
|
||||
settings = settings,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -46,11 +46,21 @@ class AlterationService(
|
|||
return alterationHashFlow.value[alterationId]
|
||||
}
|
||||
|
||||
fun update(json: AlterationJson) {
|
||||
alterationStore.save(alteration = json)
|
||||
@Throws
|
||||
fun save(
|
||||
json: AlterationJson,
|
||||
create: Boolean,
|
||||
) {
|
||||
alterationStore.save(
|
||||
json = json,
|
||||
create = create,
|
||||
)
|
||||
}
|
||||
|
||||
fun delete(alterationId: String): Boolean {
|
||||
return alterationStore.delete(id = alterationId)
|
||||
@Throws
|
||||
fun delete(alterationId: String) {
|
||||
return alterationStore.delete(
|
||||
id = alterationId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
package com.pixelized.server.lwa.model.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
import com.pixelized.server.lwa.server.exception.JsonCodingException
|
||||
import com.pixelized.server.lwa.server.exception.JsonConversionException
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -13,6 +19,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.text.Collator
|
||||
|
||||
class AlterationStore(
|
||||
private val pathProvider: PathProvider,
|
||||
|
|
@ -28,17 +35,17 @@ class AlterationStore(
|
|||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// load the initial data
|
||||
scope.launch {
|
||||
updateAlterations()
|
||||
updateAlterationFlow()
|
||||
}
|
||||
}
|
||||
|
||||
fun alterationsFlow(): StateFlow<List<Alteration>> = alterationFlow
|
||||
|
||||
private fun updateAlterations() {
|
||||
private fun updateAlterationFlow() {
|
||||
alterationFlow.value = try {
|
||||
loadAlterations()
|
||||
load()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +54,9 @@ class AlterationStore(
|
|||
FileReadException::class,
|
||||
JsonConversionException::class,
|
||||
)
|
||||
private fun loadAlterations(): List<Alteration> {
|
||||
private fun load(
|
||||
directory: File = this.directory,
|
||||
): List<Alteration> {
|
||||
return directory
|
||||
.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
|
|
@ -60,85 +69,118 @@ class AlterationStore(
|
|||
if (json.isBlank()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
// decode the file
|
||||
val data = try {
|
||||
this.json.decodeFromString<AlterationJson>(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// parse the json string.
|
||||
try {
|
||||
val data = this.json.decodeFromString<AlterationJson>(json)
|
||||
factory.convertFromJson(data)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
}
|
||||
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
@Throws(JsonConversionException::class, FileWriteException::class)
|
||||
@Throws(
|
||||
BusinessException::class,
|
||||
JsonConversionException::class,
|
||||
JsonCodingException::class,
|
||||
FileWriteException::class,
|
||||
)
|
||||
fun save(
|
||||
alteration: Alteration,
|
||||
json: AlterationJson,
|
||||
create: Boolean,
|
||||
) {
|
||||
val json = try {
|
||||
factory.convertToJson(data = alteration)
|
||||
val file = alterationFile(id = json.id)
|
||||
// Guard case on update alteration
|
||||
if (create && file.exists()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration already exist, creation is impossible.",
|
||||
)
|
||||
}
|
||||
// Transform the json into the model.
|
||||
val alteration = try {
|
||||
factory.convertFromJson(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
|
||||
save(alteration = json)
|
||||
}
|
||||
|
||||
@Throws(FileWriteException::class)
|
||||
fun save(
|
||||
alteration: AlterationJson,
|
||||
) {
|
||||
// encode the json into a string
|
||||
if (alteration.id.isEmpty()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration 'id' is a mandatory field.",
|
||||
code = APIResponse.ErrorCode.AlterationId,
|
||||
)
|
||||
}
|
||||
if (alteration.metadata.name.isEmpty()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration 'name' is a mandatory field.",
|
||||
code = APIResponse.ErrorCode.AlterationName,
|
||||
)
|
||||
}
|
||||
// Encode the json into a string.
|
||||
val data = try {
|
||||
json.encodeToString(alteration)
|
||||
this.json.encodeToString(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// write the alteration into a file.
|
||||
// Write the alteration into a file.
|
||||
try {
|
||||
val file = alterationFile(id = alteration.id)
|
||||
file.writeText(
|
||||
text = data,
|
||||
charset = Charsets.UTF_8,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw FileWriteException(
|
||||
root = exception
|
||||
)
|
||||
throw FileWriteException(root = exception)
|
||||
}
|
||||
// Update the dataflow.
|
||||
alterationFlow.update { alterations ->
|
||||
val index = alterations.indexOfFirst { it.id == alteration.id }
|
||||
val alt = factory.convertFromJson(alteration)
|
||||
|
||||
alterations.toMutableList().also {
|
||||
if (index >= 0) {
|
||||
it[index] = alt
|
||||
} else {
|
||||
it.add(alt)
|
||||
val index = alterations.indexOfFirst { it.id == json.id }
|
||||
alterations.toMutableList()
|
||||
.also {
|
||||
if (index >= 0) {
|
||||
it[index] = alteration
|
||||
} else {
|
||||
it.add(alteration)
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy(Collator.getInstance()) {
|
||||
it.metadata.name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(id: String): Boolean {
|
||||
@Throws(BusinessException::class)
|
||||
fun delete(id: String) {
|
||||
val file = alterationFile(id = id)
|
||||
val deleted = file.delete()
|
||||
if (deleted) {
|
||||
alterationFlow.update { alterations ->
|
||||
alterations.toMutableList().also { alteration ->
|
||||
// Guard case on the file existence.
|
||||
if (file.exists().not()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration doesn't not exist, deletion is impossible.",
|
||||
)
|
||||
}
|
||||
// Guard case on the file deletion
|
||||
if (file.delete().not()) {
|
||||
throw BusinessException(
|
||||
message = "Alteration file have not been deleted for unknown reason.",
|
||||
)
|
||||
}
|
||||
// Update the data model with the deleted alteration.
|
||||
alterationFlow.update { alterations ->
|
||||
alterations.toMutableList()
|
||||
.also { alteration ->
|
||||
alteration.removeIf { it.id == id }
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy(Collator.getInstance()) {
|
||||
it.metadata.name
|
||||
})
|
||||
}
|
||||
return deleted
|
||||
}
|
||||
|
||||
private fun alterationFile(id: String): File {
|
||||
return File("${pathProvider.alterationsPath()}${id}.json")
|
||||
}
|
||||
|
||||
sealed class AlterationStoreException(root: Exception) : Exception(root)
|
||||
class JsonConversionException(root: Exception) : AlterationStoreException(root)
|
||||
class FileWriteException(root: Exception) : AlterationStoreException(root)
|
||||
class FileReadException(root: Exception) : AlterationStoreException(root)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.server.lwa.model.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
|
|
@ -37,223 +38,91 @@ class CampaignService(
|
|||
return campaignJsonFlow.value
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun addCharacter(
|
||||
characterSheetId: String,
|
||||
): Boolean {
|
||||
// check if the character is already in the campaign.
|
||||
if (campaign.characters.contains(characterSheetId)) return false
|
||||
// update the corresponding instance
|
||||
val characters = campaign.characters.toMutableSet().also { it.add(characterSheetId) }
|
||||
// save the campaign to the disk (update the flow).
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(characters = characters)
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
) {
|
||||
// Check if the character is already in the campaign.
|
||||
if (campaign.instances.contains(characterSheetId)) {
|
||||
throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addNpc(
|
||||
characterSheetId: String,
|
||||
): Boolean {
|
||||
// check if the character is already in the campaign.
|
||||
if (campaign.npcs.contains(characterSheetId)) return false
|
||||
// update the corresponding instance
|
||||
val characters = campaign.npcs.toMutableSet().also { it.add(characterSheetId) }
|
||||
// save the campaign to the disk (update the flow).
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(npcs = characters)
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
// Update the corresponding instance
|
||||
val characters = campaign.characters.toMutableSet().also {
|
||||
it.add(characterSheetId)
|
||||
}
|
||||
// Save the campaign to the disk (update the flow).
|
||||
store.save(
|
||||
campaign = campaign.copy(characters = characters)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun removeCharacter(
|
||||
@Throws
|
||||
fun addNpc(
|
||||
characterSheetId: String,
|
||||
): Boolean {
|
||||
// check if the character is in the campaign.
|
||||
if (campaign.characters.contains(characterSheetId).not()) return false
|
||||
// update the corresponding instance
|
||||
val characters = campaign.characters.toMutableSet().also { it.remove(characterSheetId) }
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(characters = characters)
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
) {
|
||||
// Check if the character is already in the campaign.
|
||||
if (campaign.instances.contains(characterSheetId)) {
|
||||
throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeNpc(
|
||||
characterSheetId: String,
|
||||
): Boolean {
|
||||
// check if the character is in the campaign.
|
||||
if (campaign.npcs.contains(characterSheetId).not()) return false
|
||||
// update the corresponding instance
|
||||
val characters = campaign.npcs.toMutableSet().also { it.remove(characterSheetId) }
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign = campaign.copy(npcs = characters)
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
// Update the corresponding instance
|
||||
val characters = campaign.npcs.toMutableSet().also {
|
||||
it.add(characterSheetId)
|
||||
}
|
||||
// Save the campaign to the disk (update the flow).
|
||||
store.save(
|
||||
campaign = campaign.copy(npcs = characters)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun removeInstance(
|
||||
@Throws
|
||||
fun removeCharacter(
|
||||
characterSheetId: String,
|
||||
): Boolean {
|
||||
return removeCharacter(characterSheetId) || removeNpc(characterSheetId)
|
||||
) {
|
||||
// Check if the character is in the campaign.
|
||||
if (campaign.characters.contains(characterSheetId).not()) {
|
||||
throw BusinessException(message = "Character with id:$characterSheetId is not in the party.")
|
||||
}
|
||||
// Update the corresponding instance
|
||||
val characters = campaign.characters.toMutableSet().also {
|
||||
it.remove(characterSheetId)
|
||||
}
|
||||
// Save the campaign to the disk + update the flow.
|
||||
store.save(
|
||||
campaign = campaign.copy(characters = characters)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun setScene(
|
||||
@Throws
|
||||
fun removeNpc(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
// Check if the character is in the campaign.
|
||||
if (campaign.npcs.contains(characterSheetId).not()) {
|
||||
throw BusinessException(message = "Character with id:$characterSheetId is not in the npcs.")
|
||||
}
|
||||
// Update the corresponding instance
|
||||
val characters = campaign.npcs.toMutableSet().also {
|
||||
it.remove(characterSheetId)
|
||||
}
|
||||
// Save the campaign to the disk + update the flow.
|
||||
store.save(
|
||||
campaign = campaign.copy(npcs = characters)
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun setScene(
|
||||
scene: Campaign.Scene,
|
||||
): Boolean {
|
||||
) {
|
||||
// save the campaign to the disk + update the flow.
|
||||
return try {
|
||||
store.save(
|
||||
campaign.copy(scene = scene)
|
||||
)
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
}
|
||||
store.save(
|
||||
campaign = campaign.copy(scene = scene)
|
||||
)
|
||||
}
|
||||
|
||||
// Data manipulation through WebSocket.
|
||||
|
||||
// suspend fun updateCharacteristic(
|
||||
// characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
// characteristic: Campaign.CharacterInstance.Characteristic,
|
||||
// value: Int,
|
||||
// ) {
|
||||
// when (characterInstanceId.prefix) {
|
||||
// Campaign.CharacterInstance.Id.PLAYER -> {
|
||||
// // fetch all the current campaign character
|
||||
// val characters = campaign.characters.toMutableMap()
|
||||
// // update the corresponding character using the use case.
|
||||
// characters[characterInstanceId] = useCase.updateCharacteristic(
|
||||
// instance = campaign.character(id = characterInstanceId),
|
||||
// characteristic = characteristic,
|
||||
// value = value,
|
||||
// )
|
||||
// // save the campaign to the disk + update the flow.
|
||||
// store.save(
|
||||
// campaign = campaign.copy(characters = characters)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// Campaign.CharacterInstance.Id.NPC -> {
|
||||
// // fetch all the current campaign character
|
||||
// val npcs = campaign.npcs.toMutableMap()
|
||||
// // update the corresponding character using the use case.
|
||||
// npcs[characterInstanceId] = useCase.updateCharacteristic(
|
||||
// instance = campaign.npc(id = characterInstanceId),
|
||||
// characteristic = characteristic,
|
||||
// value = value,
|
||||
// )
|
||||
// // save the campaign to the disk + update the flow.
|
||||
// store.save(
|
||||
// campaign = campaign.copy(npcs = npcs)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// suspend fun updateDiminished(
|
||||
// characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
// diminished: Int,
|
||||
// ) {
|
||||
// when (characterInstanceId.prefix) {
|
||||
// Campaign.CharacterInstance.Id.PLAYER -> {
|
||||
// // fetch all the current campaign character
|
||||
// val characters = campaign.characters.toMutableMap()
|
||||
// // update the corresponding character using the use case.
|
||||
// characters[characterInstanceId] = useCase.updateDiminished(
|
||||
// instance = campaign.character(id = characterInstanceId),
|
||||
// diminished = diminished,
|
||||
// )
|
||||
// // save the campaign to the disk + update the flow.
|
||||
// store.save(
|
||||
// campaign = campaign.copy(characters = characters)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// Campaign.CharacterInstance.Id.NPC -> {
|
||||
// // fetch all the current campaign character
|
||||
// val npcs = campaign.npcs.toMutableMap()
|
||||
// // update the corresponding character using the use case.
|
||||
// npcs[characterInstanceId] = useCase.updateDiminished(
|
||||
// instance = campaign.npc(id = characterInstanceId),
|
||||
// diminished = diminished,
|
||||
// )
|
||||
// // save the campaign to the disk + update the flow.
|
||||
// store.save(
|
||||
// campaign = campaign.copy(npcs = npcs)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// suspend fun toggleAlteration(
|
||||
// characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
// alterationId: String,
|
||||
// ) {
|
||||
// when (characterInstanceId.prefix) {
|
||||
// Campaign.CharacterInstance.Id.PLAYER -> {
|
||||
// // fetch all the current campaign character
|
||||
// val characters = campaign.characters.toMutableMap()
|
||||
// // update the corresponding character alterations
|
||||
// characters[characterInstanceId]?.let { character ->
|
||||
// characters[characterInstanceId] = character.copy(
|
||||
// alterations = character.alterations.toMutableList().also { alterations ->
|
||||
// if (alterations.contains(alterationId)) {
|
||||
// alterations.remove(alterationId)
|
||||
// } else {
|
||||
// alterations.add(alterationId)
|
||||
// }
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
// // save the campaign to the disk + update the flow.
|
||||
// store.save(
|
||||
// campaign = campaign.copy(characters = characters)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// Campaign.CharacterInstance.Id.NPC -> {
|
||||
// // fetch all the current campaign character
|
||||
// val characters = campaign.npcs.toMutableMap()
|
||||
// // update the corresponding character alterations
|
||||
// characters[characterInstanceId]?.let { character ->
|
||||
// characters[characterInstanceId] = character.copy(
|
||||
// alterations = character.alterations.toMutableList().also { alterations ->
|
||||
// if (alterations.contains(alterationId)) {
|
||||
// alterations.remove(alterationId)
|
||||
// } else {
|
||||
// alterations.add(alterationId)
|
||||
// }
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
// // save the campaign to the disk + update the flow.
|
||||
// store.save(
|
||||
// campaign = campaign.copy(npcs = characters)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
suspend fun updateToggleParty() {
|
||||
fun updateToggleParty() {
|
||||
store.save(
|
||||
campaign = campaign.copy(
|
||||
options = campaign.options.copy(
|
||||
|
|
@ -263,7 +132,7 @@ class CampaignService(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun updateToggleNpc() {
|
||||
fun updateToggleNpc() {
|
||||
store.save(
|
||||
campaign = campaign.copy(
|
||||
options = campaign.options.copy(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.pixelized.server.lwa.model.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
import com.pixelized.server.lwa.server.exception.JsonCodingException
|
||||
import com.pixelized.server.lwa.server.exception.JsonConversionException
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
|
|
@ -9,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
|
@ -27,77 +32,84 @@ class CampaignStore(
|
|||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// load the initial data
|
||||
scope.launch {
|
||||
updateCampaignFromDisk()
|
||||
updateCampaignFlow()
|
||||
}
|
||||
}
|
||||
|
||||
fun campaignFlow(): StateFlow<Campaign> = campaignFlow
|
||||
|
||||
private fun updateCampaignFromDisk() {
|
||||
private fun updateCampaignFlow() {
|
||||
campaignFlow.value = try {
|
||||
loadCampaign()
|
||||
load()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
println(exception.message) // TODO proper exception handling
|
||||
Campaign.empty()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(
|
||||
FileReadException::class,
|
||||
JsonCodingException::class,
|
||||
JsonConversionException::class,
|
||||
)
|
||||
fun loadCampaign(): Campaign {
|
||||
val file = file()
|
||||
val json = try {
|
||||
fun load(): Campaign {
|
||||
val file = campaignFile()
|
||||
// Read the campaign file.
|
||||
val data = try {
|
||||
file.readText(charset = Charsets.UTF_8)
|
||||
} catch (exception: Exception) {
|
||||
throw FileReadException(root = exception)
|
||||
}
|
||||
|
||||
// Guard, if the file is empty we load a default campaign.
|
||||
if (json.isBlank()) return Campaign.empty()
|
||||
|
||||
if (data.isBlank()) return Campaign.empty()
|
||||
// Decode the json into a string.
|
||||
val json = try {
|
||||
this.json.decodeFromString<CampaignJson>(data)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// Convert from the Json format
|
||||
val campaign = try {
|
||||
val data = this.json.decodeFromString<CampaignJson>(json)
|
||||
factory.convertFromJson(data)
|
||||
factory.convertFromJson(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
|
||||
return campaign
|
||||
}
|
||||
|
||||
@Throws(
|
||||
JsonConversionException::class,
|
||||
JsonCodingException::class,
|
||||
FileWriteException::class,
|
||||
)
|
||||
fun save(campaign: Campaign) {
|
||||
// convert the data to json format
|
||||
// Transform the json into the model.
|
||||
val json = try {
|
||||
factory.convertToJson(campaign = campaign).let(json::encodeToString)
|
||||
factory.convertToJson(campaign = campaign)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
// write the file
|
||||
// Encode the json into a string.
|
||||
val data = try {
|
||||
this.json.encodeToString(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// Write the file
|
||||
try {
|
||||
val file = file()
|
||||
val file = campaignFile()
|
||||
file.writeText(
|
||||
text = json,
|
||||
text = data,
|
||||
charset = Charsets.UTF_8,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw FileWriteException(root = exception)
|
||||
}
|
||||
// Update the dataflow.
|
||||
campaignFlow.value = campaign
|
||||
campaignFlow.update { campaign }
|
||||
}
|
||||
|
||||
sealed class CampaignStoreException(root: Exception) : Exception(root)
|
||||
class JsonConversionException(root: Exception) : CampaignStoreException(root)
|
||||
class FileWriteException(root: Exception) : CampaignStoreException(root)
|
||||
class FileReadException(root: Exception) : CampaignStoreException(root)
|
||||
|
||||
private fun file(): File {
|
||||
private fun campaignFile(): File {
|
||||
return File("${pathProvider.campaignPath()}campaign.json")
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,9 @@ class CharacterSheetService(
|
|||
private val factory: CharacterSheetJsonFactory,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
private val sheets get() = sheetsFlow.value
|
||||
|
||||
private val sheetsFlow = characterStore.characterSheetsFlow()
|
||||
.map { entry -> entry.associateBy { character -> character.id } }
|
||||
.stateIn(
|
||||
|
|
@ -40,7 +42,7 @@ class CharacterSheetService(
|
|||
return sheets[characterSheetId]
|
||||
}
|
||||
|
||||
fun tags() : Collection<TagJson> {
|
||||
fun tags(): Collection<TagJson> {
|
||||
return alterationTags.value
|
||||
}
|
||||
|
||||
|
|
@ -52,14 +54,24 @@ class CharacterSheetService(
|
|||
return sheets[characterSheetId]?.let(factory::convertToJson)
|
||||
}
|
||||
|
||||
suspend fun updateCharacterSheet(character: CharacterSheetJson) {
|
||||
@Throws
|
||||
suspend fun save(
|
||||
character: CharacterSheetJson,
|
||||
create: Boolean,
|
||||
) {
|
||||
return characterStore.save(
|
||||
sheet = factory.convertFromJson(character)
|
||||
sheet = factory.convertFromJson(character),
|
||||
create = create,
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteCharacterSheet(characterSheetId: String): Boolean {
|
||||
return characterStore.delete(id = characterSheetId)
|
||||
@Throws
|
||||
fun deleteCharacterSheet(
|
||||
characterSheetId: String,
|
||||
) {
|
||||
characterStore.delete(
|
||||
characterSheetId = characterSheetId
|
||||
)
|
||||
}
|
||||
|
||||
// Data manipulation through WebSocket.
|
||||
|
|
@ -78,7 +90,8 @@ class CharacterSheetService(
|
|||
characterStore.save(
|
||||
sheet = character.copy(
|
||||
alterations = alterations,
|
||||
)
|
||||
),
|
||||
create = false,
|
||||
)
|
||||
}
|
||||
if (active.not() && contain) {
|
||||
|
|
@ -88,7 +101,8 @@ class CharacterSheetService(
|
|||
characterStore.save(
|
||||
sheet = character.copy(
|
||||
alterations = alterations,
|
||||
)
|
||||
),
|
||||
create = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -100,7 +114,10 @@ class CharacterSheetService(
|
|||
) {
|
||||
sheets[characterSheetId]?.let { character ->
|
||||
val update = character.copy(damage = damage)
|
||||
characterStore.save(sheet = update)
|
||||
characterStore.save(
|
||||
sheet = update,
|
||||
create = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +127,10 @@ class CharacterSheetService(
|
|||
) {
|
||||
sheets[characterSheetId]?.let { character ->
|
||||
val update = character.copy(diminished = diminished)
|
||||
characterStore.save(sheet = update)
|
||||
characterStore.save(
|
||||
sheet = update,
|
||||
create = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +140,10 @@ class CharacterSheetService(
|
|||
) {
|
||||
sheets[characterSheetId]?.let { character ->
|
||||
val update = character.copy(fatigue = fatigue)
|
||||
characterStore.save(sheet = update)
|
||||
characterStore.save(
|
||||
sheet = update,
|
||||
create = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +164,10 @@ class CharacterSheetService(
|
|||
skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
|
||||
},
|
||||
)
|
||||
characterStore.save(sheet = update)
|
||||
characterStore.save(
|
||||
sheet = update,
|
||||
create = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,21 @@
|
|||
package com.pixelized.server.lwa.model.character
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
import com.pixelized.server.lwa.server.exception.JsonCodingException
|
||||
import com.pixelized.server.lwa.server.exception.JsonConversionException
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
|
@ -27,26 +34,29 @@ class CharacterSheetStore(
|
|||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// load the initial data
|
||||
scope.launch {
|
||||
updateCharacterSheets()
|
||||
updateCharacterFlow()
|
||||
}
|
||||
}
|
||||
|
||||
fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow
|
||||
|
||||
private suspend fun updateCharacterSheets() {
|
||||
private suspend fun updateCharacterFlow() {
|
||||
characterSheetsFlow.value = try {
|
||||
loadCharacterSheets()
|
||||
load()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(
|
||||
FileReadException::class,
|
||||
JsonCodingException::class,
|
||||
JsonConversionException::class,
|
||||
)
|
||||
suspend fun loadCharacterSheets(): List<CharacterSheet> {
|
||||
suspend fun load(
|
||||
directory: File = this.directory,
|
||||
): List<CharacterSheet> {
|
||||
return directory
|
||||
.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
|
|
@ -59,9 +69,15 @@ class CharacterSheetStore(
|
|||
if (json.isBlank()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
// decode the file
|
||||
val data = try {
|
||||
this.json.decodeFromString<CharacterSheetJson>(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// parse the json string.
|
||||
try {
|
||||
val sheet = this.json.decodeFromString<CharacterSheetJson>(json)
|
||||
factory.convertFromJson(sheet)
|
||||
factory.convertFromJson(data)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
|
|
@ -71,19 +87,34 @@ class CharacterSheetStore(
|
|||
}
|
||||
|
||||
@Throws(
|
||||
FileWriteException::class,
|
||||
BusinessException::class,
|
||||
JsonConversionException::class,
|
||||
JsonCodingException::class,
|
||||
FileWriteException::class,
|
||||
)
|
||||
fun save(sheet: CharacterSheet) {
|
||||
// convert the character sheet into json format.
|
||||
val data = try {
|
||||
factory.convertToJson(sheet = sheet).let(json::encodeToString)
|
||||
fun save(
|
||||
sheet: CharacterSheet,
|
||||
create: Boolean,
|
||||
) {
|
||||
val file = characterSheetFile(id = sheet.id)
|
||||
// Guard case on update alteration
|
||||
if (create && file.exists()) {
|
||||
throw BusinessException(message = "Character already exist, creation is impossible.")
|
||||
}
|
||||
// Transform the json into the model.
|
||||
val json = try {
|
||||
factory.convertToJson(sheet = sheet)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
// Encode the json into a string.
|
||||
val data = try {
|
||||
this.json.encodeToString(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// write the character file.
|
||||
try {
|
||||
val file = characterSheetFile(id = sheet.id)
|
||||
file.writeText(
|
||||
text = data,
|
||||
charset = Charsets.UTF_8,
|
||||
|
|
@ -92,40 +123,47 @@ class CharacterSheetStore(
|
|||
throw FileWriteException(root = exception)
|
||||
}
|
||||
// Update the dataflow.
|
||||
characterSheetsFlow.value = characterSheetsFlow.value
|
||||
.toMutableList()
|
||||
.also { data ->
|
||||
val index = data.indexOfFirst { it.id == sheet.id }
|
||||
if (index >= 0) {
|
||||
data[index] = sheet
|
||||
} else {
|
||||
data.add(sheet)
|
||||
characterSheetsFlow.update { characters ->
|
||||
characters.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 })
|
||||
.sortedWith(compareBy(Collator.getInstance()) {
|
||||
it.name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(id: String): Boolean {
|
||||
val file = characterSheetFile(id = id)
|
||||
val deleted = file.delete()
|
||||
if (deleted) {
|
||||
characterSheetsFlow.value = characterSheetsFlow.value.toMutableList()
|
||||
.also { data ->
|
||||
data.removeIf { it.id == id }
|
||||
}
|
||||
.sortedBy {
|
||||
it.name
|
||||
}
|
||||
@Throws(BusinessException::class)
|
||||
fun delete(characterSheetId: String) {
|
||||
val file = characterSheetFile(id = characterSheetId)
|
||||
// Guard case on the file existence.
|
||||
if (file.exists().not()) {
|
||||
throw BusinessException(
|
||||
message = "Character file with id:$characterSheetId doesn't not exist.",
|
||||
code = APIResponse.ErrorCode.CharacterSheetId
|
||||
)
|
||||
}
|
||||
// Guard case on the file deletion
|
||||
if (file.delete().not()) {
|
||||
throw BusinessException(
|
||||
message = "Character file have not been deleted for unknown reason.",
|
||||
)
|
||||
}
|
||||
// Update the data model with the deleted character.
|
||||
characterSheetsFlow.update { characters ->
|
||||
characters.toMutableList()
|
||||
.also { data -> data.removeIf { it.id == characterSheetId } }
|
||||
.sortedWith(compareBy(Collator.getInstance()) { it.name })
|
||||
}
|
||||
return deleted
|
||||
}
|
||||
|
||||
private fun characterSheetFile(id: String): File {
|
||||
return File("${pathProvider.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)
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package com.pixelized.server.lwa.model.tag
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.FileReadException
|
||||
import com.pixelized.server.lwa.server.exception.FileWriteException
|
||||
import com.pixelized.server.lwa.server.exception.JsonConversionException
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -49,7 +52,7 @@ class TagStore(
|
|||
flow.value = try {
|
||||
file.readTags().associateBy { it.id }
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
}
|
||||
}
|
||||
|
|
@ -100,9 +103,4 @@ class TagStore(
|
|||
private fun characterFile() = File("${pathProvider.tagsPath()}$CHARACTER.json")
|
||||
|
||||
private fun alterationFile() = File("${pathProvider.tagsPath()}$ALTERATION.json")
|
||||
|
||||
sealed class TagStoreException(root: Exception) : Exception(root)
|
||||
class JsonConversionException(root: Exception) : TagStoreException(root)
|
||||
class FileWriteException(root: Exception) : TagStoreException(root)
|
||||
class FileReadException(root: Exception) : TagStoreException(root)
|
||||
}
|
||||
|
|
@ -21,6 +21,14 @@ class Engine(
|
|||
val webSocket = MutableSharedFlow<SocketMessage>()
|
||||
|
||||
suspend fun handle(message: SocketMessage) {
|
||||
try {
|
||||
unSafeHandle(message)
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun unSafeHandle(message: SocketMessage) {
|
||||
when (message) {
|
||||
|
||||
is RollEvent -> Unit // Nothing to do here.
|
||||
|
|
@ -61,10 +69,22 @@ class Engine(
|
|||
}
|
||||
|
||||
is CampaignEvent -> when (message) {
|
||||
is CampaignEvent.CharacterAdded -> Unit // TODO
|
||||
is CampaignEvent.CharacterRemoved -> Unit // TODO
|
||||
is CampaignEvent.NpcAdded -> Unit // TODO
|
||||
is CampaignEvent.NpcRemoved -> Unit // TODO
|
||||
is CampaignEvent.CharacterAdded -> campaignService.addCharacter(
|
||||
characterSheetId = message.characterSheetId,
|
||||
)
|
||||
|
||||
is CampaignEvent.CharacterRemoved -> campaignService.removeCharacter(
|
||||
characterSheetId = message.characterSheetId,
|
||||
)
|
||||
|
||||
is CampaignEvent.NpcAdded -> campaignService.addNpc(
|
||||
characterSheetId = message.characterSheetId,
|
||||
)
|
||||
|
||||
is CampaignEvent.NpcRemoved -> campaignService.removeNpc(
|
||||
characterSheetId = message.characterSheetId,
|
||||
)
|
||||
|
||||
is CampaignEvent.UpdateScene -> Unit // TODO
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import com.pixelized.server.lwa.server.rest.alteration.getAlteration
|
|||
import com.pixelized.server.lwa.server.rest.alteration.getAlterationTags
|
||||
import com.pixelized.server.lwa.server.rest.alteration.getAlterations
|
||||
import com.pixelized.server.lwa.server.rest.alteration.putAlteration
|
||||
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.campaign.getCampaign
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignScene
|
||||
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignCharacter
|
||||
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.character.deleteCharacter
|
||||
import com.pixelized.server.lwa.server.rest.character.getCharacter
|
||||
import com.pixelized.server.lwa.server.rest.character.getCharacterTags
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
|
||||
class BusinessException(
|
||||
message: String,
|
||||
val code: APIResponse.ErrorCode? = null,
|
||||
) : ServerException(
|
||||
root = Exception(message)
|
||||
)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
class FileReadException(root: Exception) : ServerException(root)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
class FileWriteException(root: Exception) : ServerException(root)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
class JsonCodingException(root: Exception) : ServerException(root)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
class JsonConversionException(root: Exception) : ServerException(root)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
class MissingParameterException(name: String) :
|
||||
ServerException(root = Exception("Missing '$name' parameter."))
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.server.lwa.server.exception
|
||||
|
||||
sealed class ServerException(root: Exception) : Exception(root)
|
||||
|
|
@ -2,24 +2,24 @@ package com.pixelized.server.lwa.server.rest.alteration
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.alterationId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.deleteAlteration(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val alterationId = call.parameters.alterationId
|
||||
|
||||
val deleted = alterationService.delete(
|
||||
// delete the alteration.
|
||||
alterationService.delete(
|
||||
alterationId = alterationId
|
||||
)
|
||||
|
||||
if (deleted.not()) error("Unexpected error occurred")
|
||||
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
// API & WebSocket responses.
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.AlterationDelete(
|
||||
|
|
@ -28,9 +28,8 @@ fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.()
|
|||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ package com.pixelized.server.lwa.server.rest.alteration
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.alterationId
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getAlteration(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
|
|
@ -17,12 +18,13 @@ fun Engine.getAlteration(): suspend io.ktor.server.routing.RoutingContext.() ->
|
|||
?: error("Alteration with id:$alterationId not found.")
|
||||
// send it back to the user.
|
||||
call.respond(
|
||||
message = alteration,
|
||||
message = APIResponse.success(
|
||||
data = alteration
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getAlterationTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getAlterationTags(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
call.respond(
|
||||
message = alterationService.tags(),
|
||||
)
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = alterationService.tags(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getAlterations(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getAlterations(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
call.respond(
|
||||
message = alterationService.alterations(),
|
||||
)
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = alterationService.alterations(),
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.pixelized.server.lwa.server.rest.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respondText
|
||||
|
||||
fun Engine.putAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val form = call.receive<AlterationJson>()
|
||||
|
||||
alterationService.update(json = form)
|
||||
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.AlterationUpdate(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
alterationId = form.id,
|
||||
),
|
||||
)
|
||||
} catch (exception : Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.pixelized.server.lwa.server.rest.alteration
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.create
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putAlteration(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val form = call.receive<AlterationJson>()
|
||||
val create = call.queryParameters.create
|
||||
|
||||
alterationService.save(
|
||||
json = form,
|
||||
create = create,
|
||||
)
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.AlterationUpdate(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
alterationId = form.id,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,27 +2,24 @@ package com.pixelized.server.lwa.server.rest.campaign
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.removeCampaignCharacter(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
// remove the character form the party
|
||||
val updated = campaignService.removeCharacter(
|
||||
campaignService.removeCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
// error case
|
||||
if (updated.not()) {
|
||||
error("Unexpected error when removing character (characterSheetId:$characterSheetId) from party.")
|
||||
}
|
||||
// API & WebSocket responses
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.CharacterRemoved(
|
||||
|
|
@ -31,9 +28,8 @@ fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingCont
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,24 @@ package com.pixelized.server.lwa.server.rest.campaign
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
// remove the character form the party
|
||||
val updated = campaignService.removeNpc(characterSheetId = characterSheetId)
|
||||
// error case
|
||||
if (updated.not()) {
|
||||
error("Unexpected error when removing character (characterSheetId:$characterSheetId) from npcs.")
|
||||
}
|
||||
campaignService.removeNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
// API & WebSocket responses
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.NpcRemoved(
|
||||
|
|
@ -29,9 +28,8 @@ fun Engine.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.()
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getCampaign(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getCampaign(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
call.respond(
|
||||
message = campaignService.campaignJson(),
|
||||
)
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = campaignService.campaignJson(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,25 +2,24 @@ package com.pixelized.server.lwa.server.rest.campaign
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCampaignCharacter(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
// add the character to the party.
|
||||
val update = campaignService.addCharacter(characterSheetId = characterSheetId)
|
||||
// error case
|
||||
if (update.not()) {
|
||||
error("Unexpected error occurred when the character instance was added to the party")
|
||||
}
|
||||
campaignService.addCharacter(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "Character $characterSheetId successfully added to the party",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.CharacterAdded(
|
||||
|
|
@ -29,9 +28,8 @@ fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${exception.message}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,24 @@ package com.pixelized.server.lwa.server.rest.campaign
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCampaignNpc(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
// add the character to the npcs.
|
||||
val update = campaignService.addNpc(characterSheetId = characterSheetId)
|
||||
// error case
|
||||
if (update.not()) {
|
||||
error("Unexpected error occurred when the character instance was added to the npcs")
|
||||
}
|
||||
campaignService.addNpc(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "Character $characterSheetId successfully added to the npcs",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.NpcAdded(
|
||||
|
|
@ -29,9 +28,8 @@ fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() ->
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${exception.message}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
package com.pixelized.server.lwa.server.rest.campaign
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV2
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// Get the scene json from the body of the request
|
||||
|
|
@ -15,15 +17,12 @@ fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.()
|
|||
// convert the scene into the a usable data model.
|
||||
val scene = campaignJsonFactory.convertFromJson(json = form)
|
||||
// update the campaign.
|
||||
val updated = campaignService.setScene(scene = scene)
|
||||
// error case
|
||||
if (updated.not()) {
|
||||
error("Unexpected error when updating the scene.")
|
||||
}
|
||||
campaignService.setScene(
|
||||
scene = scene,
|
||||
)
|
||||
// API & WebSocket responses
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CampaignEvent.UpdateScene(
|
||||
|
|
@ -32,9 +31,8 @@ fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.()
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = exception.localizedMessage,
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,24 @@ package com.pixelized.server.lwa.server.rest.character
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
val characterSheetId = call.parameters.characterSheetId
|
||||
|
||||
val deleted = characterService.deleteCharacterSheet(
|
||||
characterSheetId = characterSheetId
|
||||
)
|
||||
// Remove the character fom the campaign if needed.
|
||||
// TODO probably useless because all data will not be cleaned up (all campaign / screnes)
|
||||
campaignService.removeInstance(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
|
||||
if (deleted) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.parameters.characterSheetId
|
||||
// delete the character sheet.
|
||||
characterService.deleteCharacterSheet(
|
||||
characterSheetId = characterSheetId
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respond(
|
||||
message = APIResponse.success()
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.CharacterSheetDelete(
|
||||
|
|
@ -30,10 +27,9 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -
|
|||
characterSheetId = characterSheetId,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ package com.pixelized.server.lwa.server.rest.character
|
|||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getCharacter(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
|
|
@ -17,12 +18,13 @@ fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
|
|||
?: error("CharacterSheet with id:$characterSheetId not found.")
|
||||
// send it back to the user.
|
||||
call.respond(
|
||||
message = characterSheet,
|
||||
message = APIResponse.success(
|
||||
data = characterSheet,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getCharacterTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getCharacterTags(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
call.respond(
|
||||
message = characterService.tags(),
|
||||
)
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = characterService.tags(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getCharacters(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.getCharacters(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
call.respond(
|
||||
message = characterService.charactersJson(),
|
||||
)
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = characterService.charactersJson(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,26 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.create
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCharacter(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val form = call.receive<CharacterSheetJson>()
|
||||
characterService.updateCharacterSheet(
|
||||
val create = call.queryParameters.create
|
||||
characterService.save(
|
||||
character = form,
|
||||
create = create,
|
||||
)
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success()
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.CharacterSheetUpdate(
|
||||
|
|
@ -25,9 +29,8 @@ fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
|
|||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.active
|
||||
import com.pixelized.server.lwa.utils.extentions.alterationId
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCharacterAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCharacterAlteration(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
val alterationId = call.queryParameters.alterationId
|
||||
val active = call.queryParameters["active"]?.toBooleanStrictOrNull()
|
||||
?: error("Missing active parameter.")
|
||||
val active = call.queryParameters.active
|
||||
// Update the character damage
|
||||
characterService.updateAlteration(
|
||||
characterSheetId = characterSheetId,
|
||||
|
|
@ -22,9 +24,8 @@ fun Engine.putCharacterAlteration(): suspend io.ktor.server.routing.RoutingConte
|
|||
active = active
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success()
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CharacterSheetEvent.UpdateAlteration(
|
||||
|
|
@ -35,9 +36,8 @@ fun Engine.putCharacterAlteration(): suspend io.ktor.server.routing.RoutingConte
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,32 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCharacterDamage(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
val damage = call.queryParameters["damage"]?.toIntOrNull()
|
||||
?: error("Missing damage parameter.")
|
||||
?: throw MissingParameterException(name = "damage")
|
||||
// fetch the character sheet
|
||||
val characterSheet = characterService.character(characterSheetId)
|
||||
?: error("Character sheet not found for characterSheetId: $characterSheetId")
|
||||
?: error("CharacterSheet with id:$characterSheetId not found.")
|
||||
// Update the character damage
|
||||
characterService.updateDamage(
|
||||
characterSheetId = characterSheetId,
|
||||
damage = damage
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success()
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CharacterSheetEvent.UpdateDamage(
|
||||
|
|
@ -35,9 +37,8 @@ fun Engine.putCharacterDamage(): suspend io.ktor.server.routing.RoutingContext.(
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,29 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCharacterDiminished(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
val diminished = call.queryParameters["diminished"]?.toIntOrNull()
|
||||
?: error("Missing diminished parameter.")
|
||||
?: throw MissingParameterException(name = "diminished")
|
||||
// Update the character damage
|
||||
characterService.updateDiminished(
|
||||
characterSheetId = characterSheetId,
|
||||
diminished = diminished
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success()
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CharacterSheetEvent.UpdateDiminished(
|
||||
|
|
@ -31,9 +33,8 @@ fun Engine.putCharacterDiminished(): suspend io.ktor.server.routing.RoutingConte
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,32 @@
|
|||
package com.pixelized.server.lwa.server.rest.character
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putCharacterFatigue(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val characterSheetId = call.queryParameters.characterSheetId
|
||||
val fatigue = call.queryParameters["fatigue"]?.toIntOrNull()
|
||||
?: error("Missing fatigue parameter.")
|
||||
?: throw MissingParameterException(name = "fatigue")
|
||||
// fetch the character sheet
|
||||
val characterSheet = characterService.character(characterSheetId)
|
||||
?: error("Character sheet not found for characterSheetId: $characterSheetId")
|
||||
?: error("CharacterSheet with id:$characterSheetId not found.")
|
||||
// Update the character damage
|
||||
characterService.updateFatigue(
|
||||
characterSheetId = characterSheetId,
|
||||
fatigue = fatigue
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.OK}",
|
||||
status = HttpStatusCode.OK,
|
||||
call.respond(
|
||||
message = APIResponse.success()
|
||||
)
|
||||
webSocket.emit(
|
||||
value = CharacterSheetEvent.UpdateFatigue(
|
||||
|
|
@ -35,9 +37,8 @@ fun Engine.putCharacterFatigue(): suspend io.ktor.server.routing.RoutingContext.
|
|||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.respondText(
|
||||
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
call.exception(
|
||||
exception = exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,24 @@
|
|||
package com.pixelized.server.lwa.utils.extentions
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||
import io.ktor.http.Parameters
|
||||
|
||||
val Parameters.characterSheetId
|
||||
get() = this["characterSheetId"] ?: error("Missing characterSheetId parameter.")
|
||||
get() = "characterSheetId".let { param ->
|
||||
this[param] ?: throw MissingParameterException(name = param)
|
||||
}
|
||||
|
||||
val Parameters.alterationId
|
||||
get() = this["alterationId"] ?: error("Missing alterationId parameter.")
|
||||
get() = "alterationId".let { param ->
|
||||
this[param] ?: throw MissingParameterException(name = param)
|
||||
}
|
||||
|
||||
val Parameters.create
|
||||
get() = "create".let { param ->
|
||||
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
|
||||
}
|
||||
|
||||
val Parameters.active
|
||||
get() = "active".let { param ->
|
||||
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.pixelized.server.lwa.utils.extentions
|
||||
|
||||
import com.pixelized.server.lwa.server.exception.BusinessException
|
||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingCall
|
||||
|
||||
|
||||
suspend inline fun <reified T : Exception> RoutingCall.exception(exception: T) {
|
||||
when (exception) {
|
||||
|
||||
is MissingParameterException -> {
|
||||
respond(
|
||||
message = APIResponse.error(
|
||||
status = APIResponse.BAD_REQUEST,
|
||||
message = exception.message ?: "?",
|
||||
code = APIResponse.ErrorCode.AlterationName,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is BusinessException -> {
|
||||
respond(
|
||||
message = APIResponse.error(
|
||||
status = APIResponse.INTERNAL_ERROR,
|
||||
message = exception.message ?: "?",
|
||||
code = exception.code,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> respond(
|
||||
message = APIResponse.error(
|
||||
status = APIResponse.INTERNAL_ERROR,
|
||||
message = exception.message ?: "?",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ val toolsDependencies
|
|||
get() = module {
|
||||
factory {
|
||||
Json {
|
||||
encodeDefaults = true
|
||||
explicitNulls = false
|
||||
prettyPrint = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.pixelized.shared.lwa.protocol.rest
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class APIResponse<T>(
|
||||
val success: Boolean,
|
||||
val status: Int,
|
||||
val message: String?,
|
||||
val code: ErrorCode?,
|
||||
val data: T?,
|
||||
) {
|
||||
@Serializable
|
||||
enum class ErrorCode {
|
||||
AlterationId,
|
||||
AlterationName,
|
||||
CharacterSheetId,
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun error(
|
||||
status: Int,
|
||||
code: ErrorCode? = null,
|
||||
message: String?,
|
||||
) = APIResponse(
|
||||
success = false,
|
||||
status = status,
|
||||
code = code,
|
||||
message = message,
|
||||
data = null,
|
||||
)
|
||||
|
||||
fun success(
|
||||
status: Int = OK,
|
||||
) = APIResponse(
|
||||
success = true,
|
||||
status = status,
|
||||
code = null,
|
||||
message = null,
|
||||
data = null,
|
||||
)
|
||||
|
||||
inline fun <reified T> success(
|
||||
status: Int = OK,
|
||||
data: T? = null,
|
||||
) = APIResponse(
|
||||
success = true,
|
||||
status = status,
|
||||
code = null,
|
||||
message = null,
|
||||
data = data,
|
||||
)
|
||||
|
||||
const val OK = 200
|
||||
const val BAD_REQUEST = 400
|
||||
const val INTERNAL_ERROR = 500
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue