diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index c6004bd..c830cd1 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -188,6 +188,9 @@
Filtrer l'inventaire
Ajouter à la bourse
Retirer de la bourse
+ Filtrer les objets
+ Ajouter à l'inventaire
+ Cet objet n'a pas de description.
Les caractéristiques constituent les aptitudes innées d’un personnage comme son intelligence, sa force, son charisme, etc. Elles ne sont pas acquises, mais peuvent être parfois augmentées par un entraînement ou une utilisation réussie. Les caractéristiques des humains normaux varient de 2 (niveau extrêmement bas) à 20 (maximum du potentiel humain), avec une moyenne de 10 ou 11. Plus une caractéristique est élevée plus le personnage est puissant dans cette aptitude.\nÀ la création de votre personnage, répartissez les valeurs suivantes dans les différentes caractéristiques : 15, 15, 13, 11, 10, 9 et 7.
La Force représente essentiellement la puissance musculaire du personnage. Elle ne décrit pas nécessairement la masse musculaire brute, mais l’efficacité avec laquelle le personnage exerce ses muscles pour accomplir des actions physiques pénibles.\n\n- Bonus aux dégats\n- Réflexe\n- Athlétisme\n- Lancer\n- Saisie
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/item/ItemRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/item/ItemRepository.kt
index cf6f214..b33f9df 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/item/ItemRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/item/ItemRepository.kt
@@ -5,7 +5,7 @@ import com.pixelized.shared.lwa.model.item.Item
class ItemRepository(
private val itemStore: ItemStore,
) {
- val itemFlow get() = itemStore.items
+ fun itemFlow() = itemStore.items
suspend fun updateItemFlow() {
itemStore.updateItemFlow()
@@ -14,7 +14,7 @@ class ItemRepository(
fun item(
itemId: String?,
): Item? {
- return itemFlow.value[itemId]
+ return itemStore.items.value[itemId]
}
@Throws
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialog.kt
index d2dfc6a..278d741 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialog.kt
@@ -1,8 +1,308 @@
package com.pixelized.desktop.lwa.ui.composable.character.inventory
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+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.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+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.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.FilterQuality
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
+import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage
+import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
+import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
+import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout2
+import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
+import com.pixelized.desktop.lwa.ui.theme.lwa
+import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
+import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__title
+import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
+import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
+
+@Stable
+data class InventoryDialogUio(
+ val characterSheetId: String,
+ val filter: LwaTextFieldUio,
+ val items: List- ,
+) {
+ @Stable
+ data class Item(
+ val itemId: String,
+ val label: String,
+ val tooltips: ToolTip?,
+ ) {
+ @Stable
+ data class ToolTip(
+ val label: String,
+ val description: String,
+ val image: String?,
+ )
+ }
+}
+
+@Stable
+object InventoryDialogDefault {
+ @Stable
+ val paddings = PaddingValues(all = 16.dp)
+
+ @Stable
+ val spacings = 8.dp
+}
+
+@Stable
+object InventoryDialogItemDefault {
+ @Stable
+ val paddings = PaddingValues(horizontal = 16.dp)
+
+ @Stable
+ val spacings = 8.dp
+}
@Composable
-fun InventoryDialog() {
+fun InventoryDialog(
+ dialog: State,
+ paddings: PaddingValues = InventoryDialogDefault.paddings,
+ onDismissRequest: () -> Unit,
+ onItem: (String) -> Unit,
+) {
+ dialog.value?.let {
+ Dialog(
+ onDismissRequest = onDismissRequest,
+ content = {
+ InventoryDialogContent(
+ dialog = it,
+ paddings = paddings,
+ onDismissRequest = onDismissRequest,
+ onItem = onItem,
+ )
+ }
+ )
+ }
+}
+@Composable
+private fun InventoryDialogContent(
+ dialog: InventoryDialogUio,
+ paddings: PaddingValues = InventoryDialogDefault.paddings,
+ spacing: Dp = InventoryDialogDefault.spacings,
+ onDismissRequest: () -> Unit,
+ onItem: (String) -> Unit,
+) {
+ val layoutDirection = LocalLayoutDirection.current
+ val start = remember(layoutDirection) { paddings.calculateStartPadding(layoutDirection) }
+ val end = remember(layoutDirection) { paddings.calculateEndPadding(layoutDirection) }
+ val top = remember(layoutDirection) { paddings.calculateTopPadding() }
+ val bottom = remember(layoutDirection) { paddings.calculateBottomPadding() }
+
+ Box(
+ modifier = Modifier
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = onDismissRequest,
+ )
+ .onPreviewEscape(
+ escape = onDismissRequest,
+ enter = onDismissRequest,
+ )
+ .fillMaxSize()
+ .padding(all = 32.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ DecoratedBox {
+ Surface {
+ Column {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = top, start = start, bottom = spacing, end = end),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ modifier = Modifier.weight(weight = 1f),
+ style = MaterialTheme.typography.h5,
+ fontWeight = FontWeight.Bold,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.character__inventory__inventory__dialog__title),
+ )
+ IconButton(
+ modifier = Modifier.offset(x = end, y = -top),
+ onClick = onDismissRequest,
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_close_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ }
+ }
+ LwaTextField(
+ colors = LwaTextFieldColors(backgroundColor = Color.Transparent),
+ modifier = Modifier.fillMaxWidth(),
+ field = dialog.filter,
+ trailingIcon = {
+ val value = dialog.filter.valueFlow.collectAsState()
+ AnimatedVisibility(
+ visible = value.value.isNotBlank(),
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ IconButton(
+ onClick = { dialog.filter.onValueChange.invoke("") },
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_cancel_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ }
+ }
+ }
+ )
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(weight = 1f),
+ contentPadding = PaddingValues(
+ start = start,
+ top = spacing,
+ end = end,
+ bottom = bottom
+ ),
+ verticalArrangement = Arrangement.spacedBy(space = spacing),
+ ) {
+ items(
+ items = dialog.items,
+ key = { it.itemId },
+ ) { item ->
+ InventoryDialogItem(
+ modifier = Modifier.animateItem(),
+ item = item,
+ onItem = onItem,
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun InventoryDialogItem(
+ modifier: Modifier = Modifier,
+ paddings: PaddingValues = InventoryDialogItemDefault.paddings,
+ spacings: Dp = InventoryDialogItemDefault.spacings,
+ item: InventoryDialogUio.Item,
+ onItem: (String) -> Unit,
+) {
+ TooltipLayout2(
+ modifier = modifier,
+ delayMillis = 500,
+ tips = item.tooltips,
+ tooltip = { tooltips ->
+ DecoratedBox {
+ Surface {
+ Box(
+ modifier = Modifier.padding(all = 16.dp)
+ ) {
+ takeIf { tooltips.image?.isNotEmpty() == true }?.let {
+ DesaturatedAsyncImage(
+ modifier = Modifier
+ .size(96.dp)
+ .align(alignment = Alignment.TopEnd)
+ .offset(x = 8.dp, y = (-8).dp),
+ colorFilter = rememberSaturationFilter(),
+ model = tooltips.image,
+ contentScale = ContentScale.Crop,
+ alignment = Alignment.TopCenter,
+ filterQuality = FilterQuality.High,
+ contentDescription = null,
+ )
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(space = 8.dp)
+ ) {
+ Text(
+ style = MaterialTheme.typography.body2,
+ fontWeight = FontWeight.Bold,
+ text = tooltips.label,
+ )
+ Text(
+ style = MaterialTheme.typography.caption,
+ fontWeight = FontWeight.Light,
+ text = tooltips.description,
+ )
+ }
+ }
+ }
+ }
+ },
+ content = {
+ Row(
+ modifier = Modifier
+ .clip(shape = MaterialTheme.lwa.shapes.item)
+ .clickable(onClick = { onItem(item.itemId) })
+ .background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
+ .minimumInteractiveComponentSize()
+ .padding(paddingValues = paddings)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(space = spacings),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.base.body1,
+ fontWeight = FontWeight.Bold,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = item.label,
+ )
+ }
+ },
+ )
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogFactory.kt
index 37d375e..a4856cf 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogFactory.kt
@@ -1,5 +1,82 @@
package com.pixelized.desktop.lwa.ui.composable.character.inventory
+import com.pixelized.desktop.lwa.repository.settings.model.Settings
+import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
+import com.pixelized.desktop.lwa.utils.extention.unAccent
+import com.pixelized.shared.lwa.model.item.Item
+import kotlinx.coroutines.flow.MutableStateFlow
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character__inventory__filter_item_inventory__label
+import org.jetbrains.compose.resources.getString
+
class InventoryDialogFactory {
+ fun filterItems(
+ items: Collection
- ,
+ filter: String,
+ setting: Settings,
+ ): Collection
- {
+ return if (setting.isGameMaster == true) {
+ items.filter {
+ it.metadata.label.unAccent().contains(other = filter, ignoreCase = true)
+ }
+ } else {
+ items.filter {
+ it.tags.contains(ADDABLE_TAG_ID)
+ }.filter {
+ it.metadata.label.unAccent().contains(other = filter, ignoreCase = true)
+ }
+ }
+ }
+
+ suspend fun convertToDialogUio(
+ items: Collection
- ,
+ filterFlow: Pair, MutableStateFlow>,
+ characterSheetId: String?,
+ ): InventoryDialogUio? {
+ if (characterSheetId == null) return null
+
+ return InventoryDialogUio(
+ characterSheetId = characterSheetId,
+ filter = filterFlow.createTextField(
+ label = getString(Res.string.character__inventory__filter_item_inventory__label),
+ ),
+ items = items.map { item ->
+ InventoryDialogUio.Item(
+ itemId = item.id,
+ label = item.metadata.label,
+ tooltips = takeIf { item.metadata.description.isNotEmpty() }?.let {
+ InventoryDialogUio.Item.ToolTip(
+ label = item.metadata.label,
+ description = item.metadata.description,
+ image = item.metadata.image,
+ )
+ },
+ )
+ }
+ )
+ }
+
+ fun createTextFieldFlow(
+ value: String = "",
+ error: Boolean = false,
+ ): Pair, MutableStateFlow> {
+ return MutableStateFlow(value) to MutableStateFlow(error)
+ }
+
+ private fun Pair, MutableStateFlow>.createTextField(
+ enable: Boolean = true,
+ label: String,
+ ) = LwaTextFieldUio(
+ enable = enable,
+ isError = second,
+ valueFlow = first,
+ label = label,
+ placeHolder = null,
+ onValueChange = { first.value = it },
+ )
+
+ companion object {
+ private const val ADDABLE_TAG_ID = "META:ADDABLE"
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogViewModel.kt
index e567ccd..47e0605 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/inventory/InventoryDialogViewModel.kt
@@ -1,7 +1,52 @@
package com.pixelized.desktop.lwa.ui.composable.character.inventory
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.pixelized.desktop.lwa.repository.item.ItemRepository
+import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
-class InventoryDialogViewModel: ViewModel() {
+class InventoryDialogViewModel(
+ itemRepository: ItemRepository,
+ settingRepository: SettingsRepository,
+ factory: InventoryDialogFactory,
+) : ViewModel() {
+ private val selectedCharacterSheetId = MutableStateFlow(null)
+ private val filterFlow = factory.createTextFieldFlow()
+
+ val inventoryDialog = combine(
+ itemRepository.itemFlow().map { it.values },
+ settingRepository.settingsFlow(),
+ filterFlow.first,
+ selectedCharacterSheetId,
+ ) { items, settings, filter, characterSheetId ->
+ factory.convertToDialogUio(
+ items = factory.filterItems(
+ items = items,
+ filter = filter,
+ setting = settings,
+ ),
+ filterFlow = filterFlow,
+ characterSheetId = characterSheetId,
+ )
+ }.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.Lazily,
+ initialValue = null
+ )
+
+ fun showInventoryDialog(characterSheetId: String?) {
+ filterFlow.first.update { "" }
+ selectedCharacterSheetId.update { characterSheetId }
+ }
+
+ fun hideInventoryDialog() {
+ selectedCharacterSheetId.update { null }
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialog.kt
index 5782e5a..b70de01 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialog.kt
@@ -6,9 +6,15 @@ 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.calculateEndPadding
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
@@ -20,16 +26,23 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage
+import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
+import org.jetbrains.compose.resources.painterResource
@Stable
data class ItemDetailDialogUio(
+ val inventoryId: String?,
val itemId: String,
val label: String,
val description: String,
@@ -76,6 +89,10 @@ private fun ItemDetailDialogContent(
dialog: ItemDetailDialogUio,
onDismissRequest: () -> Unit,
) {
+ val layoutDirection = LocalLayoutDirection.current
+ val end = remember(layoutDirection, paddings) { paddings.calculateEndPadding(layoutDirection) }
+ val top = remember(paddings) { paddings.calculateTopPadding() }
+
Box(
modifier = Modifier
.clickable(
@@ -96,26 +113,47 @@ private fun ItemDetailDialogContent(
Box(
modifier = Modifier.padding(paddingValues = paddings)
) {
- DesaturatedAsyncImage(
- modifier = Modifier
- .size(64.dp * 2)
- .align(alignment = Alignment.TopEnd),
- colorFilter = rememberSaturationFilter(),
- model = dialog.image,
- contentScale = ContentScale.Crop,
- alignment = Alignment.TopCenter,
- filterQuality = FilterQuality.High,
- contentDescription = null,
- )
+ takeIf { dialog.image?.isNotEmpty() == true }?.let {
+ DesaturatedAsyncImage(
+ modifier = Modifier
+ .size(64.dp * 2)
+ .align(alignment = Alignment.TopEnd),
+ colorFilter = rememberSaturationFilter(),
+ model = dialog.image,
+ contentScale = ContentScale.Crop,
+ alignment = Alignment.TopCenter,
+ filterQuality = FilterQuality.High,
+ contentDescription = null,
+ )
+ }
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
) {
- Text(
- style = MaterialTheme.typography.h5,
- fontWeight = FontWeight.Bold,
- text = dialog.label,
- )
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ modifier = Modifier.weight(weight = 1f),
+ style = MaterialTheme.typography.h5,
+ fontWeight = FontWeight.Bold,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = dialog.label,
+ )
+ IconButton(
+ modifier = Modifier.offset(x = end, y = -top),
+ onClick = onDismissRequest,
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_close_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ }
+ }
Text(
style = MaterialTheme.typography.body1,
text = dialog.description,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt
index f92dd65..f790d59 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt
@@ -1,19 +1,26 @@
package com.pixelized.desktop.lwa.ui.composable.character.item
import com.pixelized.shared.lwa.model.item.Item
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character__inventory__description_empty__label
+import org.jetbrains.compose.resources.getString
class ItemDetailDialogFactory {
- fun convertToDialogUio(
+ suspend fun convertToDialogUio(
items: Map,
+ inventoryId: String?,
itemId: String?,
): ItemDetailDialogUio? {
val item = itemId.let(items::get) ?: return null
return ItemDetailDialogUio(
+ inventoryId = inventoryId,
itemId = item.id,
label = item.metadata.label,
- description = item.metadata.description,
+ description = item.metadata.description.ifBlank {
+ getString(Res.string.character__inventory__description_empty__label)
+ },
image = item.metadata.image,
option = ItemDetailDialogUio.OptionUio(
equipable = item.options.equipable,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt
index 543d282..39c1357 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt
@@ -14,21 +14,37 @@ class ItemDetailDialogViewModel(
factory: ItemDetailDialogFactory,
) : ViewModel() {
- private val selectedItemId = MutableStateFlow(null)
+ private val selectedItemId = MutableStateFlow(null)
val itemDialog = combine(
- itemRepository.itemFlow, selectedItemId,
- transform = { items, itemId -> factory.convertToDialogUio(items, itemId) }
+ itemRepository.itemFlow(), selectedItemId,
+ transform = { items, ids ->
+ factory.convertToDialogUio(
+ items = items,
+ inventoryId = ids?.inventoryId,
+ itemId = ids?.itemId,
+ )
+ }
).stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = null
)
- fun showItemDialog(itemId: String?) {
- selectedItemId.update { itemId }
+ fun showItemDialog(inventoryId: String?, itemId: String?) {
+ selectedItemId.update {
+ InventoryItemId(
+ inventoryId = inventoryId,
+ itemId = itemId,
+ )
+ }
}
fun hideItemDialog() {
selectedItemId.update { null }
}
+
+ private data class InventoryItemId(
+ val inventoryId: String?,
+ val itemId: String?,
+ )
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/TooltipLayout2.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/TooltipLayout2.kt
new file mode 100644
index 0000000..e1c4af4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/TooltipLayout2.kt
@@ -0,0 +1,43 @@
+package com.pixelized.desktop.lwa.ui.composable.tooltip
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.TooltipArea
+import androidx.compose.foundation.TooltipPlacement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+
+
+@Composable
+@OptIn(ExperimentalFoundationApi::class)
+fun TooltipLayout2(
+ modifier: Modifier = Modifier,
+ delayMillis: Int = 1000,
+ tooltipPlacement: TooltipPlacement = TooltipPlacement.CursorPoint(DpOffset(0.dp, 16.dp)),
+ tips: T? = null,
+ tooltip: (@Composable (tips: T) -> Unit)? = null,
+ content: @Composable () -> Unit,
+) {
+ if (tips != null && tooltip != null) {
+ TooltipArea(
+ modifier = modifier,
+ tooltip = {
+ Box(
+ modifier = Modifier.width(width = 448.dp),
+ content = { tooltip(tips) },
+ )
+ },
+ content = content,
+ delayMillis = delayMillis,
+ tooltipPlacement = tooltipPlacement,
+ )
+ } else {
+ Box(
+ modifier = modifier,
+ content = { content() },
+ )
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
index aeea9ad..6dda488 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
@@ -30,6 +30,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalBlurController
+import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialog
+import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialog
import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialog
@@ -81,6 +83,7 @@ fun CharacterDetailInventory(
paddings: PaddingValues = CharacterDetailInventoryDefault.padding,
spacing: Dp = CharacterDetailInventoryDefault.spacing,
purseViewModel: PurseDialogViewModel = koinViewModel(),
+ inventoryDialogViewModel: InventoryDialogViewModel = koinViewModel(),
itemDetailDialogViewModel: ItemDetailDialogViewModel = koinViewModel(),
inventory: State,
) {
@@ -97,11 +100,22 @@ fun CharacterDetailInventory(
inventory = unWrap,
onPurse = {
blur.show()
- purseViewModel.showPurseDialog(characterSheetId = it)
+ purseViewModel.showPurseDialog(
+ characterSheetId = it,
+ )
},
onItem = {
blur.show()
- itemDetailDialogViewModel.showItemDialog(itemId = it.itemId)
+ itemDetailDialogViewModel.showItemDialog(
+ inventoryId = it.inventoryId,
+ itemId = it.itemId,
+ )
+ },
+ onAddItem = {
+ blur.show()
+ inventoryDialogViewModel.showInventoryDialog(
+ characterSheetId = it,
+ )
}
)
}
@@ -125,6 +139,21 @@ fun CharacterDetailInventory(
}
)
+ InventoryDialog(
+ dialog = inventoryDialogViewModel.inventoryDialog.collectAsState(),
+ onDismissRequest = {
+ blur.hide()
+ inventoryDialogViewModel.hideInventoryDialog()
+ },
+ onItem = { itemId ->
+ blur.show()
+ itemDetailDialogViewModel.showItemDialog(
+ inventoryId = null,
+ itemId = itemId,
+ )
+ },
+ )
+
ItemDetailDialog(
dialog = itemDetailDialogViewModel.itemDialog.collectAsState(),
onDismissRequest = {
@@ -146,6 +175,7 @@ private fun CharacterDetailInventoryContent(
inventory: CharacterDetailInventoryUio,
onPurse: (String) -> Unit,
onItem: (InventoryItemUio) -> Unit,
+ onAddItem: (String) -> Unit,
) {
Box(
modifier = modifier,
@@ -221,7 +251,7 @@ private fun CharacterDetailInventoryContent(
colors = LwaButtonColors(),
elevation = ButtonDefaults.elevation(4.dp),
shape = CircleShape,
- onClick = { },
+ onClick = { onAddItem(inventory.characterSheetId) },
) {
Text(
modifier = Modifier.padding(end = 4.dp),
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt
index b7940c6..fd9b6e7 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt
@@ -42,7 +42,7 @@ class CharacterDetailInventoryFactory(
)
return combine(
inventoryRepository.inventoryFlow(characterSheetId = characterSheetId),
- itemRepository.itemFlow,
+ itemRepository.itemFlow(),
filterFlow.map { it.unAccent() },
) { inventory, items, filter ->
convertToCharacterInventoryUio(
@@ -85,6 +85,13 @@ class CharacterDetailInventoryFactory(
label = item.metadata.label,
count = it.count,
equipped = it.equipped,
+ tooltips = takeIf { item.metadata.description.isNotEmpty() }?.let {
+ InventoryItemUio.Tooltips(
+ label = item.metadata.label,
+ description = item.metadata.description,
+ image = item.metadata.image,
+ )
+ }
)
}
?.sortedWith(compareBy(Collator.getInstance()) { it.label })
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt
index 7b5709b..3234591 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt
@@ -1,20 +1,22 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item
import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
@@ -23,21 +25,35 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.FilterQuality
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
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.composable.decoratedBox.DecoratedBox
+import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage
+import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout2
import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.utils.extention.ribbon
+import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
@Stable
data class InventoryItemUio(
val inventoryId: String,
val itemId: String,
val label: String,
- val count: Int,
+ val count: Float,
val equipped: Boolean,
-)
+ val tooltips: Tooltips?,
+) {
+ @Stable
+ data class Tooltips(
+ val label: String,
+ val description: String,
+ val image: String?,
+ )
+}
@Stable
object GMCharacterPreviewDefault {
@@ -48,6 +64,7 @@ object GMCharacterPreviewDefault {
val spacing: Dp = 4.dp
}
+@OptIn(ExperimentalFoundationApi::class)
@Composable
fun InventoryItem(
modifier: Modifier = Modifier,
@@ -56,53 +73,90 @@ fun InventoryItem(
item: InventoryItemUio,
onClick: () -> Unit,
) {
- Row(
- modifier = Modifier
- .clip(shape = MaterialTheme.lwa.shapes.item)
- .clickable(onClick = onClick)
- .background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
- .ribbon(
- color = when (item.equipped) {
- true -> MaterialTheme.lwa.colorScheme.base.primary
- else -> Color.Transparent
- }
- )
- .minimumInteractiveComponentSize()
- .padding(paddingValues = padding)
- .then(other = modifier),
- horizontalArrangement = Arrangement.spacedBy(space = spacing),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text(
- modifier = Modifier.alignByBaseline(),
- style = MaterialTheme.lwa.typography.base.body1,
- fontWeight = FontWeight.Bold,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- text = item.label,
- )
- AnimatedVisibility(
- visible = item.count > 1,
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- AnimatedContent(
- targetState = item.count,
- transitionSpec = {
- val prod = if (initialState < targetState) 1 else -1
- val enter = fadeIn() + slideInVertically { -8 * prod }
- val exit = fadeOut() + slideOutVertically { 8 * prod }
- enter togetherWith exit using SizeTransform(clip = false)
+ TooltipLayout2(
+ delayMillis = 500,
+ tips = item.tooltips,
+ tooltip = { tooltips ->
+ DecoratedBox {
+ Surface {
+ Box(
+ modifier = Modifier.padding(all = 16.dp)
+ ) {
+ takeIf { tooltips.image?.isNotEmpty() == true }?.let {
+ DesaturatedAsyncImage(
+ modifier = Modifier
+ .size(96.dp)
+ .align(alignment = Alignment.TopEnd)
+ .offset(x = 8.dp, y = (-8).dp),
+ colorFilter = rememberSaturationFilter(),
+ model = tooltips.image,
+ contentScale = ContentScale.Crop,
+ alignment = Alignment.TopCenter,
+ filterQuality = FilterQuality.High,
+ contentDescription = null,
+ )
+ }
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(space = 8.dp)
+ ) {
+ Text(
+ style = MaterialTheme.typography.body2,
+ fontWeight = FontWeight.Bold,
+ text = tooltips.label,
+ )
+ Text(
+ style = MaterialTheme.typography.caption,
+ fontWeight = FontWeight.Light,
+ text = tooltips.description,
+ )
+ }
+ }
}
+ }
+ },
+ content = {
+ Row(
+ modifier = Modifier
+ .clip(shape = MaterialTheme.lwa.shapes.item)
+ .clickable(onClick = onClick)
+ .background(color = MaterialTheme.lwa.colorScheme.elevated.base2dp)
+ .ribbon(
+ color = when (item.equipped) {
+ true -> MaterialTheme.lwa.colorScheme.base.primary
+ else -> Color.Transparent
+ }
+ )
+ .minimumInteractiveComponentSize()
+ .padding(paddingValues = padding)
+ .then(other = modifier),
+ horizontalArrangement = Arrangement.spacedBy(space = spacing),
+ verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.base.body1,
+ fontWeight = FontWeight.Bold,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
- text = "x${it}",
+ text = item.label,
)
+ AnimatedContent(
+ modifier = Modifier.alignByBaseline(),
+ targetState = item.count,
+ transitionSpec = { fadeIn() togetherWith fadeOut() },
+ ) {
+ when (it) {
+ 0f, 1f -> Unit
+ else -> Text(
+ style = MaterialTheme.lwa.typography.base.caption,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = "x${it}",
+ )
+ }
+ }
}
- }
- }
+ },
+ )
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/list/GMItemViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/list/GMItemViewModel.kt
index 0b6f250..a236887 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/list/GMItemViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/list/GMItemViewModel.kt
@@ -60,7 +60,7 @@ class GMItemViewModel(
)
val items: StateFlow
> = combine(
- itemRepository.itemFlow,
+ itemRepository.itemFlow(),
tagRepository.itemsTagFlow(),
filter.valueFlow.map { it.unAccent() },
selectedTagId,
diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/factory/CharacterSheetJsonFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/factory/CharacterSheetJsonFactory.kt
index 5b8edb9..71ad69a 100644
--- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/factory/CharacterSheetJsonFactory.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/characterSheet/factory/CharacterSheetJsonFactory.kt
@@ -26,8 +26,8 @@ class CharacterSheetJsonFactory(
val json = CharacterSheetJsonV1(
id = sheet.id,
name = sheet.name,
- portrait = sheet.portrait,
- thumbnail = sheet.thumbnail,
+ portrait = sheet.portrait?.takeIf { it.isNotBlank() },
+ thumbnail = sheet.thumbnail?.takeIf { it.isNotBlank() },
level = sheet.level,
shouldLevelUp = sheet.shouldLevelUp,
strength = sheet.strength,
diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/Inventory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/Inventory.kt
index 1fbfb71..6efec1a 100644
--- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/Inventory.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/Inventory.kt
@@ -14,7 +14,7 @@ data class Inventory(
data class Item(
val inventoryId: String,
val itemId: String,
- val count: Int,
+ val count: Float,
val equipped: Boolean,
)
diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/InventoryJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/InventoryJsonV1.kt
index 10fa214..303a0f0 100644
--- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/InventoryJsonV1.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/inventory/InventoryJsonV1.kt
@@ -20,7 +20,7 @@ data class InventoryJsonV1(
data class ItemJson(
val inventoryId: String,
val itemId: String,
- val count: Int,
+ val count: Float,
val equipped: Boolean?,
)
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/item/factory/ItemJsonFactoryV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/item/factory/ItemJsonFactoryV1.kt
index 0577ace..64f9ea2 100644
--- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/item/factory/ItemJsonFactoryV1.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/item/factory/ItemJsonFactoryV1.kt
@@ -30,8 +30,8 @@ class ItemJsonFactoryV1 {
metadata = ItemJsonV1.ItemMetadataJsonV1(
name = item.metadata.label,
description = item.metadata.description,
- image = item.metadata.image,
- thumbnail = item.metadata.thumbnail,
+ image = item.metadata.image?.takeIf { it.isNotBlank() },
+ thumbnail = item.metadata.thumbnail?.takeIf { it.isNotBlank() },
),
options = ItemJsonV1.ItemOptionJsonV1(
stackable = item.options.stackable,