Add the alteration page in the GameMaster screen.
This commit is contained in:
parent
ee4445490c
commit
76336dfbb0
17 changed files with 507 additions and 44 deletions
|
|
@ -242,5 +242,7 @@
|
||||||
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
|
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
|
||||||
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
||||||
<string name="game_master__create_character_sheet">Créer un personnage</string>
|
<string name="game_master__create_character_sheet">Créer un personnage</string>
|
||||||
|
<string name="game_master__alteration__filter">Filtrer par nom :</string>
|
||||||
|
<string name="game_master__alteration__delete">Supprimer l'altération</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -36,6 +36,8 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEdi
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationFactory
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterFactory
|
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.character.GMCharacterViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
||||||
|
|
@ -122,7 +124,7 @@ val factoryDependencies
|
||||||
factoryOf(::TextMessageFactory)
|
factoryOf(::TextMessageFactory)
|
||||||
factoryOf(::LevelUpFactory)
|
factoryOf(::LevelUpFactory)
|
||||||
factoryOf(::GMCharacterFactory)
|
factoryOf(::GMCharacterFactory)
|
||||||
factoryOf(::GMActionViewModel)
|
factoryOf(::GMAlterationFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModelDependencies
|
val viewModelDependencies
|
||||||
|
|
@ -145,6 +147,8 @@ val viewModelDependencies
|
||||||
viewModelOf(::PortraitOverlayViewModel)
|
viewModelOf(::PortraitOverlayViewModel)
|
||||||
viewModelOf(::GMCharacterViewModel)
|
viewModelOf(::GMCharacterViewModel)
|
||||||
viewModelOf(::GameMasterViewModel)
|
viewModelOf(::GameMasterViewModel)
|
||||||
|
viewModelOf(::GMActionViewModel)
|
||||||
|
viewModelOf(::GMAlterationViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val useCaseDependencies
|
val useCaseDependencies
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class AlterationRepository(
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
|
||||||
|
val alterationFlow get() = alterationStore.alterationsFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This flow transform the campaign instance (player + npc) into a
|
* This flow transform the campaign instance (player + npc) into a
|
||||||
* Map<CharacterSheetId, List<AlterationId>>.
|
* Map<CharacterSheetId, List<AlterationId>>.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationPage
|
||||||
|
|
||||||
object GMAlterationDestination {
|
object GMAlterationDestination {
|
||||||
private const val ROUTE = "GameMasterAlteration"
|
private const val ROUTE = "GameMasterAlteration"
|
||||||
|
|
@ -15,7 +16,7 @@ fun NavGraphBuilder.composableGameMasterAlterationPage() {
|
||||||
composable(
|
composable(
|
||||||
route = GMAlterationDestination.baseRoute(),
|
route = GMAlterationDestination.baseRoute(),
|
||||||
) {
|
) {
|
||||||
|
GMAlterationPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
||||||
|
|
||||||
|
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.utils.extention.unAccent
|
||||||
|
import com.pixelized.shared.lwa.model.alteration.Alteration
|
||||||
|
import java.text.Collator
|
||||||
|
|
||||||
|
class GMAlterationFactory {
|
||||||
|
|
||||||
|
fun filterAlteration(
|
||||||
|
alterations: Collection<Alteration>,
|
||||||
|
unAccentFilter: String,
|
||||||
|
selectedTagId: String?,
|
||||||
|
): List<Alteration> {
|
||||||
|
return alterations.filter {
|
||||||
|
val matchName = it.metadata.name.unAccent().contains(
|
||||||
|
other = unAccentFilter,
|
||||||
|
ignoreCase = true
|
||||||
|
)
|
||||||
|
val matchTag = selectedTagId == null || it.tags.contains(
|
||||||
|
element = selectedTagId
|
||||||
|
)
|
||||||
|
matchName && matchTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertToGMAlterationUio(
|
||||||
|
alterations: List<Alteration>,
|
||||||
|
selectedTagId: String?,
|
||||||
|
): List<GMAlterationUio> {
|
||||||
|
return alterations
|
||||||
|
.map { alteration ->
|
||||||
|
GMAlterationUio(
|
||||||
|
alterationId = alteration.id,
|
||||||
|
label = alteration.metadata.name,
|
||||||
|
tags = alteration.tags.map { tag ->
|
||||||
|
GMTagItemUio(
|
||||||
|
id = tag,
|
||||||
|
label = tag,
|
||||||
|
highlight = tag == 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
||||||
|
|
||||||
|
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.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.theme.color.component.LwaButtonColors
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__create_character_sheet
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMAlterationPage(
|
||||||
|
viewModel: GMAlterationViewModel = koinViewModel(),
|
||||||
|
) {
|
||||||
|
val alterations = viewModel.alterations.collectAsState()
|
||||||
|
val tags = viewModel.alterationTags.collectAsState()
|
||||||
|
|
||||||
|
Box {
|
||||||
|
GMAlterationContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
filter = viewModel.filter,
|
||||||
|
tags = tags,
|
||||||
|
alterations = alterations,
|
||||||
|
onTag = viewModel::onTag,
|
||||||
|
onAlterationEdit = { },
|
||||||
|
onAlterationDelete = { },
|
||||||
|
onAlterationCreate = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GMAlterationContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
padding: Dp = 8.dp,
|
||||||
|
spacing: Dp = 8.dp,
|
||||||
|
filter: LwaTextFieldUio,
|
||||||
|
tags: State<List<GMTagItemUio>>,
|
||||||
|
alterations: State<List<GMAlterationUio>>,
|
||||||
|
onTag: (String) -> Unit,
|
||||||
|
onAlterationEdit: (String) -> Unit,
|
||||||
|
onAlterationDelete: (String) -> Unit,
|
||||||
|
onAlterationCreate: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
elevation = 1.dp,
|
||||||
|
) {
|
||||||
|
GMFilterHeader(
|
||||||
|
padding = padding,
|
||||||
|
spacing = spacing,
|
||||||
|
filter = filter,
|
||||||
|
tags = tags,
|
||||||
|
onTag = onTag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
contentPadding = remember {
|
||||||
|
PaddingValues(
|
||||||
|
start = padding,
|
||||||
|
top = padding,
|
||||||
|
end = padding,
|
||||||
|
bottom = padding + 48.dp + padding,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = alterations.value,
|
||||||
|
key = { it.alterationId },
|
||||||
|
) { alteration ->
|
||||||
|
GMAlteration(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateItem(),
|
||||||
|
alteration = alteration,
|
||||||
|
onAlteration = {
|
||||||
|
onAlterationEdit(alteration.alterationId)
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
onAlterationDelete(alteration.alterationId)
|
||||||
|
},
|
||||||
|
onTag = onTag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.BottomEnd)
|
||||||
|
.padding(all = padding),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
colors = LwaButtonColors(),
|
||||||
|
shape = CircleShape,
|
||||||
|
onClick = onAlterationCreate,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
text = stringResource(Res.string.game_master__create_character_sheet),
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration
|
||||||
|
|
||||||
|
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.utils.extention.unAccent
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
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
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character__filter
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
|
class GMAlterationViewModel(
|
||||||
|
alterationRepository: AlterationRepository,
|
||||||
|
alterationFactory: GMAlterationFactory,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _filter = MutableStateFlow("")
|
||||||
|
val filter = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||||
|
valueFlow = _filter,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
placeHolderFlow = MutableStateFlow(null),
|
||||||
|
onValueChange = { _filter.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,
|
||||||
|
selectedTagId,
|
||||||
|
) { alterationTagIds, selectedTagId ->
|
||||||
|
alterationFactory.convertToGMTagItemUio(
|
||||||
|
alterationTagIds = alterationTagIds,
|
||||||
|
selectedTagId = selectedTagId,
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Lazily,
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val alterations = combine(
|
||||||
|
alterationRepository.alterationFlow,
|
||||||
|
filter.valueFlow.map { it.unAccent() },
|
||||||
|
selectedTagId,
|
||||||
|
transform = { alterations, unAccentFilter, selectedTagId ->
|
||||||
|
alterationFactory.filterAlteration(
|
||||||
|
alterations = alterations.values,
|
||||||
|
unAccentFilter = unAccentFilter,
|
||||||
|
selectedTagId = selectedTagId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).mapLatest { alterations ->
|
||||||
|
alterationFactory.convertToGMAlterationUio(
|
||||||
|
alterations = alterations,
|
||||||
|
selectedTagId = selectedTagId.value
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Lazily,
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onTag(id: String) {
|
||||||
|
selectedTagId.update {
|
||||||
|
when (it) {
|
||||||
|
id -> null
|
||||||
|
else -> id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,11 +13,16 @@ import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
class GMCharacterFactory {
|
class GMCharacterFactory {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PLAYER_ID = "PLAYER"
|
||||||
|
const val NPC_ID = "NPC"
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun convertToGMCharacterPreviewUio(
|
suspend fun convertToGMCharacterPreviewUio(
|
||||||
campaign: Campaign,
|
campaign: Campaign,
|
||||||
characters: List<CharacterSheetPreview>,
|
characters: List<CharacterSheetPreview>,
|
||||||
filter: String,
|
filter: String,
|
||||||
tags: Map<GMTagItemUio.TagId, Boolean>,
|
tags: Map<String, Boolean>,
|
||||||
): List<GMCharacterItemUio> {
|
): List<GMCharacterItemUio> {
|
||||||
val normalizedFilter = filter.unAccent()
|
val normalizedFilter = filter.unAccent()
|
||||||
|
|
||||||
|
|
@ -35,7 +40,7 @@ class GMCharacterFactory {
|
||||||
campaign: Campaign,
|
campaign: Campaign,
|
||||||
character: CharacterSheetPreview,
|
character: CharacterSheetPreview,
|
||||||
filter: String,
|
filter: String,
|
||||||
tags: Map<GMTagItemUio.TagId, Boolean>,
|
tags: Map<String, Boolean>,
|
||||||
): GMCharacterItemUio? {
|
): GMCharacterItemUio? {
|
||||||
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
||||||
val isPlayer = campaign.characters.firstOrNull {
|
val isPlayer = campaign.characters.firstOrNull {
|
||||||
|
|
@ -55,11 +60,11 @@ class GMCharacterFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tag filter process : Player.
|
// Tag filter process : Player.
|
||||||
if (tags[GMTagItemUio.TagId.PLAYER] == true && isPlayer.not()) {
|
if (tags[PLAYER_ID] == true && isPlayer.not()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Tag filter process : Npc.
|
// Tag filter process : Npc.
|
||||||
if (tags[GMTagItemUio.TagId.NPC] == true && isNpc.not()) {
|
if (tags[NPC_ID] == true && isNpc.not()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Build the call tag list.
|
// Build the call tag list.
|
||||||
|
|
@ -67,18 +72,18 @@ class GMCharacterFactory {
|
||||||
if (isPlayer) {
|
if (isPlayer) {
|
||||||
add(
|
add(
|
||||||
GMTagItemUio(
|
GMTagItemUio(
|
||||||
id = GMTagItemUio.TagId.PLAYER,
|
id = PLAYER_ID,
|
||||||
label = getString(Res.string.game_master__character_tag__character),
|
label = getString(Res.string.game_master__character_tag__character),
|
||||||
highlight = tags[GMTagItemUio.TagId.PLAYER] ?: false,
|
highlight = tags[PLAYER_ID] ?: false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (isNpc) {
|
if (isNpc) {
|
||||||
add(
|
add(
|
||||||
GMTagItemUio(
|
GMTagItemUio(
|
||||||
id = GMTagItemUio.TagId.NPC,
|
id = NPC_ID,
|
||||||
label = getString(Res.string.game_master__character_tag__npc),
|
label = getString(Res.string.game_master__character_tag__npc),
|
||||||
highlight = tags[GMTagItemUio.TagId.NPC] ?: false,
|
highlight = tags[NPC_ID] ?: false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -104,4 +109,25 @@ class GMCharacterFactory {
|
||||||
actions = actions,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +165,7 @@ fun GMCharacterContent(
|
||||||
filter: LwaTextFieldUio,
|
filter: LwaTextFieldUio,
|
||||||
tags: State<List<GMTagItemUio>>,
|
tags: State<List<GMTagItemUio>>,
|
||||||
characters: State<List<GMCharacterItemUio>>,
|
characters: State<List<GMCharacterItemUio>>,
|
||||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
onTag: (String) -> Unit,
|
||||||
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
||||||
onCharacterSheetDetail: (String) -> Unit,
|
onCharacterSheetDetail: (String) -> Unit,
|
||||||
onCharacterSheetEdit: (String) -> Unit,
|
onCharacterSheetEdit: (String) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,9 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio.TagId
|
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
|
@ -41,22 +39,15 @@ class GMCharacterViewModel(
|
||||||
onValueChange = { _filter.value = it },
|
onValueChange = { _filter.value = it },
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _tags = MutableStateFlow(mapOf(TagId.PLAYER to false, TagId.NPC to false))
|
private val _tags = MutableStateFlow(
|
||||||
val tags = _tags.map { it: Map<TagId, Boolean> ->
|
mapOf(
|
||||||
it.map { (tag, highlight) ->
|
GMCharacterFactory.PLAYER_ID to false,
|
||||||
when (tag) {
|
GMCharacterFactory.NPC_ID to false
|
||||||
TagId.PLAYER -> GMTagItemUio(
|
|
||||||
id = TagId.PLAYER,
|
|
||||||
label = getString(Res.string.game_master__character_tag__character),
|
|
||||||
highlight = highlight,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
TagId.NPC -> GMTagItemUio(
|
|
||||||
id = TagId.NPC,
|
|
||||||
label = getString(Res.string.game_master__character_tag__npc),
|
|
||||||
highlight = highlight,
|
|
||||||
)
|
)
|
||||||
}
|
val tags = _tags.map { it: Map<String, Boolean> ->
|
||||||
|
it.mapNotNull { (id, highlight) ->
|
||||||
|
factory.convertToGMTagItemUio(id = id, highlight = highlight)
|
||||||
}
|
}
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
|
|
@ -113,7 +104,7 @@ class GMCharacterViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTag(
|
fun onTag(
|
||||||
id: TagId,
|
id: String,
|
||||||
) {
|
) {
|
||||||
_tags.value = _tags.value.toMutableMap().also {
|
_tags.value = _tags.value.toMutableMap().also {
|
||||||
it[id] = it.getOrPut(id) { true }.not()
|
it[id] = it.getOrPut(id) { true }.not()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.DropdownMenu
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
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.filled.MoreVert
|
||||||
|
import androidx.compose.material.minimumInteractiveComponentSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__delete
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_delete_forever_24dp
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class GMAlterationUio(
|
||||||
|
val alterationId: String,
|
||||||
|
val label: String,
|
||||||
|
val tags: List<GMTagItemUio>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
object GMAlterationDefault {
|
||||||
|
val padding = PaddingValues(start = 16.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMAlteration(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
padding: PaddingValues = GMAlterationDefault.padding,
|
||||||
|
alteration: GMAlterationUio,
|
||||||
|
onAlteration: () -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
onTag: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||||
|
.clickable(onClick = onAlteration)
|
||||||
|
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.padding(paddingValues = padding)
|
||||||
|
.then(other = modifier),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.lwa.typography.base.body1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
text = alteration.label,
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f).height(intrinsicSize = IntrinsicSize.Min),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 2.dp, alignment = Alignment.End)
|
||||||
|
) {
|
||||||
|
alteration.tags.forEach { tag ->
|
||||||
|
GMTag(
|
||||||
|
elevation = 4.dp,
|
||||||
|
tag = tag,
|
||||||
|
onTag = { onTag(tag.id) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OverflowActionMenu(
|
||||||
|
alteration = alteration,
|
||||||
|
onDelete = onDelete,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun OverflowActionMenu(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alteration: GMAlterationUio,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
) {
|
||||||
|
val overflowMenu = remember(alteration) {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = { overflowMenu.value = true },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.MoreVert,
|
||||||
|
tint = MaterialTheme.colors.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = overflowMenu.value,
|
||||||
|
onDismissRequest = {
|
||||||
|
overflowMenu.value = false
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
overflowMenu.value = false
|
||||||
|
onDelete()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_delete_forever_24dp),
|
||||||
|
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.lwa.typography.base.body1,
|
||||||
|
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
text = stringResource(Res.string.game_master__alteration__delete),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.minimumInteractiveComponentSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -94,7 +95,7 @@ data class GMCharacterItemUio(
|
||||||
}
|
}
|
||||||
|
|
||||||
object GMCharacterPreviewDefault {
|
object GMCharacterPreviewDefault {
|
||||||
val padding = PaddingValues(horizontal = 16.dp)
|
val padding = PaddingValues(start = 16.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
@ -106,11 +107,8 @@ fun GMCharacter(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onSecondary: () -> Unit,
|
onSecondary: () -> Unit,
|
||||||
onAction: (Action) -> Unit,
|
onAction: (Action) -> Unit,
|
||||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
onTag: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
|
||||||
val startPadding = padding.calculateStartPadding(layoutDirection)
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||||
|
|
@ -120,10 +118,11 @@ fun GMCharacter(
|
||||||
)
|
)
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.padding(paddingValues = padding)
|
||||||
.then(other = modifier),
|
.then(other = modifier),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(start = startPadding),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fun GMFilterHeader(
|
||||||
spacing: Dp = 8.dp,
|
spacing: Dp = 8.dp,
|
||||||
filter: LwaTextFieldUio,
|
filter: LwaTextFieldUio,
|
||||||
tags: State<List<GMTagItemUio>>,
|
tags: State<List<GMTagItemUio>>,
|
||||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
onTag: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
|
@ -19,15 +20,10 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class GMTagItemUio(
|
data class GMTagItemUio(
|
||||||
val id: TagId,
|
val id: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val highlight: Boolean,
|
val highlight: Boolean,
|
||||||
) {
|
)
|
||||||
@Stable
|
|
||||||
enum class TagId {
|
|
||||||
PLAYER, NPC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
object GmTagDefault {
|
object GmTagDefault {
|
||||||
|
|
@ -60,6 +56,8 @@ fun GMTag(
|
||||||
.padding(paddingValues = padding),
|
.padding(paddingValues = padding),
|
||||||
style = MaterialTheme.lwa.typography.base.caption,
|
style = MaterialTheme.lwa.typography.base.caption,
|
||||||
color = animatedColor.value,
|
color = animatedColor.value,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
text = tag.label,
|
text = tag.label,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.pixelized.shared.lwa.parser.expression.Expression
|
||||||
data class Alteration(
|
data class Alteration(
|
||||||
val id: String,
|
val id: String,
|
||||||
val metadata: MetaData,
|
val metadata: MetaData,
|
||||||
|
val tags: List<String>,
|
||||||
val fields: List<Field>,
|
val fields: List<Field>,
|
||||||
) {
|
) {
|
||||||
data class MetaData(
|
data class MetaData(
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class AlterationJsonFactory(
|
||||||
return Alteration(
|
return Alteration(
|
||||||
id = json.id,
|
id = json.id,
|
||||||
metadata = convertFromJsonV1(json = json.metadata),
|
metadata = convertFromJsonV1(json = json.metadata),
|
||||||
|
tags = json.tags ?: emptyList(),
|
||||||
fields = json.fields.map { convertFromJsonV1(json = it) }
|
fields = json.fields.map { convertFromJsonV1(json = it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -49,6 +50,7 @@ class AlterationJsonFactory(
|
||||||
return AlterationJsonV1(
|
return AlterationJsonV1(
|
||||||
id = data.id,
|
id = data.id,
|
||||||
metadata = convertToJson(data = data.metadata),
|
metadata = convertToJson(data = data.metadata),
|
||||||
|
tags = data.tags,
|
||||||
fields = data.fields.map { convertToJson(data = it) },
|
fields = data.fields.map { convertToJson(data = it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
|
||||||
data class AlterationJsonV1(
|
data class AlterationJsonV1(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
val metadata: AlterationMetadataJsonV1,
|
val metadata: AlterationMetadataJsonV1,
|
||||||
|
val tags: List<String>?,
|
||||||
val fields: List<FieldJsonV1>,
|
val fields: List<FieldJsonV1>,
|
||||||
) : AlterationJson {
|
) : AlterationJson {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue