diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 4251b6a..39eea3a 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -178,10 +178,10 @@
niv : %1$d -
niv: %1$d
- joueur
- joueur: %1$d
- npc
- npc: %1$d
+ Joueur
+ Joueur-%1$d
+ Npc
+ Npc-%1$d
Afficher le portrait
Ajouter au groupe
Retirer du groupe (id: %1$d)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterFactory.kt
index ddca343..505328a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterFactory.kt
@@ -3,12 +3,11 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster
import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action
+import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
import com.pixelized.shared.lwa.model.campaign.Campaign
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character_label
-import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character_search
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc_label
-import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc_search
import org.jetbrains.compose.resources.getString
import java.text.Normalizer
@@ -18,6 +17,7 @@ class GameMasterFactory {
campaign: Campaign,
characters: List,
filter: String,
+ tags: Map,
): List {
val normalizedFilter = Normalizer.normalize(filter, Normalizer.Form.NFD)
@@ -26,6 +26,7 @@ class GameMasterFactory {
campaign = campaign,
character = it,
filter = normalizedFilter,
+ tags = tags,
)
}
}
@@ -34,71 +35,67 @@ class GameMasterFactory {
campaign: Campaign,
character: CharacterSheetPreview,
filter: String,
+ tags: Map,
): GMCharacterPreviewUio? {
- val characterId = campaign.characters.keys.firstOrNull {
- it.characterSheetId == character.characterSheetId
- }
-
+ // get the characterInstanceId from the player list corresponding to this CharacterSheet if any
+ val characterInstanceId: Campaign.CharacterInstance.Id? =
+ campaign.characters.keys.firstOrNull {
+ it.characterSheetId == character.characterSheetId
+ }
+ // get all characterInstanceId from the npcs list corresponding to this CharacterSheet if any
val npcIds = campaign.npcs.keys.filter {
it.characterSheetId == character.characterSheetId
}
-
- var playerTagHighlighted = false
- var npcTagHighlighted = false
-
- // Filter process.
+ // Filter process : Name.
if (filter.isNotEmpty()) {
val normalizedName = Normalizer.normalize(character.name, Normalizer.Form.NFD)
// If the filter is not empty and the character is not
- val playerTag = getString(Res.string.game_master__character_tag__character_search)
- val npcTag = getString(Res.string.game_master__character_tag__npc_search)
-
- playerTagHighlighted = playerTag.contains(other = filter, ignoreCase = true)
- if (playerTagHighlighted && characterId == null) {
- return null
- }
-
- npcTagHighlighted = npcTag.contains(other = filter, ignoreCase = true)
- if (npcTagHighlighted && npcIds.isEmpty()) {
- return null
- }
-
val nameHighlight = normalizedName.contains(other = filter, ignoreCase = true)
- if (nameHighlight.not() && playerTagHighlighted.not() && npcTagHighlighted.not()) {
+ if (nameHighlight.not()) {
return null
}
}
-
- val tags = buildList {
- if (characterId != null) {
+ // Tag filter process : Player.
+ if (tags[GMTagUio.TagId.PLAYER] == true && characterInstanceId == null) {
+ return null
+ }
+ // Tag filter process : Npc.
+ if (tags[GMTagUio.TagId.NPC] == true && npcIds.isEmpty()) {
+ return null
+ }
+ // Build the call tag list.
+ val previewTagsList = buildList {
+ if (characterInstanceId != null) {
add(
- GMCharacterPreviewUio.Tag(
+ GMTagUio(
+ id = GMTagUio.TagId.PLAYER,
label = getString(
Res.string.game_master__character_tag__character_label,
- characterId.instanceId,
+ characterInstanceId.instanceId,
),
- highlight = playerTagHighlighted,
+ highlight = tags[GMTagUio.TagId.PLAYER] ?: false,
)
)
}
addAll(
npcIds.map { npcId ->
- GMCharacterPreviewUio.Tag(
+ GMTagUio(
+ id = GMTagUio.TagId.NPC,
label = getString(
Res.string.game_master__character_tag__npc_label,
npcId.instanceId
),
- highlight = npcTagHighlighted,
+ highlight = tags[GMTagUio.TagId.NPC] ?: false,
)
}
)
}
-
+ // build the cell action list
val actions = buildList {
add(
- when (characterId) {
+ when (characterInstanceId) {
null -> Action.AddToGroup
- else -> Action.RemoveFromGroup(instanceId = characterId.instanceId)
+ else -> Action.RemoveFromGroup(instanceId = characterInstanceId.instanceId)
}
)
add(Action.AddToNpc)
@@ -108,11 +105,11 @@ class GameMasterFactory {
}
)
}
-
+ // return the cell UIO.
return GMCharacterPreviewUio(
characterSheetId = character.characterSheetId,
name = character.name, level = character.level,
- tags = tags,
+ tags = previewTagsList,
actions = actions,
)
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt
index 0698ad4..abb8a77 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt
@@ -3,6 +3,11 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -10,7 +15,10 @@ 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.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@@ -21,13 +29,18 @@ import androidx.compose.material.TopAppBar
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.Modifier
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.items.GMCharacterPreview
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
+import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTag
+import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
import com.pixelized.desktop.lwa.ui.theme.lwa
+import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
import org.jetbrains.compose.resources.painterResource
@@ -38,6 +51,7 @@ fun GameMasterScreen(
viewModel: GameMasterViewModel = koinViewModel(),
) {
val characters = viewModel.characters.collectAsState()
+ val tags = viewModel.tags.collectAsState()
Surface(
modifier = Modifier.fillMaxSize()
@@ -45,7 +59,9 @@ fun GameMasterScreen(
GameMasterContent(
modifier = Modifier.fillMaxSize(),
filter = viewModel.filter,
+ tags = tags,
characters = characters,
+ onTag = viewModel::onTag,
onCharacterAction = viewModel::onCharacterAction,
)
}
@@ -54,10 +70,15 @@ fun GameMasterScreen(
@Composable
private fun GameMasterContent(
modifier: Modifier = Modifier,
+ filterChipsState: LazyListState = rememberLazyListState(),
filter: LwaTextFieldUio,
+ tags: State>,
characters: State>,
+ onTag: (GMTagUio.TagId) -> Unit,
onCharacterAction: (String, GMCharacterPreviewUio.Action) -> Unit,
) {
+ val scope = rememberCoroutineScope()
+
Scaffold(
modifier = modifier,
topBar = {
@@ -95,9 +116,32 @@ private fun GameMasterContent(
}
}
)
+ LazyRow(
+ modifier = Modifier.draggable(
+ orientation = Orientation.Horizontal,
+ state = rememberDraggableState { delta ->
+ scope.launch {
+ filterChipsState.scrollBy(-delta)
+ }
+ },
+ ),
+ state = filterChipsState,
+ contentPadding = remember { PaddingValues(all = 8.dp) },
+ horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
+ ) {
+ items(
+ items = tags.value,
+ ) { tag ->
+ GMTag(
+ style = MaterialTheme.lwa.typography.base.body1,
+ tag = tag,
+ onTag = { onTag(tag.id) },
+ )
+ }
+ }
LazyColumn(
modifier = Modifier.weight(1f),
- contentPadding = PaddingValues(all = 8.dp),
+ contentPadding = remember { PaddingValues(all = 8.dp) },
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
items(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterViewModel.kt
index ddf37ad..dffbde7 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterViewModel.kt
@@ -6,11 +6,18 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
+import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
+import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio.TagId
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.launch
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character_search
+import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc_search
+import org.jetbrains.compose.resources.getString
class GameMasterViewModel(
private val campaignRepository: CampaignRepository,
@@ -28,10 +35,34 @@ class GameMasterViewModel(
onValueChange = { _filter.value = it },
)
+ private val _tags = MutableStateFlow(mapOf(TagId.PLAYER to false, TagId.NPC to false))
+ val tags = _tags.map { it: Map ->
+ it.map { (tag, highlight) ->
+ when (tag) {
+ TagId.PLAYER -> GMTagUio(
+ id = TagId.PLAYER,
+ label = getString(Res.string.game_master__character_tag__character_search),
+ highlight = highlight,
+ )
+
+ TagId.NPC -> GMTagUio(
+ id = TagId.NPC,
+ label = getString(Res.string.game_master__character_tag__npc_search),
+ highlight = highlight,
+ )
+ }
+ }
+ }.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyList(),
+ )
+
val characters = combine(
campaignRepository.campaignFlow,
characterSheetRepository.characterSheetPreviewFlow,
filter.valueFlow,
+ _tags,
gameMasterFactory::convertToGMCharacterPreviewUio,
).stateIn(
scope = viewModelScope,
@@ -54,4 +85,12 @@ class GameMasterViewModel(
}
}
}
+
+ fun onTag(
+ id: TagId,
+ ) {
+ _tags.value = _tags.value.toMutableMap().also {
+ it[id] = it.getOrPut(id) { true }.not()
+ }
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMCharacterPreview.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMCharacterPreview.kt
index 34a8945..bf0261d 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMCharacterPreview.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMCharacterPreview.kt
@@ -8,7 +8,6 @@ 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.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
@@ -44,15 +43,9 @@ data class GMCharacterPreviewUio(
val characterSheetId: String,
val name: String,
val level: Int,
- val tags: List,
+ val tags: List,
val actions: List,
) {
- @Stable
- data class Tag(
- val label: String,
- val highlight: Boolean,
- )
-
@Stable
sealed class Action {
@Stable
@@ -127,7 +120,10 @@ fun GMCharacterPreview(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
character.tags.forEach { tag ->
- Tag(tag = tag)
+ GMTag(
+ style = MaterialTheme.lwa.typography.base.caption,
+ tag = tag,
+ )
}
}
}
@@ -200,27 +196,4 @@ private fun OverflowActionMenu(
}
},
)
-}
-
-
-@Composable
-private fun Tag(
- modifier: Modifier = Modifier,
- padding: PaddingValues = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
- tag: GMCharacterPreviewUio.Tag,
-) {
- Text(
- modifier = modifier
- .background(
- color = MaterialTheme.lwa.colorScheme.elevated.base4dp,
- shape = CircleShape,
- )
- .padding(paddingValues = padding),
- style = MaterialTheme.lwa.typography.base.caption,
- color = when (tag.highlight) {
- true -> MaterialTheme.lwa.colorScheme.base.secondary
- else -> MaterialTheme.lwa.colorScheme.base.onSurface
- },
- text = tag.label,
- )
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMTagUio.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMTagUio.kt
new file mode 100644
index 0000000..eeca5b1
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/items/GMTagUio.kt
@@ -0,0 +1,68 @@
+package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
+
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.theme.lwa
+
+
+@Stable
+data class GMTagUio(
+ val id: TagId,
+ val label: String,
+ val highlight: Boolean,
+) {
+ @Stable
+ enum class TagId {
+ PLAYER, NPC
+ }
+}
+
+@Stable
+object GmTagDefault {
+ val padding = PaddingValues(horizontal = 8.dp, vertical = 2.dp)
+}
+
+@Composable
+fun GMTag(
+ modifier: Modifier = Modifier,
+ padding: PaddingValues = GmTagDefault.padding,
+ shape: Shape = CircleShape,
+ elevation: Dp = 2.dp,
+ style: TextStyle,
+ tag: GMTagUio,
+ onTag: (() -> Unit)? = null,
+) {
+ val animatedColor = animateColorAsState(
+ when (tag.highlight) {
+ true -> MaterialTheme.lwa.colorScheme.base.secondary
+ else -> MaterialTheme.lwa.colorScheme.base.onSurface
+ }
+ )
+ Surface(
+ modifier = modifier,
+ shape = shape,
+ elevation = elevation,
+ ) {
+ Text(
+ modifier = Modifier
+ .clickable(enabled = onTag != null) { onTag?.invoke() }
+ .padding(paddingValues = padding),
+ style = style,
+ color = animatedColor.value,
+ text = tag.label,
+ )
+ }
+}
\ No newline at end of file