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__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__delete">Supprimer l'altération</string>
|
||||
|
||||
</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.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.levelup.LevelUpFactory
|
||||
|
|
@ -122,7 +124,7 @@ val factoryDependencies
|
|||
factoryOf(::TextMessageFactory)
|
||||
factoryOf(::LevelUpFactory)
|
||||
factoryOf(::GMCharacterFactory)
|
||||
factoryOf(::GMActionViewModel)
|
||||
factoryOf(::GMAlterationFactory)
|
||||
}
|
||||
|
||||
val viewModelDependencies
|
||||
|
|
@ -145,6 +147,8 @@ val viewModelDependencies
|
|||
viewModelOf(::PortraitOverlayViewModel)
|
||||
viewModelOf(::GMCharacterViewModel)
|
||||
viewModelOf(::GameMasterViewModel)
|
||||
viewModelOf(::GMActionViewModel)
|
||||
viewModelOf(::GMAlterationViewModel)
|
||||
}
|
||||
|
||||
val useCaseDependencies
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ class AlterationRepository(
|
|||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
val alterationFlow get() = alterationStore.alterationsFlow
|
||||
|
||||
/**
|
||||
* This flow transform the campaign instance (player + npc) into a
|
||||
* 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.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.GMAlterationPage
|
||||
|
||||
object GMAlterationDestination {
|
||||
private const val ROUTE = "GameMasterAlteration"
|
||||
|
|
@ -15,7 +16,7 @@ fun NavGraphBuilder.composableGameMasterAlterationPage() {
|
|||
composable(
|
||||
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 {
|
||||
|
||||
companion object {
|
||||
const val PLAYER_ID = "PLAYER"
|
||||
const val NPC_ID = "NPC"
|
||||
}
|
||||
|
||||
suspend fun convertToGMCharacterPreviewUio(
|
||||
campaign: Campaign,
|
||||
characters: List<CharacterSheetPreview>,
|
||||
filter: String,
|
||||
tags: Map<GMTagItemUio.TagId, Boolean>,
|
||||
tags: Map<String, Boolean>,
|
||||
): List<GMCharacterItemUio> {
|
||||
val normalizedFilter = filter.unAccent()
|
||||
|
||||
|
|
@ -35,7 +40,7 @@ class GMCharacterFactory {
|
|||
campaign: Campaign,
|
||||
character: CharacterSheetPreview,
|
||||
filter: String,
|
||||
tags: Map<GMTagItemUio.TagId, Boolean>,
|
||||
tags: Map<String, Boolean>,
|
||||
): GMCharacterItemUio? {
|
||||
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
||||
val isPlayer = campaign.characters.firstOrNull {
|
||||
|
|
@ -55,11 +60,11 @@ class GMCharacterFactory {
|
|||
}
|
||||
}
|
||||
// Tag filter process : Player.
|
||||
if (tags[GMTagItemUio.TagId.PLAYER] == true && isPlayer.not()) {
|
||||
if (tags[PLAYER_ID] == true && isPlayer.not()) {
|
||||
return null
|
||||
}
|
||||
// Tag filter process : Npc.
|
||||
if (tags[GMTagItemUio.TagId.NPC] == true && isNpc.not()) {
|
||||
if (tags[NPC_ID] == true && isNpc.not()) {
|
||||
return null
|
||||
}
|
||||
// Build the call tag list.
|
||||
|
|
@ -67,18 +72,18 @@ class GMCharacterFactory {
|
|||
if (isPlayer) {
|
||||
add(
|
||||
GMTagItemUio(
|
||||
id = GMTagItemUio.TagId.PLAYER,
|
||||
id = PLAYER_ID,
|
||||
label = getString(Res.string.game_master__character_tag__character),
|
||||
highlight = tags[GMTagItemUio.TagId.PLAYER] ?: false,
|
||||
highlight = tags[PLAYER_ID] ?: false,
|
||||
)
|
||||
)
|
||||
}
|
||||
if (isNpc) {
|
||||
add(
|
||||
GMTagItemUio(
|
||||
id = GMTagItemUio.TagId.NPC,
|
||||
id = NPC_ID,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
characters: State<List<GMCharacterItemUio>>,
|
||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||
onTag: (String) -> Unit,
|
||||
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
||||
onCharacterSheetDetail: (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.characterSheet.CharacterSheetRepository
|
||||
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.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.TagId
|
||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
|
@ -41,22 +39,15 @@ class GMCharacterViewModel(
|
|||
onValueChange = { _filter.value = it },
|
||||
)
|
||||
|
||||
private val _tags = MutableStateFlow(mapOf(TagId.PLAYER to false, TagId.NPC to false))
|
||||
val tags = _tags.map { it: Map<TagId, Boolean> ->
|
||||
it.map { (tag, highlight) ->
|
||||
when (tag) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
private val _tags = MutableStateFlow(
|
||||
mapOf(
|
||||
GMCharacterFactory.PLAYER_ID to false,
|
||||
GMCharacterFactory.NPC_ID to false
|
||||
)
|
||||
)
|
||||
val tags = _tags.map { it: Map<String, Boolean> ->
|
||||
it.mapNotNull { (id, highlight) ->
|
||||
factory.convertToGMTagItemUio(id = id, highlight = highlight)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
|
|
@ -113,7 +104,7 @@ class GMCharacterViewModel(
|
|||
}
|
||||
|
||||
fun onTag(
|
||||
id: TagId,
|
||||
id: String,
|
||||
) {
|
||||
_tags.value = _tags.value.toMutableMap().also {
|
||||
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.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
|
||||
|
|
@ -94,7 +95,7 @@ data class GMCharacterItemUio(
|
|||
}
|
||||
|
||||
object GMCharacterPreviewDefault {
|
||||
val padding = PaddingValues(horizontal = 16.dp)
|
||||
val padding = PaddingValues(start = 16.dp)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
@ -106,11 +107,8 @@ fun GMCharacter(
|
|||
onClick: () -> Unit,
|
||||
onSecondary: () -> Unit,
|
||||
onAction: (Action) -> Unit,
|
||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||
onTag: (String) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val startPadding = padding.calculateStartPadding(layoutDirection)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||
|
|
@ -120,10 +118,11 @@ fun GMCharacter(
|
|||
)
|
||||
.clickable(onClick = onClick)
|
||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = startPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ fun GMFilterHeader(
|
|||
spacing: Dp = 8.dp,
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagItemUio>>,
|
||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||
onTag: (String) -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
|
@ -19,15 +20,10 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
|||
|
||||
@Stable
|
||||
data class GMTagItemUio(
|
||||
val id: TagId,
|
||||
val id: String,
|
||||
val label: String,
|
||||
val highlight: Boolean,
|
||||
) {
|
||||
@Stable
|
||||
enum class TagId {
|
||||
PLAYER, NPC
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Stable
|
||||
object GmTagDefault {
|
||||
|
|
@ -60,6 +56,8 @@ fun GMTag(
|
|||
.padding(paddingValues = padding),
|
||||
style = MaterialTheme.lwa.typography.base.caption,
|
||||
color = animatedColor.value,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = tag.label,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue