tmp
This commit is contained in:
parent
76336dfbb0
commit
02987a0a53
54 changed files with 1487 additions and 332 deletions
|
|
@ -243,6 +243,16 @@
|
||||||
<string name="game_master__character_action__remove_from_npc">Retirer des 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__create_character_sheet">Créer un personnage</string>
|
||||||
<string name="game_master__alteration__filter">Filtrer par nom :</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>
|
<string name="game_master__alteration__delete">Supprimer l'altération</string>
|
||||||
|
<string name="game_master__alteration__edit_add_field">Ajouter un champ</string>
|
||||||
|
<string name="game_master__alteration__edit_id">Identifiant de l'altération</string>
|
||||||
|
<string name="game_master__alteration__edit_label">Nom</string>
|
||||||
|
<string name="game_master__alteration__edit_description">Description</string>
|
||||||
|
<string name="game_master__alteration__edit_tags">Tags</string>
|
||||||
|
<string name="game_master__alteration__edit_field_id">Identifiant du champ</string>
|
||||||
|
<string name="game_master__alteration__edit_field_expression">Expression</string>
|
||||||
|
<string name="game_master__alteration__edit_field_save">Sauvegarder</string>
|
||||||
|
<string name="game_master__alteration__edit_field_cancel">Annuler</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -41,7 +41,9 @@ class DataSyncViewModel(
|
||||||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||||
.onEach {
|
.onEach {
|
||||||
alterationRepository.updateAlterations()
|
alterationRepository.updateAlterations()
|
||||||
|
alterationRepository.updateTags()
|
||||||
characterRepository.updateCharacterPreviews()
|
characterRepository.updateCharacterPreviews()
|
||||||
|
characterRepository.updateTags()
|
||||||
campaignRepository.updateCampaign()
|
campaignRepository.updateCampaign()
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,13 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEdi
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
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.action.GMActionViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationFactory
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationViewModel
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterFactory
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterViewModel
|
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
|
||||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||||
|
|
@ -123,8 +126,10 @@ val factoryDependencies
|
||||||
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
||||||
factoryOf(::TextMessageFactory)
|
factoryOf(::TextMessageFactory)
|
||||||
factoryOf(::LevelUpFactory)
|
factoryOf(::LevelUpFactory)
|
||||||
|
factoryOf(::GMTagFactory)
|
||||||
factoryOf(::GMCharacterFactory)
|
factoryOf(::GMCharacterFactory)
|
||||||
factoryOf(::GMAlterationFactory)
|
factoryOf(::GMAlterationFactory)
|
||||||
|
factoryOf(::GMAlterationEditFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModelDependencies
|
val viewModelDependencies
|
||||||
|
|
@ -149,6 +154,7 @@ val viewModelDependencies
|
||||||
viewModelOf(::GameMasterViewModel)
|
viewModelOf(::GameMasterViewModel)
|
||||||
viewModelOf(::GMActionViewModel)
|
viewModelOf(::GMActionViewModel)
|
||||||
viewModelOf(::GMAlterationViewModel)
|
viewModelOf(::GMAlterationViewModel)
|
||||||
|
viewModelOf(::GMAlterationEditViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val useCaseDependencies
|
val useCaseDependencies
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.network
|
||||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
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.CharacterPreviewJson
|
||||||
|
|
||||||
interface LwaClient {
|
interface LwaClient {
|
||||||
|
|
@ -15,6 +16,16 @@ interface LwaClient {
|
||||||
alterationId: String,
|
alterationId: String,
|
||||||
): AlterationJson?
|
): AlterationJson?
|
||||||
|
|
||||||
|
suspend fun updateAlteration(
|
||||||
|
alterationJson: AlterationJson
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun deleteAlteration(
|
||||||
|
alterationId: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun alterationTags(): List<TagJson>
|
||||||
|
|
||||||
// Campaign
|
// Campaign
|
||||||
|
|
||||||
suspend fun campaign(): CampaignJson
|
suspend fun campaign(): CampaignJson
|
||||||
|
|
@ -39,6 +50,8 @@ interface LwaClient {
|
||||||
|
|
||||||
suspend fun characters(): List<CharacterPreviewJson>
|
suspend fun characters(): List<CharacterPreviewJson>
|
||||||
|
|
||||||
|
suspend fun characterTags(): List<TagJson>
|
||||||
|
|
||||||
suspend fun character(
|
suspend fun character(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
): CharacterSheetJson
|
): CharacterSheetJson
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||||
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
import com.pixelized.shared.lwa.model.campaign.CampaignJson
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
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.CharacterPreviewJson
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.call.body
|
import io.ktor.client.call.body
|
||||||
|
|
@ -30,6 +31,23 @@ class LwaClientImpl(
|
||||||
.get("$root/alteration/detail?alterationId=$alterationId")
|
.get("$root/alteration/detail?alterationId=$alterationId")
|
||||||
.body()
|
.body()
|
||||||
|
|
||||||
|
override suspend fun updateAlteration(
|
||||||
|
alterationJson: AlterationJson
|
||||||
|
) = client
|
||||||
|
.put("$root/alteration/update") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(alterationJson)
|
||||||
|
}
|
||||||
|
.body<Unit>()
|
||||||
|
|
||||||
|
override suspend fun deleteAlteration(alterationId: String) = client
|
||||||
|
.delete("$root/alteration/delete?alterationId=$alterationId")
|
||||||
|
.body<Unit>()
|
||||||
|
|
||||||
|
override suspend fun alterationTags(): List<TagJson> = client
|
||||||
|
.get("$root/alteration/tags")
|
||||||
|
.body()
|
||||||
|
|
||||||
override suspend fun campaign(): CampaignJson = client
|
override suspend fun campaign(): CampaignJson = client
|
||||||
.get("$root/campaign")
|
.get("$root/campaign")
|
||||||
.body()
|
.body()
|
||||||
|
|
@ -62,6 +80,10 @@ class LwaClientImpl(
|
||||||
.get("$root/character/all")
|
.get("$root/character/all")
|
||||||
.body()
|
.body()
|
||||||
|
|
||||||
|
override suspend fun characterTags(): List<TagJson> = client
|
||||||
|
.get("$root/character/tags")
|
||||||
|
.body()
|
||||||
|
|
||||||
override suspend fun character(
|
override suspend fun character(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
): CharacterSheetJson = client
|
): CharacterSheetJson = client
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class AlterationRepository(
|
||||||
|
|
||||||
val alterationFlow get() = alterationStore.alterationsFlow
|
val alterationFlow get() = alterationStore.alterationsFlow
|
||||||
|
|
||||||
|
val tagsFlow get() = alterationStore.tagsFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This flow transform the campaign instance (player + npc) into a
|
* This flow transform the campaign instance (player + npc) into a
|
||||||
* Map<CharacterSheetId, List<AlterationId>>.
|
* Map<CharacterSheetId, List<AlterationId>>.
|
||||||
|
|
@ -67,6 +69,16 @@ class AlterationRepository(
|
||||||
alterationStore.updateAlterations()
|
alterationStore.updateAlterations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateTags() {
|
||||||
|
alterationStore.updateTags()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun alteration(
|
||||||
|
alterationId: String?,
|
||||||
|
): Alteration? {
|
||||||
|
return alterationFlow.value[alterationId]
|
||||||
|
}
|
||||||
|
|
||||||
fun fieldAlterations(
|
fun fieldAlterations(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
): Map<String, List<FieldAlteration>> {
|
): Map<String, List<FieldAlteration>> {
|
||||||
|
|
@ -79,6 +91,18 @@ class AlterationRepository(
|
||||||
return activeAlterationMapFlow.map { it[characterSheetId] ?: emptyMap() }
|
return activeAlterationMapFlow.map { it[characterSheetId] ?: emptyMap() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateAlteration(
|
||||||
|
alteration: Alteration
|
||||||
|
) {
|
||||||
|
alterationStore.putAlteration(alteration)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteAlteration(
|
||||||
|
alterationId: String,
|
||||||
|
) {
|
||||||
|
alterationStore.deleteAlteration(alterationId = alterationId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun transformToAlterationFieldMap(
|
private fun transformToAlterationFieldMap(
|
||||||
alterations: Map<String, Alteration>,
|
alterations: Map<String, Alteration>,
|
||||||
actives: List<String>,
|
actives: List<String>,
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,92 @@
|
||||||
package com.pixelized.desktop.lwa.repository.alteration
|
package com.pixelized.desktop.lwa.repository.alteration
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.network.LwaClient
|
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.Alteration
|
||||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
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
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AlterationStore(
|
class AlterationStore(
|
||||||
|
private val networkRepository: NetworkRepository,
|
||||||
private val alterationFactory: AlterationJsonFactory,
|
private val alterationFactory: AlterationJsonFactory,
|
||||||
|
private val tagFactory: TagJsonFactory,
|
||||||
private val client: LwaClient,
|
private val client: LwaClient,
|
||||||
) {
|
) {
|
||||||
private val _alterationsFlow = MutableStateFlow<Map<String, Alteration>>(emptyMap())
|
private val _alterationsFlow = MutableStateFlow<Map<String, Alteration>>(emptyMap())
|
||||||
val alterationsFlow: StateFlow<Map<String, Alteration>> = _alterationsFlow
|
val alterationsFlow: StateFlow<Map<String, Alteration>> = _alterationsFlow
|
||||||
|
|
||||||
|
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||||
|
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateAlterations()
|
updateAlterations()
|
||||||
|
updateTags()
|
||||||
}
|
}
|
||||||
|
// data update through WebSocket.
|
||||||
|
scope.launch {
|
||||||
|
networkRepository.data.collect(::handleMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun alterations(): Collection<Alteration> {
|
||||||
|
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 updateAlterations() {
|
||||||
_alterationsFlow.value = try {
|
_alterationsFlow.value = try {
|
||||||
loadAlteration()
|
getAlteration()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println(exception.message) // TODO proper exception handling
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateAlteration(
|
||||||
|
alterationId: String,
|
||||||
|
) {
|
||||||
|
val alteration = try {
|
||||||
|
getAlteration(alterationId = alterationId)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println(exception.message) // TODO proper exception handling
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alteration == null) return
|
||||||
|
|
||||||
|
_alterationsFlow.update { alterations ->
|
||||||
|
alterations.toMutableMap().also {
|
||||||
|
it[alterationId] = alteration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateTags() {
|
||||||
|
_tagsFlow.value = try {
|
||||||
|
getAlterationTag()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
println(exception.message) // TODO proper exception handling
|
println(exception.message) // TODO proper exception handling
|
||||||
emptyMap()
|
emptyMap()
|
||||||
|
|
@ -34,17 +94,62 @@ class AlterationStore(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
private suspend fun loadAlteration(): Map<String, Alteration> {
|
private suspend fun getAlteration(): Map<String, Alteration> {
|
||||||
val request = client.alterations()
|
val request = client.alterations()
|
||||||
val data = request.map { alterationFactory.convertFromJson(json = it) }
|
val data = request.map { alterationFactory.convertFromJson(json = it) }
|
||||||
return data.associateBy { it.id }
|
return data.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun alterations(): Collection<Alteration> {
|
@Throws
|
||||||
return alterationsFlow.value.values
|
private suspend fun getAlteration(
|
||||||
|
alterationId: String,
|
||||||
|
): Alteration? {
|
||||||
|
val request = client.alterations(alterationId = alterationId)
|
||||||
|
return request?.let { alterationFactory.convertFromJson(json = it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun alteration(alterationId: String): Alteration? {
|
@Throws
|
||||||
return alterationsFlow.value[alterationId]
|
private suspend fun getAlterationTag(): Map<String, Tag> {
|
||||||
|
val request = client.alterationTags()
|
||||||
|
val data = request.map { tagFactory.convertFromJson(json = it) }
|
||||||
|
return data.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
suspend fun putAlteration(
|
||||||
|
alteration: Alteration,
|
||||||
|
) {
|
||||||
|
client.updateAlteration(
|
||||||
|
alterationJson = alterationFactory.convertToJson(data = alteration)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
suspend fun deleteAlteration(
|
||||||
|
alterationId: String
|
||||||
|
) {
|
||||||
|
client.deleteAlteration(
|
||||||
|
alterationId = alterationId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// region: WebSocket & data update.
|
||||||
|
|
||||||
|
private suspend fun handleMessage(message: SocketMessage) {
|
||||||
|
when (message) {
|
||||||
|
is ApiSynchronisation.AlterationUpdate -> updateAlteration(
|
||||||
|
alterationId = message.alterationId,
|
||||||
|
)
|
||||||
|
|
||||||
|
is ApiSynchronisation.AlterationDelete -> _alterationsFlow.update { alterations ->
|
||||||
|
alterations.toMutableMap().also {
|
||||||
|
it.remove(message.alterationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -19,9 +19,14 @@ class CharacterSheetRepository(
|
||||||
val characterSheetPreviewFlow get() = store.previewFlow
|
val characterSheetPreviewFlow get() = store.previewFlow
|
||||||
|
|
||||||
val characterDetailFlow get() = store.detailFlow
|
val characterDetailFlow get() = store.detailFlow
|
||||||
|
val tagsFlow get() = store.tagsFlow
|
||||||
|
|
||||||
suspend fun updateCharacterPreviews() {
|
suspend fun updateCharacterPreviews() {
|
||||||
store.charactersPreview()
|
store.updateCharactersPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateTags() {
|
||||||
|
store.updateTags()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
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.ApiSynchronisation
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||||
|
|
@ -23,7 +25,7 @@ class CharacterSheetStore(
|
||||||
private val client: LwaClient,
|
private val client: LwaClient,
|
||||||
private val network: NetworkRepository,
|
private val network: NetworkRepository,
|
||||||
private val factory: CharacterSheetJsonFactory,
|
private val factory: CharacterSheetJsonFactory,
|
||||||
private val useCase: CharacterSheetUseCase,
|
private val tagFactory: TagJsonFactory,
|
||||||
) {
|
) {
|
||||||
private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
|
private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
|
||||||
val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow
|
val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow
|
||||||
|
|
@ -31,11 +33,15 @@ class CharacterSheetStore(
|
||||||
private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
|
private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
|
||||||
val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow
|
val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow
|
||||||
|
|
||||||
|
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||||
|
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
// initial data loading.
|
// initial data loading.
|
||||||
scope.launch {
|
scope.launch {
|
||||||
charactersPreview()
|
updateCharactersPreview()
|
||||||
|
updateTags()
|
||||||
}
|
}
|
||||||
// data update through WebSocket.
|
// data update through WebSocket.
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
@ -45,7 +51,7 @@ class CharacterSheetStore(
|
||||||
|
|
||||||
// region Rest
|
// region Rest
|
||||||
|
|
||||||
suspend fun charactersPreview(): List<CharacterSheetPreview> {
|
suspend fun updateCharactersPreview(): List<CharacterSheetPreview> {
|
||||||
val request = try {
|
val request = try {
|
||||||
client.characters()
|
client.characters()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
@ -58,6 +64,21 @@ class CharacterSheetStore(
|
||||||
return _previewFlow.update(characters)
|
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 getCharacterSheet(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
forceUpdate: Boolean = false,
|
forceUpdate: Boolean = false,
|
||||||
|
|
@ -110,7 +131,7 @@ class CharacterSheetStore(
|
||||||
forceUpdate = true,
|
forceUpdate = true,
|
||||||
)
|
)
|
||||||
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
|
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
|
||||||
charactersPreview()
|
updateCharactersPreview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,6 +147,9 @@ class CharacterSheetStore(
|
||||||
sheets.toMutableMap().also { it.remove(message.characterSheetId) }
|
sheets.toMutableMap().also { it.remove(message.characterSheetId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ApiSynchronisation.AlterationUpdate -> Unit
|
||||||
|
is ApiSynchronisation.AlterationDelete -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
is CharacterSheetEvent -> when (message) {
|
is CharacterSheetEvent -> when (message) {
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
data class LwaTextFieldUio(
|
data class LwaTextFieldUio(
|
||||||
val enable: Boolean = true,
|
val enable: Boolean = true,
|
||||||
val isError: StateFlow<Boolean>,
|
val isError: StateFlow<Boolean>,
|
||||||
val labelFlow: StateFlow<String?>,
|
val labelFlow: StateFlow<String?>?,
|
||||||
val valueFlow: StateFlow<String>,
|
val valueFlow: StateFlow<String>,
|
||||||
val placeHolderFlow: StateFlow<String?>,
|
val placeHolderFlow: StateFlow<String?>?,
|
||||||
val onValueChange: (String) -> Unit,
|
val onValueChange: (String) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -43,9 +43,9 @@ fun LwaTextField(
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
val label = field.labelFlow.collectAsState()
|
val label = field.labelFlow?.collectAsState()
|
||||||
|
val placeHolder = field.placeHolderFlow?.collectAsState()
|
||||||
val value = field.valueFlow.collectAsState()
|
val value = field.valueFlow.collectAsState()
|
||||||
val placeHolder = field.placeHolderFlow.collectAsState()
|
|
||||||
val isError = field.isError.collectAsState()
|
val isError = field.isError.collectAsState()
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
|
|
@ -56,7 +56,7 @@ fun LwaTextField(
|
||||||
},
|
},
|
||||||
enabled = field.enable,
|
enabled = field.enable,
|
||||||
singleLine = singleLine,
|
singleLine = singleLine,
|
||||||
placeholder = placeHolder.value?.let {
|
placeholder = placeHolder?.value?.let {
|
||||||
{
|
{
|
||||||
Text(
|
Text(
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|
@ -66,7 +66,7 @@ fun LwaTextField(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isError = isError.value,
|
isError = isError.value,
|
||||||
label = label.value?.let {
|
label = label?.value?.let {
|
||||||
{
|
{
|
||||||
Text(
|
Text(
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
|
@ -15,8 +16,7 @@ object CharacterSheetEditDestination {
|
||||||
|
|
||||||
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
|
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
|
||||||
|
|
||||||
fun navigationRoute(characterSheetId: String?) = ROUTE +
|
fun navigationRoute(characterSheetId: String?) = "$ROUTE?$CHARACTER_ID=$characterSheetId"
|
||||||
"?$CHARACTER_ID=$characterSheetId"
|
|
||||||
|
|
||||||
fun arguments() = listOf(
|
fun arguments() = listOf(
|
||||||
navArgument(CHARACTER_ID) {
|
navArgument(CHARACTER_ID) {
|
||||||
|
|
@ -25,6 +25,7 @@ object CharacterSheetEditDestination {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Argument(
|
data class Argument(
|
||||||
val id: String?,
|
val id: String?,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationPage
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationPage
|
||||||
|
|
||||||
object GMAlterationDestination {
|
object GMAlterationDestination {
|
||||||
private const val ROUTE = "GameMasterAlteration"
|
private const val ROUTE = "GameMasterAlteration"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
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.utils.extention.ARG
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
object GMAlterationEditDestination {
|
||||||
|
private const val ROUTE = "GameMasterAlterationEdit"
|
||||||
|
private const val ALTERATION_ID = "id"
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
fun baseRoute() = "$ROUTE?${ALTERATION_ID.ARG}"
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
fun navigationRoute(alterationId: String?) = "$ROUTE?$ALTERATION_ID=$alterationId"
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
fun arguments() = listOf(
|
||||||
|
navArgument(ALTERATION_ID) {
|
||||||
|
nullable = true
|
||||||
|
type = NavType.StringType
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class Argument(
|
||||||
|
val id: String?,
|
||||||
|
) {
|
||||||
|
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||||
|
id = savedStateHandle.get<String>(ALTERATION_ID),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableGameMasterAlterationEditPage() {
|
||||||
|
composable(
|
||||||
|
route = GMAlterationEditDestination.baseRoute(),
|
||||||
|
arguments = GMAlterationEditDestination.arguments(),
|
||||||
|
) {
|
||||||
|
GMAlterationEditPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToGameMasterAlterationEditPage(
|
||||||
|
alterationId: String?,
|
||||||
|
) {
|
||||||
|
val route = GMAlterationEditDestination.navigationRoute(
|
||||||
|
alterationId = alterationId,
|
||||||
|
)
|
||||||
|
navigate(route = route)
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterPage
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterPage
|
||||||
|
|
||||||
object GMCharacterDestination {
|
object GMCharacterDestination {
|
||||||
private const val ROUTE = "GameMasterCharacter"
|
private const val ROUTE = "GameMasterCharacter"
|
||||||
|
|
|
||||||
|
|
@ -20,27 +20,18 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.pixelized.desktop.lwa.LocalBlurController
|
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
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.GMActionDestination
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
|
||||||
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationPage
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterCharacterPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterCharacterPage
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterObjectPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterObjectPage
|
||||||
|
|
@ -48,11 +39,8 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.nav
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationPage
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterPage
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterObjectPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterObjectPage
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMTab
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMTabUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTab
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTabUio
|
|
||||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors
|
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
|
@ -175,6 +163,7 @@ private fun GameMasterContent(
|
||||||
composableGameMasterActionPage()
|
composableGameMasterActionPage()
|
||||||
composableGameMasterCharacterPage()
|
composableGameMasterCharacterPage()
|
||||||
composableGameMasterAlterationPage()
|
composableGameMasterAlterationPage()
|
||||||
|
composableGameMasterAlterationEditPage()
|
||||||
composableGameMasterObjectPage()
|
composableGameMasterObjectPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.action
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -6,10 +6,8 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.minimumInteractiveComponentSize
|
import androidx.compose.material.minimumInteractiveComponentSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAction
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||||
|
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||||
|
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||||
|
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_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,
|
||||||
|
) {
|
||||||
|
suspend fun createForm(
|
||||||
|
alteration: Alteration?,
|
||||||
|
): 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)))
|
||||||
|
|
||||||
|
return GMAlterationEditPageUio(
|
||||||
|
id = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)),
|
||||||
|
valueFlow = id,
|
||||||
|
placeHolderFlow = null,
|
||||||
|
onValueChange = { id.value = it },
|
||||||
|
),
|
||||||
|
label = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_label)),
|
||||||
|
valueFlow = label,
|
||||||
|
placeHolderFlow = null,
|
||||||
|
onValueChange = { label.value = it },
|
||||||
|
),
|
||||||
|
description = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_description)),
|
||||||
|
valueFlow = description,
|
||||||
|
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 },
|
||||||
|
),
|
||||||
|
fields = fields,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createField(
|
||||||
|
alteration: Alteration.Field?,
|
||||||
|
): GMAlterationEditPageUio.SkillUio {
|
||||||
|
val idFlow = MutableStateFlow(alteration?.fieldId ?: "")
|
||||||
|
val expressionFlow = MutableStateFlow(alteration?.expression?.toString() ?: "")
|
||||||
|
|
||||||
|
return GMAlterationEditPageUio.SkillUio(
|
||||||
|
key = "${UUID.randomUUID()}-${System.currentTimeMillis()}",
|
||||||
|
id = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_field_id)),
|
||||||
|
valueFlow = idFlow,
|
||||||
|
placeHolderFlow = null,
|
||||||
|
onValueChange = { idFlow.value = it },
|
||||||
|
),
|
||||||
|
expression = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_field_expression)),
|
||||||
|
valueFlow = expressionFlow,
|
||||||
|
placeHolderFlow = null,
|
||||||
|
onValueChange = { expressionFlow.value = it },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createAlteration(
|
||||||
|
form: GMAlterationEditPageUio?,
|
||||||
|
): Alteration? {
|
||||||
|
if (form == null) return null
|
||||||
|
|
||||||
|
return Alteration(
|
||||||
|
id = form.id.valueFlow.value,
|
||||||
|
metadata = Alteration.MetaData(
|
||||||
|
name = form.label.valueFlow.value,
|
||||||
|
description = form.description.valueFlow.value,
|
||||||
|
),
|
||||||
|
tags = form.tags.valueFlow.value.split(","),
|
||||||
|
fields = form.fields.value.mapNotNull { field ->
|
||||||
|
expressionParser.parse(input = field.expression.valueFlow.value)?.let {
|
||||||
|
Alteration.Field(
|
||||||
|
fieldId = field.id.valueFlow.value,
|
||||||
|
expression = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
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.Text
|
||||||
|
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.collectAsState
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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.unit.dp
|
||||||
|
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.theme.color.component.LwaButtonColors
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
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.ic_save_24dp
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class GMAlterationEditPageUio(
|
||||||
|
val id: LwaTextFieldUio,
|
||||||
|
val label: LwaTextFieldUio,
|
||||||
|
val description: LwaTextFieldUio,
|
||||||
|
val tags: LwaTextFieldUio,
|
||||||
|
val fields: MutableStateFlow<List<SkillUio>>,
|
||||||
|
) {
|
||||||
|
@Stable
|
||||||
|
data class SkillUio(
|
||||||
|
val key: String,
|
||||||
|
val id: LwaTextFieldUio,
|
||||||
|
val expression: LwaTextFieldUio,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
object GMAlterationEditPageDefault {
|
||||||
|
val paddings = PaddingValues(all = 8.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMAlterationEditPage(
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlterationEditKeyHandler(
|
||||||
|
onDismissRequest = {
|
||||||
|
screen.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GMAlterationEditContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
form: GMAlterationEditPageUio,
|
||||||
|
paddings: PaddingValues,
|
||||||
|
addField: () -> Unit,
|
||||||
|
removeField: (index: Int) -> Unit,
|
||||||
|
onSave: () -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
) {
|
||||||
|
val fields = form.fields.collectAsState()
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
private fun AlterationEditKeyHandler(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
) {
|
||||||
|
KeyHandler {
|
||||||
|
when {
|
||||||
|
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {
|
||||||
|
onDismissRequest()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
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.repository.alteration.AlterationRepository
|
||||||
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class GMAlterationEditViewModel(
|
||||||
|
private val alterationRepository: AlterationRepository,
|
||||||
|
private val factory: GMAlterationEditFactory,
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
) : ViewModel() {
|
||||||
|
private val argument = GMAlterationEditDestination.Argument(savedStateHandle)
|
||||||
|
|
||||||
|
private val _form = MutableStateFlow<GMAlterationEditPageUio?>(null)
|
||||||
|
val form: StateFlow<GMAlterationEditPageUio?> get() = _form
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_form.value = factory.createForm(
|
||||||
|
alteration = alterationRepository.alteration(alterationId = argument.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun save() {
|
||||||
|
val edited = factory.createAlteration(form = form.value)
|
||||||
|
|
||||||
|
val actual = alterationRepository.alterationFlow.value[edited?.id]
|
||||||
|
|
||||||
|
// TODO if argument.id == null et actual?.id != null on créer et on écrase existant !!!
|
||||||
|
|
||||||
|
if (edited != null)
|
||||||
|
|
||||||
|
alterationRepository.updateAlteration(edited)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addField() {
|
||||||
|
form.value?.fields?.update { fields ->
|
||||||
|
fields.toMutableList().also {
|
||||||
|
it.add(element = factory.createField(alteration = null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeField(index: Int) {
|
||||||
|
form.value?.fields?.update { fields ->
|
||||||
|
fields.toMutableList().also {
|
||||||
|
it.removeAt(index = index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -26,6 +26,8 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
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.screen.gamemaster.common.tag.GMTag
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__delete
|
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__delete
|
||||||
|
|
@ -37,7 +39,7 @@ import org.jetbrains.compose.resources.stringResource
|
||||||
data class GMAlterationUio(
|
data class GMAlterationUio(
|
||||||
val alterationId: String,
|
val alterationId: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val tags: List<GMTagItemUio>,
|
val tags: List<GMTagUio>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlterationUio
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
|
||||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||||
|
import com.pixelized.shared.lwa.model.tag.Tag
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
||||||
class GMAlterationFactory {
|
class GMAlterationFactory(
|
||||||
|
private val tagFactory: GMTagFactory,
|
||||||
|
) {
|
||||||
|
|
||||||
fun filterAlteration(
|
fun filterAlteration(
|
||||||
alterations: Collection<Alteration>,
|
alterations: Collection<Alteration>,
|
||||||
|
|
@ -27,6 +29,7 @@ class GMAlterationFactory {
|
||||||
|
|
||||||
fun convertToGMAlterationUio(
|
fun convertToGMAlterationUio(
|
||||||
alterations: List<Alteration>,
|
alterations: List<Alteration>,
|
||||||
|
tags: Map<String, Tag>,
|
||||||
selectedTagId: String?,
|
selectedTagId: String?,
|
||||||
): List<GMAlterationUio> {
|
): List<GMAlterationUio> {
|
||||||
return alterations
|
return alterations
|
||||||
|
|
@ -34,28 +37,16 @@ class GMAlterationFactory {
|
||||||
GMAlterationUio(
|
GMAlterationUio(
|
||||||
alterationId = alteration.id,
|
alterationId = alteration.id,
|
||||||
label = alteration.metadata.name,
|
label = alteration.metadata.name,
|
||||||
tags = alteration.tags.map { tag ->
|
tags = alteration.tags.mapNotNull {
|
||||||
GMTagItemUio(
|
tags[it]?.let { tag ->
|
||||||
id = tag,
|
tagFactory.convertToGMTagItemUio(
|
||||||
label = tag,
|
tag = tag,
|
||||||
highlight = tag == selectedTagId,
|
selectedTagId = selectedTagId,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedWith(compareBy(Collator.getInstance()) { it.label })
|
.sortedWith(compareBy(Collator.getInstance()) { it.label })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertToGMTagItemUio(
|
|
||||||
alterationTagIds: List<String>,
|
|
||||||
selectedTagId: String?,
|
|
||||||
): List<GMTagItemUio> {
|
|
||||||
return alterationTagIds.map {
|
|
||||||
GMTagItemUio(
|
|
||||||
id = it,
|
|
||||||
label = it,
|
|
||||||
highlight = it == selectedTagId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -21,17 +21,20 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlteration
|
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlterationUio
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationEditPage
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMFilterHeader
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
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.color.component.LwaButtonColors
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__create
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__create_character_sheet
|
import lwacharactersheet.composeapp.generated.resources.game_master__create_character_sheet
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
@ -40,8 +43,11 @@ import org.koin.compose.viewmodel.koinViewModel
|
||||||
fun GMAlterationPage(
|
fun GMAlterationPage(
|
||||||
viewModel: GMAlterationViewModel = koinViewModel(),
|
viewModel: GMAlterationViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
|
val screen = LocalScreenController.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val alterations = viewModel.alterations.collectAsState()
|
val alterations = viewModel.alterations.collectAsState()
|
||||||
val tags = viewModel.alterationTags.collectAsState()
|
val tags = viewModel.tags.collectAsState()
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
GMAlterationContent(
|
GMAlterationContent(
|
||||||
|
|
@ -50,9 +56,17 @@ fun GMAlterationPage(
|
||||||
tags = tags,
|
tags = tags,
|
||||||
alterations = alterations,
|
alterations = alterations,
|
||||||
onTag = viewModel::onTag,
|
onTag = viewModel::onTag,
|
||||||
onAlterationEdit = { },
|
onAlterationEdit = {
|
||||||
onAlterationDelete = { },
|
screen.navigateToGameMasterAlterationEditPage(alterationId = it)
|
||||||
onAlterationCreate = { },
|
},
|
||||||
|
onAlterationDelete = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.deleteAlteration(alterationId = it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAlterationCreate = {
|
||||||
|
screen.navigateToGameMasterAlterationEditPage(alterationId = null)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +77,7 @@ private fun GMAlterationContent(
|
||||||
padding: Dp = 8.dp,
|
padding: Dp = 8.dp,
|
||||||
spacing: Dp = 8.dp,
|
spacing: Dp = 8.dp,
|
||||||
filter: LwaTextFieldUio,
|
filter: LwaTextFieldUio,
|
||||||
tags: State<List<GMTagItemUio>>,
|
tags: State<List<GMTagUio>>,
|
||||||
alterations: State<List<GMAlterationUio>>,
|
alterations: State<List<GMAlterationUio>>,
|
||||||
onTag: (String) -> Unit,
|
onTag: (String) -> Unit,
|
||||||
onAlterationEdit: (String) -> Unit,
|
onAlterationEdit: (String) -> Unit,
|
||||||
|
|
@ -131,7 +145,7 @@ private fun GMAlterationContent(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(end = 4.dp),
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
text = stringResource(Res.string.game_master__create_character_sheet),
|
text = stringResource(Res.string.game_master__alteration__create),
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
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
|
||||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
@ -20,33 +20,29 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character__
|
||||||
import org.jetbrains.compose.resources.getString
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
class GMAlterationViewModel(
|
class GMAlterationViewModel(
|
||||||
alterationRepository: AlterationRepository,
|
private val alterationRepository: AlterationRepository,
|
||||||
alterationFactory: GMAlterationFactory,
|
alterationFactory: GMAlterationFactory,
|
||||||
|
tagFactory: GMTagFactory,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _filter = MutableStateFlow("")
|
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||||
|
private val filterValue = MutableStateFlow("")
|
||||||
|
|
||||||
val filter = LwaTextFieldUio(
|
val filter = LwaTextFieldUio(
|
||||||
enable = true,
|
enable = true,
|
||||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||||
valueFlow = _filter,
|
valueFlow = filterValue,
|
||||||
isError = MutableStateFlow(false),
|
isError = MutableStateFlow(false),
|
||||||
placeHolderFlow = MutableStateFlow(null),
|
placeHolderFlow = MutableStateFlow(null),
|
||||||
onValueChange = { _filter.value = it },
|
onValueChange = { filterValue.value = it },
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
val tags: StateFlow<List<GMTagUio>> = combine(
|
||||||
private val alterationTagIds = alterationRepository.alterationFlow
|
alterationRepository.tagsFlow,
|
||||||
.mapLatest { alterations -> alterations.values.flatMap { it.tags }.toSet().toList() }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
|
||||||
|
|
||||||
val alterationTags = combine(
|
|
||||||
alterationTagIds,
|
|
||||||
selectedTagId,
|
selectedTagId,
|
||||||
) { alterationTagIds, selectedTagId ->
|
) { tags, selectedTagId ->
|
||||||
alterationFactory.convertToGMTagItemUio(
|
tagFactory.convertToGMTagItemUio(
|
||||||
alterationTagIds = alterationTagIds,
|
tags = tags.values,
|
||||||
selectedTagId = selectedTagId,
|
selectedTagId = selectedTagId,
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
|
|
@ -55,22 +51,20 @@ class GMAlterationViewModel(
|
||||||
initialValue = emptyList(),
|
initialValue = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
val alterations: StateFlow<List<GMAlterationUio>> = combine(
|
||||||
val alterations = combine(
|
|
||||||
alterationRepository.alterationFlow,
|
alterationRepository.alterationFlow,
|
||||||
|
alterationRepository.tagsFlow,
|
||||||
filter.valueFlow.map { it.unAccent() },
|
filter.valueFlow.map { it.unAccent() },
|
||||||
selectedTagId,
|
selectedTagId,
|
||||||
transform = { alterations, unAccentFilter, selectedTagId ->
|
) { alterations, tags, unAccentFilter, selectedTagId ->
|
||||||
alterationFactory.filterAlteration(
|
alterationFactory.convertToGMAlterationUio(
|
||||||
|
alterations = alterationFactory.filterAlteration(
|
||||||
alterations = alterations.values,
|
alterations = alterations.values,
|
||||||
unAccentFilter = unAccentFilter,
|
unAccentFilter = unAccentFilter,
|
||||||
selectedTagId = selectedTagId
|
selectedTagId = selectedTagId,
|
||||||
)
|
),
|
||||||
}
|
tags = tags,
|
||||||
).mapLatest { alterations ->
|
selectedTagId = selectedTagId,
|
||||||
alterationFactory.convertToGMAlterationUio(
|
|
||||||
alterations = alterations,
|
|
||||||
selectedTagId = selectedTagId.value
|
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
|
|
@ -86,4 +80,8 @@ class GMAlterationViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteAlteration(alterationId: String) {
|
||||||
|
alterationRepository.deleteAlteration(alterationId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
|
||||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
|
||||||
import org.jetbrains.compose.resources.getString
|
|
||||||
|
|
||||||
class GMCharacterFactory {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val PLAYER_ID = "PLAYER"
|
|
||||||
const val NPC_ID = "NPC"
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertToGMCharacterPreviewUio(
|
|
||||||
campaign: Campaign,
|
|
||||||
characters: List<CharacterSheetPreview>,
|
|
||||||
filter: String,
|
|
||||||
tags: Map<String, Boolean>,
|
|
||||||
): List<GMCharacterItemUio> {
|
|
||||||
val normalizedFilter = filter.unAccent()
|
|
||||||
|
|
||||||
return characters.mapNotNull {
|
|
||||||
convertToGMCharacterPreviewUio(
|
|
||||||
campaign = campaign,
|
|
||||||
character = it,
|
|
||||||
filter = normalizedFilter,
|
|
||||||
tags = tags,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun convertToGMCharacterPreviewUio(
|
|
||||||
campaign: Campaign,
|
|
||||||
character: CharacterSheetPreview,
|
|
||||||
filter: String,
|
|
||||||
tags: Map<String, Boolean>,
|
|
||||||
): GMCharacterItemUio? {
|
|
||||||
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
|
||||||
val isPlayer = campaign.characters.firstOrNull {
|
|
||||||
it == character.characterSheetId
|
|
||||||
} != null
|
|
||||||
// get all characterInstanceId from the npcs list corresponding to this CharacterSheet if any
|
|
||||||
val isNpc = campaign.npcs.firstOrNull {
|
|
||||||
it == character.characterSheetId
|
|
||||||
} != null
|
|
||||||
// Filter process : Name.
|
|
||||||
if (filter.isNotEmpty()) {
|
|
||||||
val normalizedName = character.name.unAccent()
|
|
||||||
// If the filter is not empty and the character is not
|
|
||||||
val nameHighlight = normalizedName.contains(other = filter, ignoreCase = true)
|
|
||||||
if (nameHighlight.not()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Tag filter process : Player.
|
|
||||||
if (tags[PLAYER_ID] == true && isPlayer.not()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
// Tag filter process : Npc.
|
|
||||||
if (tags[NPC_ID] == true && isNpc.not()) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
// Build the call tag list.
|
|
||||||
val previewTagsList = buildList {
|
|
||||||
if (isPlayer) {
|
|
||||||
add(
|
|
||||||
GMTagItemUio(
|
|
||||||
id = PLAYER_ID,
|
|
||||||
label = getString(Res.string.game_master__character_tag__character),
|
|
||||||
highlight = tags[PLAYER_ID] ?: false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isNpc) {
|
|
||||||
add(
|
|
||||||
GMTagItemUio(
|
|
||||||
id = NPC_ID,
|
|
||||||
label = getString(Res.string.game_master__character_tag__npc),
|
|
||||||
highlight = tags[NPC_ID] ?: false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// build the cell action list
|
|
||||||
val actions = buildList {
|
|
||||||
add(Action.DisplayPortrait)
|
|
||||||
when {
|
|
||||||
isPlayer -> add(Action.RemoveFromGroup)
|
|
||||||
isNpc -> add(Action.RemoveFromNpc)
|
|
||||||
else -> {
|
|
||||||
add(Action.AddToGroup)
|
|
||||||
add(Action.AddToNpc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return the cell UIO.
|
|
||||||
return GMCharacterItemUio(
|
|
||||||
characterSheetId = character.characterSheetId,
|
|
||||||
name = character.name,
|
|
||||||
level = character.level,
|
|
||||||
tags = previewTagsList,
|
|
||||||
actions = actions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun convertToGMTagItemUio(
|
|
||||||
id: String?,
|
|
||||||
highlight: Boolean,
|
|
||||||
): GMTagItemUio? {
|
|
||||||
return when (id) {
|
|
||||||
PLAYER_ID -> GMTagItemUio(
|
|
||||||
id = id,
|
|
||||||
label = getString(Res.string.game_master__character_tag__character),
|
|
||||||
highlight = highlight,
|
|
||||||
)
|
|
||||||
|
|
||||||
NPC_ID -> GMTagItemUio(
|
|
||||||
id = id,
|
|
||||||
label = getString(Res.string.game_master__character_tag__npc),
|
|
||||||
highlight = highlight,
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.PointerMatcher
|
import androidx.compose.foundation.PointerMatcher
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.onClick
|
import androidx.compose.foundation.onClick
|
||||||
import androidx.compose.material.DropdownMenu
|
import androidx.compose.material.DropdownMenu
|
||||||
|
|
@ -28,9 +27,10 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.input.pointer.PointerButton
|
import androidx.compose.ui.input.pointer.PointerButton
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTag
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__add_to_group
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__add_to_group
|
||||||
|
|
@ -54,7 +54,7 @@ data class GMCharacterItemUio(
|
||||||
val characterSheetId: String,
|
val characterSheetId: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val level: Int,
|
val level: Int,
|
||||||
val tags: List<GMTagItemUio>,
|
val tags: List<GMTagUio>,
|
||||||
val actions: List<Action>,
|
val actions: List<Action>,
|
||||||
) {
|
) {
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||||
|
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||||
|
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||||
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
|
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
|
class GMCharacterFactory {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PLAYER_ID = "PLAYER"
|
||||||
|
const val NPC_ID = "NPC"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterCharacter(
|
||||||
|
campaign: Campaign,
|
||||||
|
characters: List<CharacterSheetPreview>,
|
||||||
|
unAccentFilter: String,
|
||||||
|
selectedTagId: String?,
|
||||||
|
): List<CharacterSheetPreview> {
|
||||||
|
return characters.filter {
|
||||||
|
val matchName = it.name.unAccent().contains(
|
||||||
|
other = unAccentFilter,
|
||||||
|
ignoreCase = true,
|
||||||
|
)
|
||||||
|
val matchTag = when (selectedTagId) {
|
||||||
|
null -> true
|
||||||
|
PLAYER_ID -> campaign.characters.contains(it.characterSheetId)
|
||||||
|
NPC_ID -> campaign.npcs.contains(it.characterSheetId)
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
matchName && matchTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertToGMCharacterPreviewUio(
|
||||||
|
campaign: Campaign,
|
||||||
|
characters: List<CharacterSheetPreview>,
|
||||||
|
tagIdMap: List<GMTagUio>,
|
||||||
|
): List<GMCharacterItemUio> {
|
||||||
|
return characters.map {
|
||||||
|
convertToGMCharacterPreviewUio(
|
||||||
|
campaign = campaign,
|
||||||
|
character = it,
|
||||||
|
tagIdMap = tagIdMap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertToGMCharacterPreviewUio(
|
||||||
|
campaign: Campaign,
|
||||||
|
character: CharacterSheetPreview,
|
||||||
|
tagIdMap: List<GMTagUio>,
|
||||||
|
): GMCharacterItemUio {
|
||||||
|
val isPlayer = campaign.characters.contains(character.characterSheetId)
|
||||||
|
val isNpc = campaign.npcs.contains(character.characterSheetId)
|
||||||
|
|
||||||
|
// Build the call tag list.
|
||||||
|
val tags = tagIdMap.filter {
|
||||||
|
when (it.id) {
|
||||||
|
PLAYER_ID -> isPlayer
|
||||||
|
NPC_ID -> isNpc
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the cell action list
|
||||||
|
val actions = buildList {
|
||||||
|
add(Action.DisplayPortrait)
|
||||||
|
when {
|
||||||
|
isPlayer -> add(Action.RemoveFromGroup)
|
||||||
|
isNpc -> add(Action.RemoveFromNpc)
|
||||||
|
else -> {
|
||||||
|
add(Action.AddToGroup)
|
||||||
|
add(Action.AddToNpc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return the cell UIO.
|
||||||
|
return GMCharacterItemUio(
|
||||||
|
characterSheetId = character.characterSheetId,
|
||||||
|
name = character.name,
|
||||||
|
level = character.level,
|
||||||
|
tags = tags,
|
||||||
|
actions = actions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun convertToGMTagItemUio(
|
||||||
|
id: String?,
|
||||||
|
highlight: Boolean,
|
||||||
|
): GMTagUio? {
|
||||||
|
return when (id) {
|
||||||
|
PLAYER_ID -> GMTagUio(
|
||||||
|
id = id,
|
||||||
|
label = getString(Res.string.game_master__character_tag__character),
|
||||||
|
highlight = highlight,
|
||||||
|
)
|
||||||
|
|
||||||
|
NPC_ID -> GMTagUio(
|
||||||
|
id = id,
|
||||||
|
label = getString(Res.string.game_master__character_tag__npc),
|
||||||
|
highlight = highlight,
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -44,10 +44,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetai
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacter
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMFilterHeader
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
|
||||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
|
@ -163,7 +161,7 @@ fun GMCharacterContent(
|
||||||
padding: Dp = 8.dp,
|
padding: Dp = 8.dp,
|
||||||
spacing: Dp = 8.dp,
|
spacing: Dp = 8.dp,
|
||||||
filter: LwaTextFieldUio,
|
filter: LwaTextFieldUio,
|
||||||
tags: State<List<GMTagItemUio>>,
|
tags: State<List<GMTagUio>>,
|
||||||
characters: State<List<GMCharacterItemUio>>,
|
characters: State<List<GMCharacterItemUio>>,
|
||||||
onTag: (String) -> Unit,
|
onTag: (String) -> Unit,
|
||||||
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
|
@ -6,20 +6,20 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||||
|
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character__filter
|
import lwacharactersheet.composeapp.generated.resources.game_master__character__filter
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
|
||||||
import org.jetbrains.compose.resources.getString
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
class GMCharacterViewModel(
|
class GMCharacterViewModel(
|
||||||
|
|
@ -27,41 +27,53 @@ class GMCharacterViewModel(
|
||||||
private val campaignRepository: CampaignRepository,
|
private val campaignRepository: CampaignRepository,
|
||||||
characterSheetRepository: CharacterSheetRepository,
|
characterSheetRepository: CharacterSheetRepository,
|
||||||
private val factory: GMCharacterFactory,
|
private val factory: GMCharacterFactory,
|
||||||
|
private val tagFactory: GMTagFactory,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _filter = MutableStateFlow("")
|
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||||
|
private val filterValue = MutableStateFlow("")
|
||||||
|
|
||||||
val filter = LwaTextFieldUio(
|
val filter = LwaTextFieldUio(
|
||||||
enable = true,
|
enable = true,
|
||||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||||
valueFlow = _filter,
|
valueFlow = filterValue,
|
||||||
isError = MutableStateFlow(false),
|
isError = MutableStateFlow(false),
|
||||||
placeHolderFlow = MutableStateFlow(null),
|
placeHolderFlow = MutableStateFlow(null),
|
||||||
onValueChange = { _filter.value = it },
|
onValueChange = { filterValue.value = it },
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _tags = MutableStateFlow(
|
val tags = combine(
|
||||||
mapOf(
|
characterSheetRepository.tagsFlow,
|
||||||
GMCharacterFactory.PLAYER_ID to false,
|
selectedTagId,
|
||||||
GMCharacterFactory.NPC_ID to false
|
) { tags, selectedTagId ->
|
||||||
|
tagFactory.convertToGMTagItemUio(
|
||||||
|
tags = tags.values,
|
||||||
|
selectedTagId = selectedTagId,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
val tags = _tags.map { it: Map<String, Boolean> ->
|
|
||||||
it.mapNotNull { (id, highlight) ->
|
|
||||||
factory.convertToGMTagItemUio(id = id, highlight = highlight)
|
|
||||||
}
|
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Lazily,
|
||||||
initialValue = emptyList(),
|
initialValue = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val characters = combine(
|
val characters = combine(
|
||||||
campaignRepository.campaignFlow,
|
campaignRepository.campaignFlow,
|
||||||
characterSheetRepository.characterSheetPreviewFlow,
|
characterSheetRepository.characterSheetPreviewFlow,
|
||||||
filter.valueFlow,
|
filter.valueFlow.map { it.unAccent() },
|
||||||
_tags,
|
tags,
|
||||||
factory::convertToGMCharacterPreviewUio,
|
selectedTagId,
|
||||||
).stateIn(
|
) { campaign, characters, unAccentFilter, tagIdMap, selectedTagId ->
|
||||||
|
factory.convertToGMCharacterPreviewUio(
|
||||||
|
characters = factory.filterCharacter(
|
||||||
|
campaign = campaign,
|
||||||
|
characters = characters,
|
||||||
|
unAccentFilter = unAccentFilter,
|
||||||
|
selectedTagId = selectedTagId,
|
||||||
|
),
|
||||||
|
campaign = campaign,
|
||||||
|
tagIdMap = tagIdMap,
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
initialValue = emptyList(),
|
initialValue = emptyList(),
|
||||||
|
|
@ -106,8 +118,11 @@ class GMCharacterViewModel(
|
||||||
fun onTag(
|
fun onTag(
|
||||||
id: String,
|
id: String,
|
||||||
) {
|
) {
|
||||||
_tags.value = _tags.value.toMutableMap().also {
|
selectedTagId.update {
|
||||||
it[id] = it.getOrPut(id) { true }.not()
|
when (it) {
|
||||||
|
id -> null
|
||||||
|
else -> id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
|
|
@ -28,6 +28,8 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTag
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
|
@ -41,7 +43,7 @@ fun GMFilterHeader(
|
||||||
padding: Dp = 16.dp,
|
padding: Dp = 16.dp,
|
||||||
spacing: Dp = 8.dp,
|
spacing: Dp = 8.dp,
|
||||||
filter: LwaTextFieldUio,
|
filter: LwaTextFieldUio,
|
||||||
tags: State<List<GMTagItemUio>>,
|
tags: State<List<GMTagUio>>,
|
||||||
onTag: (String) -> Unit,
|
onTag: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
@ -54,7 +56,6 @@ fun GMFilterHeader(
|
||||||
field = filter,
|
field = filter,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
val value = filter.valueFlow.collectAsState()
|
val value = filter.valueFlow.collectAsState()
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = value.value.isNotBlank(),
|
visible = value.value.isNotBlank(),
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
|
|
@ -72,28 +73,37 @@ fun GMFilterHeader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
LazyRow(
|
AnimatedVisibility(
|
||||||
modifier = Modifier.draggable(
|
visible = tags.value.isNotEmpty(),
|
||||||
orientation = Orientation.Horizontal,
|
enter = fadeIn(),
|
||||||
state = rememberDraggableState { delta ->
|
exit = fadeOut(),
|
||||||
scope.launch {
|
|
||||||
lazyListState.scrollBy(-delta)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
state = lazyListState,
|
|
||||||
contentPadding = remember(padding, spacing) {
|
|
||||||
PaddingValues(horizontal = padding, vertical = spacing)
|
|
||||||
},
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
|
||||||
) {
|
) {
|
||||||
items(
|
LazyRow(
|
||||||
items = tags.value,
|
modifier = Modifier.draggable(
|
||||||
) { tag ->
|
orientation = Orientation.Horizontal,
|
||||||
GMTag(
|
state = rememberDraggableState { delta ->
|
||||||
tag = tag,
|
scope.launch {
|
||||||
onTag = { onTag(tag.id) },
|
lazyListState.scrollBy(-delta)
|
||||||
)
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
state = lazyListState,
|
||||||
|
contentPadding = remember(padding, spacing) {
|
||||||
|
PaddingValues(
|
||||||
|
horizontal = padding,
|
||||||
|
vertical = spacing,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = tags.value,
|
||||||
|
) { tag ->
|
||||||
|
GMTag(
|
||||||
|
tag = tag,
|
||||||
|
onTag = { onTag(tag.id) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common
|
||||||
|
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -19,7 +19,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class GMTagItemUio(
|
data class GMTagUio(
|
||||||
val id: String,
|
val id: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val highlight: Boolean,
|
val highlight: Boolean,
|
||||||
|
|
@ -36,7 +36,7 @@ fun GMTag(
|
||||||
padding: PaddingValues = GmTagDefault.padding,
|
padding: PaddingValues = GmTagDefault.padding,
|
||||||
shape: Shape = CircleShape,
|
shape: Shape = CircleShape,
|
||||||
elevation: Dp = 2.dp,
|
elevation: Dp = 2.dp,
|
||||||
tag: GMTagItemUio,
|
tag: GMTagUio,
|
||||||
onTag: (() -> Unit)? = null,
|
onTag: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val animatedColor = animateColorAsState(
|
val animatedColor = animateColorAsState(
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag
|
||||||
|
|
||||||
|
import com.pixelized.shared.lwa.model.tag.Tag
|
||||||
|
|
||||||
|
class GMTagFactory {
|
||||||
|
|
||||||
|
fun convertToGMTagItemUio(
|
||||||
|
tags: Collection<Tag>,
|
||||||
|
selectedTagId: String?,
|
||||||
|
): List<GMTagUio> {
|
||||||
|
return tags.map { tag ->
|
||||||
|
convertToGMTagItemUio(
|
||||||
|
tag = tag,
|
||||||
|
selectedTagId = selectedTagId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertToGMTagItemUio(
|
||||||
|
tag: Tag,
|
||||||
|
selectedTagId: String?,
|
||||||
|
): GMTagUio {
|
||||||
|
return GMTagUio(
|
||||||
|
id = tag.id,
|
||||||
|
label = tag.label,
|
||||||
|
highlight = tag.id == selectedTagId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.business
|
||||||
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
|
import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class SkillNormalizerUseCaseText {
|
class SkillUioNormalizerUseCaseText {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNormalization() {
|
fun testNormalization() {
|
||||||
|
|
@ -4,7 +4,7 @@ import com.pixelized.shared.lwa.usecase.SkillStepUseCase
|
||||||
import com.pixelized.shared.lwa.usecase.SkillStepUseCase.SkillStep
|
import com.pixelized.shared.lwa.usecase.SkillStepUseCase.SkillStep
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class SkillStepUseCaseTest {
|
class SkillUioStepUseCaseTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStepForSkill() {
|
fun testStepForSkill() {
|
||||||
|
|
@ -4,6 +4,7 @@ import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||||
import com.pixelized.server.lwa.model.campaign.CampaignStore
|
import com.pixelized.server.lwa.model.campaign.CampaignStore
|
||||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||||
import com.pixelized.server.lwa.model.character.CharacterSheetStore
|
import com.pixelized.server.lwa.model.character.CharacterSheetStore
|
||||||
|
import com.pixelized.server.lwa.model.tag.TagStore
|
||||||
import com.pixelized.server.lwa.server.Engine
|
import com.pixelized.server.lwa.server.Engine
|
||||||
import com.pixelized.shared.lwa.utils.PathProvider
|
import com.pixelized.shared.lwa.utils.PathProvider
|
||||||
import org.koin.core.module.dsl.createdAtStart
|
import org.koin.core.module.dsl.createdAtStart
|
||||||
|
|
@ -35,6 +36,7 @@ val storeDependencies
|
||||||
singleOf(::CharacterSheetStore)
|
singleOf(::CharacterSheetStore)
|
||||||
singleOf(::CampaignStore)
|
singleOf(::CampaignStore)
|
||||||
singleOf(::AlterationStore)
|
singleOf(::AlterationStore)
|
||||||
|
singleOf(::TagStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
val serviceDependencies
|
val serviceDependencies
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.pixelized.server.lwa.model.alteration
|
package com.pixelized.server.lwa.model.alteration
|
||||||
|
|
||||||
|
import com.pixelized.server.lwa.model.tag.TagStore
|
||||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||||
|
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||||
|
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -9,25 +12,45 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
class AlterationService(
|
class AlterationService(
|
||||||
store: AlterationStore,
|
private val alterationStore: AlterationStore,
|
||||||
|
tagStore: TagStore,
|
||||||
|
factory: AlterationJsonFactory,
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
|
||||||
private val alterationsFlow = store.alterationsFlow()
|
private val alterationHashFlow = alterationStore.alterationsFlow()
|
||||||
|
.map { alterations -> alterations.associate { it.id to factory.convertToJson(it) } }
|
||||||
private val alterationHashFlow = alterationsFlow
|
|
||||||
.map { data -> data.associateBy { it.id } }
|
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
started = SharingStarted.Eagerly,
|
started = SharingStarted.Eagerly,
|
||||||
initialValue = emptyMap()
|
initialValue = emptyMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val alterationTags = tagStore.alterationTags()
|
||||||
|
.map { it.values.toList() }
|
||||||
|
.stateIn(
|
||||||
|
scope = scope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
fun alterations(): List<AlterationJson> {
|
fun alterations(): List<AlterationJson> {
|
||||||
return alterationsFlow.value
|
return alterationHashFlow.value.values.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tags(): List<TagJson> {
|
||||||
|
return alterationTags.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun alteration(alterationId: String): AlterationJson? {
|
fun alteration(alterationId: String): AlterationJson? {
|
||||||
return alterationHashFlow.value[alterationId]
|
return alterationHashFlow.value[alterationId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun update(json: AlterationJson) {
|
||||||
|
alterationStore.save(alteration = json)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(alterationId: String): Boolean {
|
||||||
|
return alterationStore.delete(id = alterationId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
package com.pixelized.server.lwa.model.alteration
|
package com.pixelized.server.lwa.model.alteration
|
||||||
|
|
||||||
|
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||||
|
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||||
import com.pixelized.shared.lwa.utils.PathProvider
|
import com.pixelized.shared.lwa.utils.PathProvider
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class AlterationStore(
|
class AlterationStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
|
private val factory: AlterationJsonFactory,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
||||||
private val alterationsFlow = MutableStateFlow<List<AlterationJson>>(emptyList())
|
|
||||||
|
private val alterationFlow = MutableStateFlow<List<Alteration>>(emptyList())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// build a coroutine scope for async calls
|
// build a coroutine scope for async calls
|
||||||
|
|
@ -27,10 +32,10 @@ class AlterationStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun alterationsFlow(): StateFlow<List<AlterationJson>> = alterationsFlow
|
fun alterationsFlow(): StateFlow<List<Alteration>> = alterationFlow
|
||||||
|
|
||||||
private fun updateAlterations() {
|
private fun updateAlterations() {
|
||||||
alterationsFlow.value = try {
|
alterationFlow.value = try {
|
||||||
loadAlterations()
|
loadAlterations()
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
println(exception) // TODO proper exception handling
|
println(exception) // TODO proper exception handling
|
||||||
|
|
@ -42,7 +47,7 @@ class AlterationStore(
|
||||||
FileReadException::class,
|
FileReadException::class,
|
||||||
JsonConversionException::class,
|
JsonConversionException::class,
|
||||||
)
|
)
|
||||||
private fun loadAlterations(): List<AlterationJson> {
|
private fun loadAlterations(): List<Alteration> {
|
||||||
return directory
|
return directory
|
||||||
.listFiles()
|
.listFiles()
|
||||||
?.mapNotNull { file ->
|
?.mapNotNull { file ->
|
||||||
|
|
@ -56,7 +61,8 @@ class AlterationStore(
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.json.decodeFromString<AlterationJson>(json)
|
val data = this.json.decodeFromString<AlterationJson>(json)
|
||||||
|
factory.convertFromJson(data)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
throw JsonConversionException(root = exception)
|
throw JsonConversionException(root = exception)
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +70,73 @@ class AlterationStore(
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(JsonConversionException::class, FileWriteException::class)
|
||||||
|
fun save(
|
||||||
|
alteration: Alteration,
|
||||||
|
) {
|
||||||
|
val json = try {
|
||||||
|
factory.convertToJson(data = alteration)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
save(alteration = json)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(FileWriteException::class)
|
||||||
|
fun save(
|
||||||
|
alteration: AlterationJson,
|
||||||
|
) {
|
||||||
|
// encode the json into a string
|
||||||
|
val data = try {
|
||||||
|
json.encodeToString(alteration)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(id: String): Boolean {
|
||||||
|
val file = alterationFile(id = id)
|
||||||
|
val deleted = file.delete()
|
||||||
|
if (deleted) {
|
||||||
|
alterationFlow.update { alterations ->
|
||||||
|
alterations.toMutableList().also { alteration ->
|
||||||
|
alteration.removeIf { it.id == id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun alterationFile(id: String): File {
|
||||||
|
return File("${pathProvider.alterationsPath()}${id}.json")
|
||||||
|
}
|
||||||
|
|
||||||
sealed class AlterationStoreException(root: Exception) : Exception(root)
|
sealed class AlterationStoreException(root: Exception) : Exception(root)
|
||||||
class JsonConversionException(root: Exception) : AlterationStoreException(root)
|
class JsonConversionException(root: Exception) : AlterationStoreException(root)
|
||||||
class FileWriteException(root: Exception) : AlterationStoreException(root)
|
class FileWriteException(root: Exception) : AlterationStoreException(root)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.pixelized.server.lwa.model.character
|
package com.pixelized.server.lwa.model.character
|
||||||
|
|
||||||
|
import com.pixelized.server.lwa.model.tag.TagStore
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
||||||
|
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -12,12 +14,13 @@ import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
class CharacterSheetService(
|
class CharacterSheetService(
|
||||||
private val store: CharacterSheetStore,
|
private val characterStore: CharacterSheetStore,
|
||||||
|
private val tagStore: TagStore,
|
||||||
private val factory: CharacterSheetJsonFactory,
|
private val factory: CharacterSheetJsonFactory,
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
private val sheets get() = sheetsFlow.value
|
private val sheets get() = sheetsFlow.value
|
||||||
private val sheetsFlow = store.characterSheetsFlow()
|
private val sheetsFlow = characterStore.characterSheetsFlow()
|
||||||
.map { entry -> entry.associateBy { character -> character.id } }
|
.map { entry -> entry.associateBy { character -> character.id } }
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
|
|
@ -25,10 +28,22 @@ class CharacterSheetService(
|
||||||
initialValue = emptyMap()
|
initialValue = emptyMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val alterationTags = tagStore.characterTags()
|
||||||
|
.map { it.values.toList() }
|
||||||
|
.stateIn(
|
||||||
|
scope = scope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
fun character(characterSheetId: String): CharacterSheet? {
|
fun character(characterSheetId: String): CharacterSheet? {
|
||||||
return sheets[characterSheetId]
|
return sheets[characterSheetId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tags() : Collection<TagJson> {
|
||||||
|
return alterationTags.value
|
||||||
|
}
|
||||||
|
|
||||||
fun charactersJson(): List<CharacterPreviewJson> {
|
fun charactersJson(): List<CharacterPreviewJson> {
|
||||||
return sheets.map { factory.convertToPreviewJson(sheet = it.value) }
|
return sheets.map { factory.convertToPreviewJson(sheet = it.value) }
|
||||||
}
|
}
|
||||||
|
|
@ -38,13 +53,13 @@ class CharacterSheetService(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateCharacterSheet(character: CharacterSheetJson) {
|
suspend fun updateCharacterSheet(character: CharacterSheetJson) {
|
||||||
return store.save(
|
return characterStore.save(
|
||||||
sheet = factory.convertFromJson(character)
|
sheet = factory.convertFromJson(character)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCharacterSheet(characterSheetId: String): Boolean {
|
fun deleteCharacterSheet(characterSheetId: String): Boolean {
|
||||||
return store.delete(id = characterSheetId)
|
return characterStore.delete(id = characterSheetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data manipulation through WebSocket.
|
// Data manipulation through WebSocket.
|
||||||
|
|
@ -60,7 +75,7 @@ class CharacterSheetService(
|
||||||
val alterations = character.alterations.toMutableList().also {
|
val alterations = character.alterations.toMutableList().also {
|
||||||
it.add(alterationId)
|
it.add(alterationId)
|
||||||
}
|
}
|
||||||
store.save(
|
characterStore.save(
|
||||||
sheet = character.copy(
|
sheet = character.copy(
|
||||||
alterations = alterations,
|
alterations = alterations,
|
||||||
)
|
)
|
||||||
|
|
@ -70,7 +85,7 @@ class CharacterSheetService(
|
||||||
val alterations = character.alterations.toMutableList().also {
|
val alterations = character.alterations.toMutableList().also {
|
||||||
it.remove(alterationId)
|
it.remove(alterationId)
|
||||||
}
|
}
|
||||||
store.save(
|
characterStore.save(
|
||||||
sheet = character.copy(
|
sheet = character.copy(
|
||||||
alterations = alterations,
|
alterations = alterations,
|
||||||
)
|
)
|
||||||
|
|
@ -85,7 +100,7 @@ class CharacterSheetService(
|
||||||
) {
|
) {
|
||||||
sheets[characterSheetId]?.let { character ->
|
sheets[characterSheetId]?.let { character ->
|
||||||
val update = character.copy(damage = damage)
|
val update = character.copy(damage = damage)
|
||||||
store.save(sheet = update)
|
characterStore.save(sheet = update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +110,7 @@ class CharacterSheetService(
|
||||||
) {
|
) {
|
||||||
sheets[characterSheetId]?.let { character ->
|
sheets[characterSheetId]?.let { character ->
|
||||||
val update = character.copy(diminished = diminished)
|
val update = character.copy(diminished = diminished)
|
||||||
store.save(sheet = update)
|
characterStore.save(sheet = update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +120,7 @@ class CharacterSheetService(
|
||||||
) {
|
) {
|
||||||
sheets[characterSheetId]?.let { character ->
|
sheets[characterSheetId]?.let { character ->
|
||||||
val update = character.copy(fatigue = fatigue)
|
val update = character.copy(fatigue = fatigue)
|
||||||
store.save(sheet = update)
|
characterStore.save(sheet = update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +141,7 @@ class CharacterSheetService(
|
||||||
skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
|
skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
store.save(sheet = update)
|
characterStore.save(sheet = update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ class CharacterSheetStore(
|
||||||
)
|
)
|
||||||
fun save(sheet: CharacterSheet) {
|
fun save(sheet: CharacterSheet) {
|
||||||
// convert the character sheet into json format.
|
// convert the character sheet into json format.
|
||||||
val json = try {
|
val data = try {
|
||||||
factory.convertToJson(sheet = sheet).let(json::encodeToString)
|
factory.convertToJson(sheet = sheet).let(json::encodeToString)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
throw JsonConversionException(root = exception)
|
throw JsonConversionException(root = exception)
|
||||||
|
|
@ -85,7 +85,7 @@ class CharacterSheetStore(
|
||||||
try {
|
try {
|
||||||
val file = characterSheetFile(id = sheet.id)
|
val file = characterSheetFile(id = sheet.id)
|
||||||
file.writeText(
|
file.writeText(
|
||||||
text = json,
|
text = data,
|
||||||
charset = Charsets.UTF_8,
|
charset = Charsets.UTF_8,
|
||||||
)
|
)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.pixelized.server.lwa.model.tag
|
||||||
|
|
||||||
|
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||||
|
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.launch
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
private const val CHARACTER = "character"
|
||||||
|
private const val ALTERATION = "alteration"
|
||||||
|
|
||||||
|
class TagStore(
|
||||||
|
private val pathProvider: PathProvider,
|
||||||
|
private val json: Json,
|
||||||
|
) {
|
||||||
|
private val alterationTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||||
|
private val characterTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||||
|
|
||||||
|
init {
|
||||||
|
// make the file path.
|
||||||
|
File(pathProvider.tagsPath()).mkdirs()
|
||||||
|
// build a coroutine scope for async calls
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
// load the initial data
|
||||||
|
scope.launch {
|
||||||
|
update(
|
||||||
|
flow = alterationTagsFlow,
|
||||||
|
file = alterationFile(),
|
||||||
|
)
|
||||||
|
update(
|
||||||
|
flow = characterTagsFlow,
|
||||||
|
file = characterFile(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun alterationTags(): StateFlow<Map<String, TagJson>> = alterationTagsFlow
|
||||||
|
fun characterTags(): StateFlow<Map<String, TagJson>> = characterTagsFlow
|
||||||
|
|
||||||
|
private fun update(
|
||||||
|
flow: MutableStateFlow<Map<String, TagJson>>,
|
||||||
|
file: File,
|
||||||
|
) {
|
||||||
|
flow.value = try {
|
||||||
|
file.readTags().associateBy { it.id }
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println(exception) // TODO proper exception handling
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(FileReadException::class, JsonConversionException::class)
|
||||||
|
private fun File.readTags(): List<TagJson> {
|
||||||
|
// read the file (force the UTF8 format)
|
||||||
|
val data = try {
|
||||||
|
readText(charset = Charsets.UTF_8)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw FileReadException(root = exception)
|
||||||
|
}
|
||||||
|
// Guard, if the json is blank no alteration have been save, ignore this file.
|
||||||
|
if (data.isBlank()) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
json.decodeFromString<List<TagJson>>(data)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(
|
||||||
|
root = exception
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(JsonConversionException::class, FileWriteException::class)
|
||||||
|
private fun saveAlterationTags(tags: List<TagJson>) {
|
||||||
|
// convert the data to json format
|
||||||
|
val json = try {
|
||||||
|
this.json.encodeToString(tags)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw JsonConversionException(root = exception)
|
||||||
|
}
|
||||||
|
// write the file
|
||||||
|
try {
|
||||||
|
val file = alterationFile()
|
||||||
|
file.writeText(
|
||||||
|
text = json,
|
||||||
|
charset = Charsets.UTF_8,
|
||||||
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
throw FileWriteException(root = exception)
|
||||||
|
}
|
||||||
|
// Update the dataflow.
|
||||||
|
alterationTagsFlow.value = tags.associateBy { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package com.pixelized.server.lwa.server
|
package com.pixelized.server.lwa.server
|
||||||
|
|
||||||
|
|
||||||
|
import com.pixelized.server.lwa.server.rest.alteration.deleteAlteration
|
||||||
import com.pixelized.server.lwa.server.rest.alteration.getAlteration
|
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.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.removeCampaignNpc
|
||||||
import com.pixelized.server.lwa.server.rest.campaign.getCampaign
|
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.putCampaignCharacter
|
||||||
|
|
@ -11,6 +14,7 @@ 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.removeCampaignCharacter
|
||||||
import com.pixelized.server.lwa.server.rest.character.deleteCharacter
|
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.getCharacter
|
||||||
|
import com.pixelized.server.lwa.server.rest.character.getCharacterTags
|
||||||
import com.pixelized.server.lwa.server.rest.character.getCharacters
|
import com.pixelized.server.lwa.server.rest.character.getCharacters
|
||||||
import com.pixelized.server.lwa.server.rest.character.putCharacter
|
import com.pixelized.server.lwa.server.rest.character.putCharacter
|
||||||
import com.pixelized.server.lwa.server.rest.character.putCharacterAlteration
|
import com.pixelized.server.lwa.server.rest.character.putCharacterAlteration
|
||||||
|
|
@ -126,6 +130,18 @@ class LocalServer {
|
||||||
path = "/detail",
|
path = "/detail",
|
||||||
body = engine.getAlteration(),
|
body = engine.getAlteration(),
|
||||||
)
|
)
|
||||||
|
get(
|
||||||
|
path = "/tags",
|
||||||
|
body = engine.getAlterationTags(),
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
path = "/update",
|
||||||
|
body = engine.putAlteration()
|
||||||
|
)
|
||||||
|
delete(
|
||||||
|
path = "/delete",
|
||||||
|
body = engine.deleteAlteration()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
route(
|
route(
|
||||||
path = "/character",
|
path = "/character",
|
||||||
|
|
@ -134,6 +150,10 @@ class LocalServer {
|
||||||
path = "/all",
|
path = "/all",
|
||||||
body = engine.getCharacters(),
|
body = engine.getCharacters(),
|
||||||
)
|
)
|
||||||
|
get(
|
||||||
|
path = "/tags",
|
||||||
|
body = engine.getCharacterTags(),
|
||||||
|
)
|
||||||
get(
|
get(
|
||||||
path = "/detail",
|
path = "/detail",
|
||||||
body = engine.getCharacter(),
|
body = engine.getCharacter(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
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.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
|
|
||||||
|
fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||||
|
return {
|
||||||
|
try {
|
||||||
|
val alterationId = call.parameters.alterationId
|
||||||
|
|
||||||
|
val deleted = alterationService.delete(
|
||||||
|
alterationId = alterationId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (deleted.not()) error("Unexpected error occurred")
|
||||||
|
|
||||||
|
call.respondText(
|
||||||
|
text = "${HttpStatusCode.OK}",
|
||||||
|
status = HttpStatusCode.OK,
|
||||||
|
)
|
||||||
|
webSocket.emit(
|
||||||
|
value = ApiSynchronisation.AlterationDelete(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
alterationId = alterationId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
call.respondText(
|
||||||
|
text = "${HttpStatusCode.UnprocessableEntity}",
|
||||||
|
status = HttpStatusCode.UnprocessableEntity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.pixelized.server.lwa.server.rest.alteration
|
||||||
|
|
||||||
|
import com.pixelized.server.lwa.server.Engine
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
|
||||||
|
fun Engine.getAlterationTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||||
|
return {
|
||||||
|
call.respond(
|
||||||
|
message = alterationService.tags(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -
|
||||||
characterSheetId = characterSheetId
|
characterSheetId = characterSheetId
|
||||||
)
|
)
|
||||||
// Remove the character fom the campaign if needed.
|
// Remove the character fom the campaign if needed.
|
||||||
|
// TODO probably useless because all data will not be cleaned up (all campaign / screnes)
|
||||||
campaignService.removeInstance(
|
campaignService.removeInstance(
|
||||||
characterSheetId = characterSheetId,
|
characterSheetId = characterSheetId,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.pixelized.server.lwa.server.rest.character
|
||||||
|
|
||||||
|
import com.pixelized.server.lwa.server.Engine
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
|
||||||
|
fun Engine.getCharacterTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||||
|
return {
|
||||||
|
call.respond(
|
||||||
|
message = characterService.tags(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonV1Factory
|
||||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonV2Factory
|
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonV2Factory
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonV1Factory
|
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonV1Factory
|
||||||
|
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||||
import com.pixelized.shared.lwa.parser.dice.DiceParser
|
import com.pixelized.shared.lwa.parser.dice.DiceParser
|
||||||
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
import com.pixelized.shared.lwa.parser.expression.ExpressionParser
|
||||||
import com.pixelized.shared.lwa.parser.word.WordParser
|
import com.pixelized.shared.lwa.parser.word.WordParser
|
||||||
|
|
@ -45,6 +46,7 @@ val factoryDependencies
|
||||||
factoryOf(::CampaignJsonV2Factory)
|
factoryOf(::CampaignJsonV2Factory)
|
||||||
factoryOf(::AlteredCharacterSheetFactory)
|
factoryOf(::AlteredCharacterSheetFactory)
|
||||||
factoryOf(::AlterationJsonFactory)
|
factoryOf(::AlterationJsonFactory)
|
||||||
|
factoryOf(::TagJsonFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parserDependencies
|
val parserDependencies
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.pixelized.shared.lwa.model.tag
|
||||||
|
|
||||||
|
data class Tag(
|
||||||
|
val id: String,
|
||||||
|
val label: String,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.pixelized.shared.lwa.model.tag
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed interface TagJson {
|
||||||
|
val id: String
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.pixelized.shared.lwa.model.tag
|
||||||
|
|
||||||
|
class TagJsonFactory {
|
||||||
|
|
||||||
|
fun convertFromJson(
|
||||||
|
json: TagJson,
|
||||||
|
): Tag {
|
||||||
|
return when (json) {
|
||||||
|
is TagJsonV1 -> Tag(
|
||||||
|
id = json.id,
|
||||||
|
label = json.label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertToJson(
|
||||||
|
tag: Tag,
|
||||||
|
): TagJson {
|
||||||
|
return TagJsonV1(
|
||||||
|
id = tag.id,
|
||||||
|
label = tag.label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.pixelized.shared.lwa.model.tag
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TagJsonV1(
|
||||||
|
override val id: String,
|
||||||
|
val label: String,
|
||||||
|
) : TagJson
|
||||||
|
|
@ -16,4 +16,16 @@ sealed interface ApiSynchronisation : SocketMessage {
|
||||||
override val timestamp: Long,
|
override val timestamp: Long,
|
||||||
override val characterSheetId: String,
|
override val characterSheetId: String,
|
||||||
) : ApiSynchronisation, CharacterSheetIdMessage
|
) : ApiSynchronisation, CharacterSheetIdMessage
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AlterationUpdate(
|
||||||
|
override val timestamp: Long,
|
||||||
|
val alterationId: String,
|
||||||
|
) : ApiSynchronisation
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AlterationDelete(
|
||||||
|
override val timestamp: Long,
|
||||||
|
val alterationId: String,
|
||||||
|
) : ApiSynchronisation
|
||||||
}
|
}
|
||||||
|
|
@ -53,4 +53,14 @@ class PathProvider(
|
||||||
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}alterations/"
|
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}alterations/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tagsPath(
|
||||||
|
os: OperatingSystem = this.operatingSystem,
|
||||||
|
app: String = this.appName,
|
||||||
|
): String {
|
||||||
|
return when (os) {
|
||||||
|
OperatingSystem.Windows -> "${storePath(os = os, app = app)}tags\\"
|
||||||
|
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}tags/"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue