Merge branch 'feature/alterationEdit' into 'main'
tmp See merge request pixelized/LwaCharacterSheet!5
This commit is contained in:
commit
3bb25f4e63
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__create_character_sheet">Créer un personnage</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__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>
|
||||
|
|
@ -41,7 +41,9 @@ class DataSyncViewModel(
|
|||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||
.onEach {
|
||||
alterationRepository.updateAlterations()
|
||||
alterationRepository.updateTags()
|
||||
characterRepository.updateCharacterPreviews()
|
||||
characterRepository.updateTags()
|
||||
campaignRepository.updateCampaign()
|
||||
}
|
||||
.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.gamemaster.GameMasterViewModel
|
||||
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.GMAlterationViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
|
||||
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.rollhistory.RollHistoryViewModel
|
||||
|
|
@ -123,8 +126,10 @@ val factoryDependencies
|
|||
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
||||
factoryOf(::TextMessageFactory)
|
||||
factoryOf(::LevelUpFactory)
|
||||
factoryOf(::GMTagFactory)
|
||||
factoryOf(::GMCharacterFactory)
|
||||
factoryOf(::GMAlterationFactory)
|
||||
factoryOf(::GMAlterationEditFactory)
|
||||
}
|
||||
|
||||
val viewModelDependencies
|
||||
|
|
@ -149,6 +154,7 @@ val viewModelDependencies
|
|||
viewModelOf(::GameMasterViewModel)
|
||||
viewModelOf(::GMActionViewModel)
|
||||
viewModelOf(::GMAlterationViewModel)
|
||||
viewModelOf(::GMAlterationEditViewModel)
|
||||
}
|
||||
|
||||
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.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
|
||||
interface LwaClient {
|
||||
|
|
@ -15,6 +16,16 @@ interface LwaClient {
|
|||
alterationId: String,
|
||||
): AlterationJson?
|
||||
|
||||
suspend fun updateAlteration(
|
||||
alterationJson: AlterationJson
|
||||
)
|
||||
|
||||
suspend fun deleteAlteration(
|
||||
alterationId: String,
|
||||
)
|
||||
|
||||
suspend fun alterationTags(): List<TagJson>
|
||||
|
||||
// Campaign
|
||||
|
||||
suspend fun campaign(): CampaignJson
|
||||
|
|
@ -39,6 +50,8 @@ interface LwaClient {
|
|||
|
||||
suspend fun characters(): List<CharacterPreviewJson>
|
||||
|
||||
suspend fun characterTags(): List<TagJson>
|
||||
|
||||
suspend fun character(
|
||||
characterSheetId: String,
|
||||
): 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.campaign.CampaignJson
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
|
|
@ -30,6 +31,23 @@ class LwaClientImpl(
|
|||
.get("$root/alteration/detail?alterationId=$alterationId")
|
||||
.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
|
||||
.get("$root/campaign")
|
||||
.body()
|
||||
|
|
@ -62,6 +80,10 @@ class LwaClientImpl(
|
|||
.get("$root/character/all")
|
||||
.body()
|
||||
|
||||
override suspend fun characterTags(): List<TagJson> = client
|
||||
.get("$root/character/tags")
|
||||
.body()
|
||||
|
||||
override suspend fun character(
|
||||
characterSheetId: String,
|
||||
): CharacterSheetJson = client
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class AlterationRepository(
|
|||
|
||||
val alterationFlow get() = alterationStore.alterationsFlow
|
||||
|
||||
val tagsFlow get() = alterationStore.tagsFlow
|
||||
|
||||
/**
|
||||
* This flow transform the campaign instance (player + npc) into a
|
||||
* Map<CharacterSheetId, List<AlterationId>>.
|
||||
|
|
@ -67,6 +69,16 @@ class AlterationRepository(
|
|||
alterationStore.updateAlterations()
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
alterationStore.updateTags()
|
||||
}
|
||||
|
||||
fun alteration(
|
||||
alterationId: String?,
|
||||
): Alteration? {
|
||||
return alterationFlow.value[alterationId]
|
||||
}
|
||||
|
||||
fun fieldAlterations(
|
||||
characterSheetId: String,
|
||||
): Map<String, List<FieldAlteration>> {
|
||||
|
|
@ -79,6 +91,18 @@ class AlterationRepository(
|
|||
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(
|
||||
alterations: Map<String, Alteration>,
|
||||
actives: List<String>,
|
||||
|
|
|
|||
|
|
@ -1,32 +1,92 @@
|
|||
package com.pixelized.desktop.lwa.repository.alteration
|
||||
|
||||
import com.pixelized.desktop.lwa.network.LwaClient
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AlterationStore(
|
||||
private val networkRepository: NetworkRepository,
|
||||
private val alterationFactory: AlterationJsonFactory,
|
||||
private val tagFactory: TagJsonFactory,
|
||||
private val client: LwaClient,
|
||||
) {
|
||||
private val _alterationsFlow = MutableStateFlow<Map<String, Alteration>>(emptyMap())
|
||||
val alterationsFlow: StateFlow<Map<String, Alteration>> = _alterationsFlow
|
||||
|
||||
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
scope.launch {
|
||||
updateAlterations()
|
||||
updateTags()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
networkRepository.data.collect(::handleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
_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) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyMap()
|
||||
|
|
@ -34,17 +94,62 @@ class AlterationStore(
|
|||
}
|
||||
|
||||
@Throws
|
||||
private suspend fun loadAlteration(): Map<String, Alteration> {
|
||||
private suspend fun getAlteration(): Map<String, Alteration> {
|
||||
val request = client.alterations()
|
||||
val data = request.map { alterationFactory.convertFromJson(json = it) }
|
||||
return data.associateBy { it.id }
|
||||
}
|
||||
|
||||
fun alterations(): Collection<Alteration> {
|
||||
return alterationsFlow.value.values
|
||||
@Throws
|
||||
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? {
|
||||
return alterationsFlow.value[alterationId]
|
||||
@Throws
|
||||
private suspend fun getAlterationTag(): Map<String, Tag> {
|
||||
val request = client.alterationTags()
|
||||
val data = request.map { tagFactory.convertFromJson(json = it) }
|
||||
return data.associateBy { it.id }
|
||||
}
|
||||
|
||||
@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 characterDetailFlow get() = store.detailFlow
|
||||
val tagsFlow get() = store.tagsFlow
|
||||
|
||||
suspend fun updateCharacterPreviews() {
|
||||
store.charactersPreview()
|
||||
store.updateCharactersPreview()
|
||||
}
|
||||
|
||||
suspend fun updateTags() {
|
||||
store.updateTags()
|
||||
}
|
||||
|
||||
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.CharacterSheetPreview
|
||||
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import com.pixelized.shared.lwa.model.tag.TagJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
|
|
@ -23,7 +25,7 @@ class CharacterSheetStore(
|
|||
private val client: LwaClient,
|
||||
private val network: NetworkRepository,
|
||||
private val factory: CharacterSheetJsonFactory,
|
||||
private val useCase: CharacterSheetUseCase,
|
||||
private val tagFactory: TagJsonFactory,
|
||||
) {
|
||||
private val _previewFlow = MutableStateFlow<List<CharacterSheetPreview>>(value = emptyList())
|
||||
val previewFlow: StateFlow<List<CharacterSheetPreview>> get() = _previewFlow
|
||||
|
|
@ -31,11 +33,15 @@ class CharacterSheetStore(
|
|||
private val _detailFlow = MutableStateFlow<Map<String, CharacterSheet>>(value = emptyMap())
|
||||
val detailFlow: StateFlow<Map<String, CharacterSheet>> get() = _detailFlow
|
||||
|
||||
private val _tagsFlow = MutableStateFlow<Map<String, Tag>>(emptyMap())
|
||||
val tagsFlow: StateFlow<Map<String, Tag>> = _tagsFlow
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// initial data loading.
|
||||
scope.launch {
|
||||
charactersPreview()
|
||||
updateCharactersPreview()
|
||||
updateTags()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
|
|
@ -45,7 +51,7 @@ class CharacterSheetStore(
|
|||
|
||||
// region Rest
|
||||
|
||||
suspend fun charactersPreview(): List<CharacterSheetPreview> {
|
||||
suspend fun updateCharactersPreview(): List<CharacterSheetPreview> {
|
||||
val request = try {
|
||||
client.characters()
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -58,6 +64,21 @@ class CharacterSheetStore(
|
|||
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(
|
||||
characterSheetId: String,
|
||||
forceUpdate: Boolean = false,
|
||||
|
|
@ -110,7 +131,7 @@ class CharacterSheetStore(
|
|||
forceUpdate = true,
|
||||
)
|
||||
if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) {
|
||||
charactersPreview()
|
||||
updateCharactersPreview()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,6 +147,9 @@ class CharacterSheetStore(
|
|||
sheets.toMutableMap().also { it.remove(message.characterSheetId) }
|
||||
}
|
||||
}
|
||||
|
||||
is ApiSynchronisation.AlterationUpdate -> Unit
|
||||
is ApiSynchronisation.AlterationDelete -> Unit
|
||||
}
|
||||
|
||||
is CharacterSheetEvent -> when (message) {
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
data class LwaTextFieldUio(
|
||||
val enable: Boolean = true,
|
||||
val isError: StateFlow<Boolean>,
|
||||
val labelFlow: StateFlow<String?>,
|
||||
val labelFlow: StateFlow<String?>?,
|
||||
val valueFlow: StateFlow<String>,
|
||||
val placeHolderFlow: StateFlow<String?>,
|
||||
val placeHolderFlow: StateFlow<String?>?,
|
||||
val onValueChange: (String) -> Unit,
|
||||
)
|
||||
|
||||
|
|
@ -43,9 +43,9 @@ fun LwaTextField(
|
|||
Modifier
|
||||
}
|
||||
|
||||
val label = field.labelFlow.collectAsState()
|
||||
val label = field.labelFlow?.collectAsState()
|
||||
val placeHolder = field.placeHolderFlow?.collectAsState()
|
||||
val value = field.valueFlow.collectAsState()
|
||||
val placeHolder = field.placeHolderFlow.collectAsState()
|
||||
val isError = field.isError.collectAsState()
|
||||
|
||||
TextField(
|
||||
|
|
@ -56,7 +56,7 @@ fun LwaTextField(
|
|||
},
|
||||
enabled = field.enable,
|
||||
singleLine = singleLine,
|
||||
placeholder = placeHolder.value?.let {
|
||||
placeholder = placeHolder?.value?.let {
|
||||
{
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -66,7 +66,7 @@ fun LwaTextField(
|
|||
}
|
||||
},
|
||||
isError = isError.value,
|
||||
label = label.value?.let {
|
||||
label = label?.value?.let {
|
||||
{
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
|
|
@ -15,8 +16,7 @@ object CharacterSheetEditDestination {
|
|||
|
||||
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
|
||||
|
||||
fun navigationRoute(characterSheetId: String?) = ROUTE +
|
||||
"?$CHARACTER_ID=$characterSheetId"
|
||||
fun navigationRoute(characterSheetId: String?) = "$ROUTE?$CHARACTER_ID=$characterSheetId"
|
||||
|
||||
fun arguments() = listOf(
|
||||
navArgument(CHARACTER_ID) {
|
||||
|
|
@ -25,6 +25,7 @@ object CharacterSheetEditDestination {
|
|||
},
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class Argument(
|
||||
val id: String?,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
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 {
|
||||
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.NavHostController
|
||||
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 {
|
||||
private const val ROUTE = "GameMasterCharacter"
|
||||
|
|
|
|||
|
|
@ -20,27 +20,18 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.desktop.lwa.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.destination.gamemaster.GMActionDestination
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage
|
||||
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.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.navigateToGameMasterCharacterPage
|
||||
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.campaign.player.detail.CharacterDetailViewModel
|
||||
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.screen.gamemaster.common.GMTab
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMTabUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -175,6 +163,7 @@ private fun GameMasterContent(
|
|||
composableGameMasterActionPage()
|
||||
composableGameMasterCharacterPage()
|
||||
composableGameMasterAlterationPage()
|
||||
composableGameMasterAlterationEditPage()
|
||||
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.clickable
|
||||
|
|
@ -6,10 +6,8 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Modifier
|
||||
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 lwacharactersheet.composeapp.generated.resources.Res
|
||||
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.clickable
|
||||
|
|
@ -26,6 +26,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
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 lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__delete
|
||||
|
|
@ -37,7 +39,7 @@ import org.jetbrains.compose.resources.stringResource
|
|||
data class GMAlterationUio(
|
||||
val alterationId: String,
|
||||
val label: String,
|
||||
val tags: List<GMTagItemUio>,
|
||||
val tags: List<GMTagUio>,
|
||||
)
|
||||
|
||||
@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.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.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.tag.Tag
|
||||
import java.text.Collator
|
||||
|
||||
class GMAlterationFactory {
|
||||
class GMAlterationFactory(
|
||||
private val tagFactory: GMTagFactory,
|
||||
) {
|
||||
|
||||
fun filterAlteration(
|
||||
alterations: Collection<Alteration>,
|
||||
|
|
@ -27,6 +29,7 @@ class GMAlterationFactory {
|
|||
|
||||
fun convertToGMAlterationUio(
|
||||
alterations: List<Alteration>,
|
||||
tags: Map<String, Tag>,
|
||||
selectedTagId: String?,
|
||||
): List<GMAlterationUio> {
|
||||
return alterations
|
||||
|
|
@ -34,28 +37,16 @@ class GMAlterationFactory {
|
|||
GMAlterationUio(
|
||||
alterationId = alteration.id,
|
||||
label = alteration.metadata.name,
|
||||
tags = alteration.tags.map { tag ->
|
||||
GMTagItemUio(
|
||||
id = tag,
|
||||
label = tag,
|
||||
highlight = tag == selectedTagId,
|
||||
)
|
||||
tags = alteration.tags.mapNotNull {
|
||||
tags[it]?.let { tag ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
tag = tag,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.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.Box
|
||||
|
|
@ -21,17 +21,20 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.screen.gamemaster.items.GMAlteration
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAlterationUio
|
||||
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.navigation.screen.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationEditPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
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.game_master__alteration__create
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__create_character_sheet
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
|
@ -40,8 +43,11 @@ import org.koin.compose.viewmodel.koinViewModel
|
|||
fun GMAlterationPage(
|
||||
viewModel: GMAlterationViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val alterations = viewModel.alterations.collectAsState()
|
||||
val tags = viewModel.alterationTags.collectAsState()
|
||||
val tags = viewModel.tags.collectAsState()
|
||||
|
||||
Box {
|
||||
GMAlterationContent(
|
||||
|
|
@ -50,9 +56,17 @@ fun GMAlterationPage(
|
|||
tags = tags,
|
||||
alterations = alterations,
|
||||
onTag = viewModel::onTag,
|
||||
onAlterationEdit = { },
|
||||
onAlterationDelete = { },
|
||||
onAlterationCreate = { },
|
||||
onAlterationEdit = {
|
||||
screen.navigateToGameMasterAlterationEditPage(alterationId = it)
|
||||
},
|
||||
onAlterationDelete = {
|
||||
scope.launch {
|
||||
viewModel.deleteAlteration(alterationId = it)
|
||||
}
|
||||
},
|
||||
onAlterationCreate = {
|
||||
screen.navigateToGameMasterAlterationEditPage(alterationId = null)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +77,7 @@ private fun GMAlterationContent(
|
|||
padding: Dp = 8.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
tags: State<List<GMTagUio>>,
|
||||
alterations: State<List<GMAlterationUio>>,
|
||||
onTag: (String) -> Unit,
|
||||
onAlterationEdit: (String) -> Unit,
|
||||
|
|
@ -131,7 +145,7 @@ private fun GMAlterationContent(
|
|||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__create_character_sheet),
|
||||
text = stringResource(Res.string.game_master__alteration__create),
|
||||
)
|
||||
Icon(
|
||||
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.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
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 kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -20,33 +20,29 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character__
|
|||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class GMAlterationViewModel(
|
||||
alterationRepository: AlterationRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
alterationFactory: GMAlterationFactory,
|
||||
tagFactory: GMTagFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _filter = MutableStateFlow("")
|
||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||
private val filterValue = MutableStateFlow("")
|
||||
|
||||
val filter = LwaTextFieldUio(
|
||||
enable = true,
|
||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||
valueFlow = _filter,
|
||||
valueFlow = filterValue,
|
||||
isError = MutableStateFlow(false),
|
||||
placeHolderFlow = MutableStateFlow(null),
|
||||
onValueChange = { _filter.value = it },
|
||||
onValueChange = { filterValue.value = it },
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val alterationTagIds = alterationRepository.alterationFlow
|
||||
.mapLatest { alterations -> alterations.values.flatMap { it.tags }.toSet().toList() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||
|
||||
val alterationTags = combine(
|
||||
alterationTagIds,
|
||||
val tags: StateFlow<List<GMTagUio>> = combine(
|
||||
alterationRepository.tagsFlow,
|
||||
selectedTagId,
|
||||
) { alterationTagIds, selectedTagId ->
|
||||
alterationFactory.convertToGMTagItemUio(
|
||||
alterationTagIds = alterationTagIds,
|
||||
) { tags, selectedTagId ->
|
||||
tagFactory.convertToGMTagItemUio(
|
||||
tags = tags.values,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}.stateIn(
|
||||
|
|
@ -55,22 +51,20 @@ class GMAlterationViewModel(
|
|||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val alterations = combine(
|
||||
val alterations: StateFlow<List<GMAlterationUio>> = combine(
|
||||
alterationRepository.alterationFlow,
|
||||
alterationRepository.tagsFlow,
|
||||
filter.valueFlow.map { it.unAccent() },
|
||||
selectedTagId,
|
||||
transform = { alterations, unAccentFilter, selectedTagId ->
|
||||
alterationFactory.filterAlteration(
|
||||
) { alterations, tags, unAccentFilter, selectedTagId ->
|
||||
alterationFactory.convertToGMAlterationUio(
|
||||
alterations = alterationFactory.filterAlteration(
|
||||
alterations = alterations.values,
|
||||
unAccentFilter = unAccentFilter,
|
||||
selectedTagId = selectedTagId
|
||||
)
|
||||
}
|
||||
).mapLatest { alterations ->
|
||||
alterationFactory.convertToGMAlterationUio(
|
||||
alterations = alterations,
|
||||
selectedTagId = selectedTagId.value
|
||||
selectedTagId = selectedTagId,
|
||||
),
|
||||
tags = tags,
|
||||
selectedTagId = selectedTagId,
|
||||
)
|
||||
}.stateIn(
|
||||
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.PointerMatcher
|
||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.onClick
|
||||
import androidx.compose.material.DropdownMenu
|
||||
|
|
@ -28,9 +27,10 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.pointer.PointerButton
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
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 lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__add_to_group
|
||||
|
|
@ -54,7 +54,7 @@ data class GMCharacterItemUio(
|
|||
val characterSheetId: String,
|
||||
val name: String,
|
||||
val level: Int,
|
||||
val tags: List<GMTagItemUio>,
|
||||
val tags: List<GMTagUio>,
|
||||
val actions: List<Action>,
|
||||
) {
|
||||
@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.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.rememberTransitionAnimation
|
||||
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.items.GMCharacterItemUio
|
||||
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.screen.gamemaster.common.GMFilterHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -163,7 +161,7 @@ fun GMCharacterContent(
|
|||
padding: Dp = 8.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
tags: State<List<GMTagUio>>,
|
||||
characters: State<List<GMCharacterItemUio>>,
|
||||
onTag: (String) -> 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.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.network.NetworkRepository
|
||||
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.items.GMTagItemUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action
|
||||
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 kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
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
|
||||
|
||||
class GMCharacterViewModel(
|
||||
|
|
@ -27,41 +27,53 @@ class GMCharacterViewModel(
|
|||
private val campaignRepository: CampaignRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
private val factory: GMCharacterFactory,
|
||||
private val tagFactory: GMTagFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _filter = MutableStateFlow("")
|
||||
private val selectedTagId = MutableStateFlow<String?>(null)
|
||||
private val filterValue = MutableStateFlow("")
|
||||
|
||||
val filter = LwaTextFieldUio(
|
||||
enable = true,
|
||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||
valueFlow = _filter,
|
||||
valueFlow = filterValue,
|
||||
isError = MutableStateFlow(false),
|
||||
placeHolderFlow = MutableStateFlow(null),
|
||||
onValueChange = { _filter.value = it },
|
||||
onValueChange = { filterValue.value = it },
|
||||
)
|
||||
|
||||
private val _tags = MutableStateFlow(
|
||||
mapOf(
|
||||
GMCharacterFactory.PLAYER_ID to false,
|
||||
GMCharacterFactory.NPC_ID to false
|
||||
val tags = combine(
|
||||
characterSheetRepository.tagsFlow,
|
||||
selectedTagId,
|
||||
) { 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(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
val characters = combine(
|
||||
campaignRepository.campaignFlow,
|
||||
characterSheetRepository.characterSheetPreviewFlow,
|
||||
filter.valueFlow,
|
||||
_tags,
|
||||
factory::convertToGMCharacterPreviewUio,
|
||||
).stateIn(
|
||||
filter.valueFlow.map { it.unAccent() },
|
||||
tags,
|
||||
selectedTagId,
|
||||
) { 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,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
|
|
@ -106,8 +118,11 @@ class GMCharacterViewModel(
|
|||
fun onTag(
|
||||
id: String,
|
||||
) {
|
||||
_tags.value = _tags.value.toMutableMap().also {
|
||||
it[id] = it.getOrPut(id) { true }.not()
|
||||
selectedTagId.update {
|
||||
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.fadeIn
|
||||
|
|
@ -28,6 +28,8 @@ 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.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 kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -41,7 +43,7 @@ fun GMFilterHeader(
|
|||
padding: Dp = 16.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
tags: State<List<GMTagUio>>,
|
||||
onTag: (String) -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -54,7 +56,6 @@ fun GMFilterHeader(
|
|||
field = filter,
|
||||
trailingIcon = {
|
||||
val value = filter.valueFlow.collectAsState()
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
|
|
@ -72,28 +73,37 @@ fun GMFilterHeader(
|
|||
}
|
||||
}
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
lazyListState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = lazyListState,
|
||||
contentPadding = remember(padding, spacing) {
|
||||
PaddingValues(horizontal = padding, vertical = spacing)
|
||||
},
|
||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
AnimatedVisibility(
|
||||
visible = tags.value.isNotEmpty(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
items(
|
||||
items = tags.value,
|
||||
) { tag ->
|
||||
GMTag(
|
||||
tag = tag,
|
||||
onTag = { onTag(tag.id) },
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
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.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.foundation.clickable
|
||||
|
|
@ -19,7 +19,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
|||
|
||||
|
||||
@Stable
|
||||
data class GMTagItemUio(
|
||||
data class GMTagUio(
|
||||
val id: String,
|
||||
val label: String,
|
||||
val highlight: Boolean,
|
||||
|
|
@ -36,7 +36,7 @@ fun GMTag(
|
|||
padding: PaddingValues = GmTagDefault.padding,
|
||||
shape: Shape = CircleShape,
|
||||
elevation: Dp = 2.dp,
|
||||
tag: GMTagItemUio,
|
||||
tag: GMTagUio,
|
||||
onTag: (() -> Unit)? = null,
|
||||
) {
|
||||
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 org.junit.Test
|
||||
|
||||
class SkillNormalizerUseCaseText {
|
||||
class SkillUioNormalizerUseCaseText {
|
||||
|
||||
@Test
|
||||
fun testNormalization() {
|
||||
|
|
@ -4,7 +4,7 @@ import com.pixelized.shared.lwa.usecase.SkillStepUseCase
|
|||
import com.pixelized.shared.lwa.usecase.SkillStepUseCase.SkillStep
|
||||
import org.junit.Test
|
||||
|
||||
class SkillStepUseCaseTest {
|
||||
class SkillUioStepUseCaseTest {
|
||||
|
||||
@Test
|
||||
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.character.CharacterSheetService
|
||||
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.shared.lwa.utils.PathProvider
|
||||
import org.koin.core.module.dsl.createdAtStart
|
||||
|
|
@ -35,6 +36,7 @@ val storeDependencies
|
|||
singleOf(::CharacterSheetStore)
|
||||
singleOf(::CampaignStore)
|
||||
singleOf(::AlterationStore)
|
||||
singleOf(::TagStore)
|
||||
}
|
||||
|
||||
val serviceDependencies
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
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.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -9,25 +12,45 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class AlterationService(
|
||||
store: AlterationStore,
|
||||
private val alterationStore: AlterationStore,
|
||||
tagStore: TagStore,
|
||||
factory: AlterationJsonFactory,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
private val alterationsFlow = store.alterationsFlow()
|
||||
|
||||
private val alterationHashFlow = alterationsFlow
|
||||
.map { data -> data.associateBy { it.id } }
|
||||
private val alterationHashFlow = alterationStore.alterationsFlow()
|
||||
.map { alterations -> alterations.associate { it.id to factory.convertToJson(it) } }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyMap()
|
||||
)
|
||||
|
||||
private val alterationTags = tagStore.alterationTags()
|
||||
.map { it.values.toList() }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun alterations(): List<AlterationJson> {
|
||||
return alterationsFlow.value
|
||||
return alterationHashFlow.value.values.toList()
|
||||
}
|
||||
|
||||
fun tags(): List<TagJson> {
|
||||
return alterationTags.value
|
||||
}
|
||||
|
||||
fun alteration(alterationId: String): AlterationJson? {
|
||||
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
|
||||
|
||||
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJson
|
||||
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
class AlterationStore(
|
||||
private val pathProvider: PathProvider,
|
||||
private val factory: AlterationJsonFactory,
|
||||
private val json: Json,
|
||||
) {
|
||||
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
|
||||
private val alterationsFlow = MutableStateFlow<List<AlterationJson>>(emptyList())
|
||||
|
||||
private val alterationFlow = MutableStateFlow<List<Alteration>>(emptyList())
|
||||
|
||||
init {
|
||||
// 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() {
|
||||
alterationsFlow.value = try {
|
||||
alterationFlow.value = try {
|
||||
loadAlterations()
|
||||
} catch (exception: Exception) {
|
||||
println(exception) // TODO proper exception handling
|
||||
|
|
@ -42,7 +47,7 @@ class AlterationStore(
|
|||
FileReadException::class,
|
||||
JsonConversionException::class,
|
||||
)
|
||||
private fun loadAlterations(): List<AlterationJson> {
|
||||
private fun loadAlterations(): List<Alteration> {
|
||||
return directory
|
||||
.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
|
|
@ -56,7 +61,8 @@ class AlterationStore(
|
|||
return@mapNotNull null
|
||||
}
|
||||
try {
|
||||
this.json.decodeFromString<AlterationJson>(json)
|
||||
val data = this.json.decodeFromString<AlterationJson>(json)
|
||||
factory.convertFromJson(data)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
|
|
@ -64,6 +70,73 @@ class AlterationStore(
|
|||
?: 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)
|
||||
class JsonConversionException(root: Exception) : AlterationStoreException(root)
|
||||
class FileWriteException(root: Exception) : AlterationStoreException(root)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
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.CharacterSheetJson
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -12,12 +14,13 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class CharacterSheetService(
|
||||
private val store: CharacterSheetStore,
|
||||
private val characterStore: CharacterSheetStore,
|
||||
private val tagStore: TagStore,
|
||||
private val factory: CharacterSheetJsonFactory,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
private val sheets get() = sheetsFlow.value
|
||||
private val sheetsFlow = store.characterSheetsFlow()
|
||||
private val sheetsFlow = characterStore.characterSheetsFlow()
|
||||
.map { entry -> entry.associateBy { character -> character.id } }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
|
|
@ -25,10 +28,22 @@ class CharacterSheetService(
|
|||
initialValue = emptyMap()
|
||||
)
|
||||
|
||||
private val alterationTags = tagStore.characterTags()
|
||||
.map { it.values.toList() }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun character(characterSheetId: String): CharacterSheet? {
|
||||
return sheets[characterSheetId]
|
||||
}
|
||||
|
||||
fun tags() : Collection<TagJson> {
|
||||
return alterationTags.value
|
||||
}
|
||||
|
||||
fun charactersJson(): List<CharacterPreviewJson> {
|
||||
return sheets.map { factory.convertToPreviewJson(sheet = it.value) }
|
||||
}
|
||||
|
|
@ -38,13 +53,13 @@ class CharacterSheetService(
|
|||
}
|
||||
|
||||
suspend fun updateCharacterSheet(character: CharacterSheetJson) {
|
||||
return store.save(
|
||||
return characterStore.save(
|
||||
sheet = factory.convertFromJson(character)
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteCharacterSheet(characterSheetId: String): Boolean {
|
||||
return store.delete(id = characterSheetId)
|
||||
return characterStore.delete(id = characterSheetId)
|
||||
}
|
||||
|
||||
// Data manipulation through WebSocket.
|
||||
|
|
@ -60,7 +75,7 @@ class CharacterSheetService(
|
|||
val alterations = character.alterations.toMutableList().also {
|
||||
it.add(alterationId)
|
||||
}
|
||||
store.save(
|
||||
characterStore.save(
|
||||
sheet = character.copy(
|
||||
alterations = alterations,
|
||||
)
|
||||
|
|
@ -70,7 +85,7 @@ class CharacterSheetService(
|
|||
val alterations = character.alterations.toMutableList().also {
|
||||
it.remove(alterationId)
|
||||
}
|
||||
store.save(
|
||||
characterStore.save(
|
||||
sheet = character.copy(
|
||||
alterations = alterations,
|
||||
)
|
||||
|
|
@ -85,7 +100,7 @@ class CharacterSheetService(
|
|||
) {
|
||||
sheets[characterSheetId]?.let { character ->
|
||||
val update = character.copy(damage = damage)
|
||||
store.save(sheet = update)
|
||||
characterStore.save(sheet = update)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +110,7 @@ class CharacterSheetService(
|
|||
) {
|
||||
sheets[characterSheetId]?.let { character ->
|
||||
val update = character.copy(diminished = diminished)
|
||||
store.save(sheet = update)
|
||||
characterStore.save(sheet = update)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +120,7 @@ class CharacterSheetService(
|
|||
) {
|
||||
sheets[characterSheetId]?.let { character ->
|
||||
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
|
||||
},
|
||||
)
|
||||
store.save(sheet = update)
|
||||
characterStore.save(sheet = update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ class CharacterSheetStore(
|
|||
)
|
||||
fun save(sheet: CharacterSheet) {
|
||||
// convert the character sheet into json format.
|
||||
val json = try {
|
||||
val data = try {
|
||||
factory.convertToJson(sheet = sheet).let(json::encodeToString)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
|
|
@ -85,7 +85,7 @@ class CharacterSheetStore(
|
|||
try {
|
||||
val file = characterSheetFile(id = sheet.id)
|
||||
file.writeText(
|
||||
text = json,
|
||||
text = data,
|
||||
charset = Charsets.UTF_8,
|
||||
)
|
||||
} 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
|
||||
|
||||
|
||||
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.getAlterationTags
|
||||
import com.pixelized.server.lwa.server.rest.alteration.getAlterations
|
||||
import com.pixelized.server.lwa.server.rest.alteration.putAlteration
|
||||
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignNpc
|
||||
import com.pixelized.server.lwa.server.rest.campaign.getCampaign
|
||||
import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter
|
||||
|
|
@ -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.character.deleteCharacter
|
||||
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.putCharacter
|
||||
import com.pixelized.server.lwa.server.rest.character.putCharacterAlteration
|
||||
|
|
@ -126,6 +130,18 @@ class LocalServer {
|
|||
path = "/detail",
|
||||
body = engine.getAlteration(),
|
||||
)
|
||||
get(
|
||||
path = "/tags",
|
||||
body = engine.getAlterationTags(),
|
||||
)
|
||||
put(
|
||||
path = "/update",
|
||||
body = engine.putAlteration()
|
||||
)
|
||||
delete(
|
||||
path = "/delete",
|
||||
body = engine.deleteAlteration()
|
||||
)
|
||||
}
|
||||
route(
|
||||
path = "/character",
|
||||
|
|
@ -134,6 +150,10 @@ class LocalServer {
|
|||
path = "/all",
|
||||
body = engine.getCharacters(),
|
||||
)
|
||||
get(
|
||||
path = "/tags",
|
||||
body = engine.getCharacterTags(),
|
||||
)
|
||||
get(
|
||||
path = "/detail",
|
||||
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
|
||||
)
|
||||
// Remove the character fom the campaign if needed.
|
||||
// TODO probably useless because all data will not be cleaned up (all campaign / screnes)
|
||||
campaignService.removeInstance(
|
||||
characterSheetId = characterSheetId,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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.characterSheet.factory.CharacterSheetJsonFactory
|
||||
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.expression.ExpressionParser
|
||||
import com.pixelized.shared.lwa.parser.word.WordParser
|
||||
|
|
@ -45,6 +46,7 @@ val factoryDependencies
|
|||
factoryOf(::CampaignJsonV2Factory)
|
||||
factoryOf(::AlteredCharacterSheetFactory)
|
||||
factoryOf(::AlterationJsonFactory)
|
||||
factoryOf(::TagJsonFactory)
|
||||
}
|
||||
|
||||
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 characterSheetId: String,
|
||||
) : 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/"
|
||||
}
|
||||
}
|
||||
|
||||
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