Add toggable addIventoryItem action linked to the Addable tag.

This commit is contained in:
Thomas Andres Gomez 2025-04-20 12:11:27 +02:00
parent ae820f5979
commit 04825cafda
9 changed files with 180 additions and 41 deletions

View file

@ -187,6 +187,8 @@
<string name="character__inventory__filter_inventory__label">Filtrer l'inventaire</string> <string name="character__inventory__filter_inventory__label">Filtrer l'inventaire</string>
<string name="character__inventory__add_to_inventory__action">Ajouter un objet</string> <string name="character__inventory__add_to_inventory__action">Ajouter un objet</string>
<string name="character__inventory__use__action">Utiliser</string> <string name="character__inventory__use__action">Utiliser</string>
<string name="character__inventory__equip__action">Équiper</string>
<string name="character__inventory__unequip__action">Déséquiper</string>
<string name="character__inventory__add_to_purse__title">Ajouter à la bourse</string> <string name="character__inventory__add_to_purse__title">Ajouter à la bourse</string>
<string name="character__inventory__remove_from_purse__title">Retirer de la bourse</string> <string name="character__inventory__remove_from_purse__title">Retirer de la bourse</string>
<string name="character__inventory__filter_item_inventory__label">Filtrer les objets</string> <string name="character__inventory__filter_item_inventory__label">Filtrer les objets</string>
@ -194,7 +196,7 @@
<string name="character__inventory__inventory__dialog__action">Ajouter à l'inventaire</string> <string name="character__inventory__inventory__dialog__action">Ajouter à l'inventaire</string>
<string name="character__inventory__inventory__dialog__count">Quantité</string> <string name="character__inventory__inventory__dialog__count">Quantité</string>
<string name="character__inventory__inventory__dialog__count_action">Modifier</string> <string name="character__inventory__inventory__dialog__count_action">Modifier</string>
<string name="character__inventory__inventory__dialog__throw_action">Jetter</string> <string name="character__inventory__inventory__dialog__throw_action">Jeter</string>
<string name="character__inventory__inventory__dialog__equip_action">Equiper</string> <string name="character__inventory__inventory__dialog__equip_action">Equiper</string>
<string name="character__inventory__inventory__dialog__consume_action">Utiliser</string> <string name="character__inventory__inventory__dialog__consume_action">Utiliser</string>
<string name="character__inventory__description_empty__label">Cet objet n'a pas de description.</string> <string name="character__inventory__description_empty__label">Cet objet n'a pas de description.</string>

View file

@ -77,6 +77,6 @@ class InventoryDialogFactory {
) )
companion object { companion object {
private const val ADDABLE_TAG_ID = "META:ADDABLE" const val ADDABLE_TAG_ID = "META:ADDABLE"
} }
} }

View file

@ -44,11 +44,12 @@ import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character__inventory__equip__action
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__action import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__action
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__consume_action import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__consume_action
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__count_action import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__count_action
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__equip_action
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__throw_action import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__throw_action
import lwacharactersheet.composeapp.generated.resources.character__inventory__unequip__action
import lwacharactersheet.composeapp.generated.resources.ic_close_24dp import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@ -66,6 +67,7 @@ data class ItemDetailDialogUio(
// options // options
val countable: LwaTextFieldUio?, val countable: LwaTextFieldUio?,
val consumable: Boolean, val consumable: Boolean,
val equipped: Boolean,
val equipable: Boolean, val equipable: Boolean,
) )
@ -213,9 +215,18 @@ fun ItemDetailDialog(
TextButton( TextButton(
onClick = { onEquip(state) }, onClick = { onEquip(state) },
) { ) {
Text( AnimatedContent(
text = stringResource(Res.string.character__inventory__inventory__dialog__equip_action), targetState = state.equipped,
) ) {
Text(
text = stringResource(
when (it) {
true -> Res.string.character__inventory__unequip__action
else -> Res.string.character__inventory__equip__action
}
)
)
}
} }
} }
if (state.consumable) { if (state.consumable) {

View file

@ -11,13 +11,14 @@ import java.text.DecimalFormat
class ItemDetailDialogFactory { class ItemDetailDialogFactory {
private val floatChecker = Regex("""^\d*[.,]?\d*${'$'}""") private val floatChecker = Regex("""^\d*,?\d*${'$'}""")
private val format = DecimalFormat("#.##") private val format = DecimalFormat("#.##")
suspend fun convertToDialogUio( suspend fun convertToDialogUio(
characterSheetId: String?, characterSheetId: String?,
items: Map<String, Item>, items: Map<String, Item>,
count: Float, count: Float,
equipped: Boolean,
inventoryId: String?, inventoryId: String?,
itemId: String?, itemId: String?,
): ItemDetailDialogUio? { ): ItemDetailDialogUio? {
@ -38,6 +39,7 @@ class ItemDetailDialogFactory {
?.let { createFieldFlow(value = format.format(count)) } ?.let { createFieldFlow(value = format.format(count)) }
?.createTextField(label = getString(Res.string.character__inventory__inventory__dialog__count)), ?.createTextField(label = getString(Res.string.character__inventory__inventory__dialog__count)),
consumable = item.options.consumable, consumable = item.options.consumable,
equipped = equipped,
equipable = item.options.equipable, equipable = item.options.equipable,
) )
} }

View file

@ -52,6 +52,7 @@ class ItemDetailDialogViewModel(
characterSheetId = ids?.characterSheetId, characterSheetId = ids?.characterSheetId,
items = items, items = items,
count = selectedInventoryItem?.count ?: 0f, count = selectedInventoryItem?.count ?: 0f,
equipped = selectedInventoryItem?.equipped ?: false,
inventoryId = ids?.inventoryId, inventoryId = ids?.inventoryId,
itemId = ids?.itemId, itemId = ids?.itemId,
) )
@ -83,7 +84,7 @@ class ItemDetailDialogViewModel(
suspend fun onAddInventoryItem( suspend fun onAddInventoryItem(
characterSheetId: String, characterSheetId: String,
itemId: String, itemId: String,
) : Boolean { ): Boolean {
try { try {
// create the inventory item on the server, get the newly create id from that. // create the inventory item on the server, get the newly create id from that.
val inventoryId = inventoryRepository.createInventoryItem( val inventoryId = inventoryRepository.createInventoryItem(
@ -143,7 +144,7 @@ class ItemDetailDialogViewModel(
suspend fun equipInventoryItem( suspend fun equipInventoryItem(
characterSheetId: String, characterSheetId: String,
inventoryId: String?, inventoryId: String?,
) : Boolean { ): Boolean {
if (inventoryId == null) return false if (inventoryId == null) return false
try { try {
inventoryRepository.equipInventoryItem( inventoryRepository.equipInventoryItem(

View file

@ -34,6 +34,13 @@ class CharacterDetailPanelViewModel(
) : ViewModel() { ) : ViewModel() {
private val characterSheetPanelFlow = MutableStateFlow<CharacterSheetPanel?>(null) private val characterSheetPanelFlow = MutableStateFlow<CharacterSheetPanel?>(null)
private val addItemAction = characterInventoryFactory
.addItemActionFlow()
.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = false,
)
val detail: StateFlow<CharacterDetailPanelUio> = characterSheetPanelFlow val detail: StateFlow<CharacterDetailPanelUio> = characterSheetPanelFlow
.map { characterSheetPanel -> .map { characterSheetPanel ->
@ -55,6 +62,7 @@ class CharacterDetailPanelViewModel(
inventory = characterInventoryFactory.convertToCharacterInventoryUioFlow( inventory = characterInventoryFactory.convertToCharacterInventoryUioFlow(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
scope = viewModelScope, scope = viewModelScope,
addItemAction = addItemAction,
initialValue = ::emptyInventory, initialValue = ::emptyInventory,
), ),
) )

View file

@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -24,9 +26,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
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.LocalBlurController import com.pixelized.desktop.lwa.LocalBlurController
@ -46,7 +52,7 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.utils.extention.plus import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character__inventory__add_to_inventory__action import lwacharactersheet.composeapp.generated.resources.character__inventory__add_to_inventory__action
@ -58,6 +64,7 @@ import org.koin.compose.viewmodel.koinViewModel
@Stable @Stable
data class CharacterDetailInventoryUio( data class CharacterDetailInventoryUio(
val characterSheetId: String, val characterSheetId: String,
val addItemAction: StateFlow<Boolean>,
val filter: LwaTextFieldUio, val filter: LwaTextFieldUio,
val purse: PurseUio, val purse: PurseUio,
val items: List<InventoryItemUio>, val items: List<InventoryItemUio>,
@ -125,6 +132,14 @@ fun CharacterDetailInventory(
inventoryId = it.inventoryId, inventoryId = it.inventoryId,
) )
} }
},
onEquip = {
scope.launch {
itemDetailDialogViewModel.equipInventoryItem(
characterSheetId = it.characterSheetId,
inventoryId = it.inventoryId,
)
}
} }
) )
} }
@ -248,14 +263,16 @@ private fun CharacterDetailInventoryContent(
onPurse: (String) -> Unit, onPurse: (String) -> Unit,
onItem: (InventoryItemUio) -> Unit, onItem: (InventoryItemUio) -> Unit,
onConsume: (InventoryItemUio) -> Unit, onConsume: (InventoryItemUio) -> Unit,
onEquip: (InventoryItemUio) -> Unit,
onAddItem: (String) -> Unit, onAddItem: (String) -> Unit,
) { ) {
val addItemAction = inventory.addItemAction.collectAsState()
Box( Box(
modifier = modifier, modifier = modifier,
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier.matchParentSize(), modifier = Modifier.matchParentSize(),
contentPadding = paddings + PaddingValues(bottom = 56.dp), contentPadding = paddings.plus(addItemAction, PaddingValues(bottom = 56.dp)),
verticalArrangement = Arrangement.spacedBy(space = spacing), verticalArrangement = Arrangement.spacedBy(space = spacing),
) { ) {
item( item(
@ -309,31 +326,61 @@ private fun CharacterDetailInventoryContent(
item = it, item = it,
onClick = { onItem(it) }, onClick = { onItem(it) },
onConsume = { onConsume(it) }, onConsume = { onConsume(it) },
onEquip = { onEquip(it) },
) )
} }
} }
AnimatedVisibility(
Row(
modifier = Modifier modifier = Modifier
.align(alignment = Alignment.BottomEnd) .align(alignment = Alignment.BottomEnd)
.padding(paddingValues = paddings), .padding(paddingValues = paddings),
horizontalArrangement = Arrangement.SpaceBetween, visible = addItemAction.value,
enter = fadeIn(),
exit = fadeOut(),
) { ) {
Button( Row(
colors = LwaButtonColors(), horizontalArrangement = Arrangement.SpaceBetween,
elevation = ButtonDefaults.elevation(4.dp),
shape = CircleShape,
onClick = { onAddItem(inventory.characterSheetId) },
) { ) {
Text( Button(
modifier = Modifier.padding(end = 4.dp), colors = LwaButtonColors(),
text = stringResource(Res.string.character__inventory__add_to_inventory__action), elevation = ButtonDefaults.elevation(4.dp),
) shape = CircleShape,
Icon( onClick = { onAddItem(inventory.characterSheetId) },
imageVector = Icons.Default.Add, ) {
contentDescription = null, Text(
) modifier = Modifier.padding(end = 4.dp),
text = stringResource(Res.string.character__inventory__add_to_inventory__action),
)
Icon(
imageVector = Icons.Default.Add,
contentDescription = null,
)
}
} }
} }
} }
} }
@Stable
@Composable
fun PaddingValues.plus(
state: State<Boolean>,
other: PaddingValues,
): PaddingValues {
val direction = LocalLayoutDirection.current
val sum by remember(this, other, direction, state) {
derivedStateOf {
if (state.value) {
PaddingValues(
start = calculateStartPadding(direction) + other.calculateStartPadding(direction),
top = calculateTopPadding() + other.calculateTopPadding(),
end = calculateEndPadding(direction) + other.calculateEndPadding(direction),
bottom = calculateBottomPadding() + other.calculateBottomPadding(),
)
} else {
this
}
}
}
return sum
}

View file

@ -2,6 +2,8 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
import com.pixelized.desktop.lwa.repository.item.ItemRepository import com.pixelized.desktop.lwa.repository.item.ItemRepository
import com.pixelized.desktop.lwa.repository.tag.TagRepository
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogFactory
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.campaign.player.detail.inventory.item.InventoryItemUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item.InventoryItemUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item.PurseUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item.PurseUio
@ -9,6 +11,7 @@ import com.pixelized.desktop.lwa.utils.extention.unAccent
import com.pixelized.shared.lwa.model.inventory.Inventory import com.pixelized.shared.lwa.model.inventory.Inventory
import com.pixelized.shared.lwa.model.item.Item import com.pixelized.shared.lwa.model.item.Item
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -24,11 +27,19 @@ class CharacterDetailInventoryFactory(
private val inventoryRepository: InventoryRepository, private val inventoryRepository: InventoryRepository,
private val itemRepository: ItemRepository, private val itemRepository: ItemRepository,
) { ) {
fun addItemActionFlow(): Flow<Boolean> {
return itemRepository.itemFlow().map { entry ->
entry.values.any { item ->
item.tags.any { id -> id == ADDABLE_TAG_ID }
}
}
}
suspend fun convertToCharacterInventoryUioFlow( suspend fun convertToCharacterInventoryUioFlow(
characterSheetId: String, characterSheetId: String,
scope: CoroutineScope, scope: CoroutineScope,
started: SharingStarted = SharingStarted.Eagerly, started: SharingStarted = SharingStarted.Eagerly,
addItemAction: StateFlow<Boolean>,
initialValue: () -> CharacterDetailInventoryUio?, initialValue: () -> CharacterDetailInventoryUio?,
): StateFlow<CharacterDetailInventoryUio?> { ): StateFlow<CharacterDetailInventoryUio?> {
val filterFlow = MutableStateFlow("") val filterFlow = MutableStateFlow("")
@ -48,8 +59,9 @@ class CharacterDetailInventoryFactory(
convertToCharacterInventoryUio( convertToCharacterInventoryUio(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
filter = filterField, filter = filterField,
purse = inventory?.purse, addItemAction = addItemAction,
inventory = inventory?.items, purse = inventory.purse,
inventory = inventory.items,
items = items.filterValues { it.metadata.label.unAccent().contains(filter, true) }, items = items.filterValues { it.metadata.label.unAccent().contains(filter, true) },
) )
}.stateIn( }.stateIn(
@ -62,6 +74,7 @@ class CharacterDetailInventoryFactory(
private suspend fun convertToCharacterInventoryUio( private suspend fun convertToCharacterInventoryUio(
characterSheetId: String?, characterSheetId: String?,
filter: LwaTextFieldUio, filter: LwaTextFieldUio,
addItemAction: StateFlow<Boolean>,
purse: Inventory.Purse?, purse: Inventory.Purse?,
inventory: List<Inventory.Item>?, inventory: List<Inventory.Item>?,
items: Map<String, Item>, items: Map<String, Item>,
@ -76,6 +89,7 @@ class CharacterDetailInventoryFactory(
copper = purse?.copper ?: 0, copper = purse?.copper ?: 0,
), ),
filter = filter, filter = filter,
addItemAction = addItemAction,
items = inventory items = inventory
?.mapNotNull { ?.mapNotNull {
val item = items[it.itemId] ?: return@mapNotNull null val item = items[it.itemId] ?: return@mapNotNull null
@ -87,6 +101,7 @@ class CharacterDetailInventoryFactory(
count = it.count, count = it.count,
equipped = it.equipped, equipped = it.equipped,
consumable = item.options.consumable, consumable = item.options.consumable,
equipable = item.options.equipable,
tooltips = takeIf { item.metadata.description.isNotEmpty() }?.let { tooltips = takeIf { item.metadata.description.isNotEmpty() }?.let {
InventoryItemUio.Tooltips( InventoryItemUio.Tooltips(
label = item.metadata.label, label = item.metadata.label,
@ -100,4 +115,8 @@ class CharacterDetailInventoryFactory(
?: emptyList() ?: emptyList()
) )
} }
companion object {
private const val ADDABLE_TAG_ID = InventoryDialogFactory.ADDABLE_TAG_ID
}
} }

View file

@ -1,8 +1,11 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -12,6 +15,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -23,12 +27,14 @@ import androidx.compose.material.TextButton
import androidx.compose.material.minimumInteractiveComponentSize 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.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@ -40,6 +46,8 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.utils.extention.ribbon import com.pixelized.desktop.lwa.utils.extention.ribbon
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character__inventory__equip__action
import lwacharactersheet.composeapp.generated.resources.character__inventory__unequip__action
import lwacharactersheet.composeapp.generated.resources.character__inventory__use__action import lwacharactersheet.composeapp.generated.resources.character__inventory__use__action
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@ -52,6 +60,7 @@ data class InventoryItemUio(
val count: Float, val count: Float,
val equipped: Boolean, val equipped: Boolean,
val consumable: Boolean, val consumable: Boolean,
val equipable: Boolean,
val tooltips: Tooltips?, val tooltips: Tooltips?,
) { ) {
@Stable @Stable
@ -68,7 +77,10 @@ object GMCharacterPreviewDefault {
val paddings = PaddingValues(horizontal = 16.dp) val paddings = PaddingValues(horizontal = 16.dp)
@Stable @Stable
val spacing: Dp = 4.dp val toolTipPaddings = PaddingValues(all = 16.dp)
@Stable
val spacing: Dp = 8.dp
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@ -76,11 +88,20 @@ object GMCharacterPreviewDefault {
fun InventoryItem( fun InventoryItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
padding: PaddingValues = GMCharacterPreviewDefault.paddings, padding: PaddingValues = GMCharacterPreviewDefault.paddings,
toolTipPaddings: PaddingValues = GMCharacterPreviewDefault.toolTipPaddings,
spacing: Dp = GMCharacterPreviewDefault.spacing, spacing: Dp = GMCharacterPreviewDefault.spacing,
item: InventoryItemUio, item: InventoryItemUio,
onClick: () -> Unit, onClick: () -> Unit,
onConsume: () -> Unit, onConsume: () -> Unit,
onEquip: () -> Unit,
) { ) {
val layoutDirection = LocalLayoutDirection.current
val toolTop = remember(toolTipPaddings) { toolTipPaddings.calculateTopPadding() }
val toolEnd = remember(toolTipPaddings, layoutDirection) {
toolTipPaddings.calculateEndPadding(layoutDirection)
}
val end = remember(padding, layoutDirection) { padding.calculateEndPadding(layoutDirection) }
TooltipLayout2( TooltipLayout2(
modifier = modifier, modifier = modifier,
delayMillis = 500, delayMillis = 500,
@ -89,14 +110,14 @@ fun InventoryItem(
DecoratedBox { DecoratedBox {
Surface { Surface {
Box( Box(
modifier = Modifier.padding(all = 16.dp) modifier = Modifier.padding(paddingValues = toolTipPaddings)
) { ) {
takeIf { tooltips.image?.isNotEmpty() == true }?.let { takeIf { tooltips.image?.isNotEmpty() == true }?.let {
DesaturatedAsyncImage( DesaturatedAsyncImage(
modifier = Modifier modifier = Modifier
.size(96.dp) .size(size = 96.dp)
.align(alignment = Alignment.TopEnd) .align(alignment = Alignment.TopEnd)
.offset(x = 8.dp, y = (-8).dp), .offset(x = toolEnd, y = -toolTop),
colorFilter = rememberSaturationFilter(), colorFilter = rememberSaturationFilter(),
model = tooltips.image, model = tooltips.image,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
@ -107,7 +128,7 @@ fun InventoryItem(
} }
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(space = 8.dp) verticalArrangement = Arrangement.spacedBy(space = spacing)
) { ) {
Text( Text(
style = MaterialTheme.typography.body2, style = MaterialTheme.typography.body2,
@ -144,10 +165,11 @@ fun InventoryItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Row( Row(
modifier = Modifier.weight(weight = 1f),
horizontalArrangement = Arrangement.spacedBy(space = spacing), horizontalArrangement = Arrangement.spacedBy(space = spacing),
) { ) {
Text( Text(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline().weight(weight = 1f, fill = false),
style = MaterialTheme.lwa.typography.base.body1, style = MaterialTheme.lwa.typography.base.body1,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@ -157,7 +179,11 @@ fun InventoryItem(
AnimatedContent( AnimatedContent(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
targetState = item.count, targetState = item.count,
transitionSpec = { fadeIn() togetherWith fadeOut() }, transitionSpec = {
val enter = fadeIn() + slideInHorizontally { 16 }
val exit = fadeOut() + slideOutHorizontally { -16 }
enter togetherWith exit using SizeTransform(clip = false)
},
) { ) {
when (it) { when (it) {
0f, 1f -> Unit 0f, 1f -> Unit
@ -170,11 +196,34 @@ fun InventoryItem(
} }
} }
} }
if (item.consumable) { Row(
TextButton( modifier = Modifier.offset(x = end - spacing),
onClick = onConsume, horizontalArrangement = Arrangement.spacedBy(space = spacing),
) { ) {
Text(text = stringResource(Res.string.character__inventory__use__action)) if (item.consumable) {
TextButton(
onClick = onConsume,
) {
Text(text = stringResource(Res.string.character__inventory__use__action))
}
}
if (item.equipable) {
TextButton(
onClick = onEquip,
) {
AnimatedContent(
targetState = item.equipped,
) {
Text(
text = stringResource(
when (it) {
true -> Res.string.character__inventory__unequip__action
else -> Res.string.character__inventory__equip__action
}
)
)
}
}
} }
} }
} }