diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 2af9d3a..2027681 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -318,6 +318,7 @@
Empilable
Équipable
Consommable
+ Ajouter une alteration
Édition de personnage
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt
index ac47243..c195595 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt
@@ -12,6 +12,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
@@ -30,8 +31,9 @@ data class GMTagUio(
val meta: Boolean,
)
-@Stable
+@Immutable
object GmTagDefault {
+ @Stable
val padding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)
}
@@ -83,11 +85,16 @@ fun GMTag(
}
}
+@Immutable
+object GmTagButtonDefault {
+ @Stable
+ val padding = PaddingValues()
+}
@Composable
fun GMTagButton(
modifier: Modifier = Modifier,
- padding: PaddingValues = GmTagDefault.padding,
+ padding: PaddingValues = GmTagButtonDefault.padding,
shape: Shape = MaterialTheme.shapes.small,
tag: GMTagUio,
onTag: (() -> Unit)? = null,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMAlterationFieldItemUio.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMAlterationFieldItemUio.kt
new file mode 100644
index 0000000..ade2211
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMAlterationFieldItemUio.kt
@@ -0,0 +1,222 @@
+package com.pixelized.desktop.lwa.ui.screen.gamemaster.item.edit
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.animateFloatAsState
+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.background
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ExposedDropdownMenuBox
+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.ArrowDropDown
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.collectAsState
+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.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+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
+import com.pixelized.desktop.lwa.utils.extention.calculateHorizontalPaddings
+import com.pixelized.desktop.lwa.utils.extention.ribbon
+import kotlinx.coroutines.flow.StateFlow
+
+@Stable
+data class GMAlterationFieldItemUio(
+ val key: String,
+ val value: StateFlow,
+ val error: StateFlow,
+ val menu: List,
+) {
+ @Stable
+ data class Field(
+ val id: String,
+ val label: String,
+ )
+}
+
+@Immutable
+data object AlterationFieldItemDefault {
+ @Stable
+ val padding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
+
+ @Stable
+ val spacing = 8.dp
+}
+
+@Composable
+fun GMAlterationFieldItem(
+ modifier: Modifier = Modifier,
+ padding: PaddingValues = AlterationFieldItemDefault.padding,
+ spacing: Dp = AlterationFieldItemDefault.spacing,
+ field: GMAlterationFieldItemUio,
+ onAlteration: (GMAlterationFieldItemUio.Field?) -> Unit,
+ onRemoveField: (String) -> Unit,
+) {
+ val error = field.error.collectAsState()
+ val menu = remember { mutableStateOf(false) }
+
+ AlterationFieldContent(
+ modifier = modifier,
+ padding = padding,
+ spacing = spacing,
+ isOpen = menu,
+ isError = error,
+ field = field,
+ onOpenStateChange = { menu.value = it },
+ onAlteration = {
+ menu.value = false
+ onAlteration(it)
+ },
+ onRemoveField = onRemoveField,
+ )
+}
+
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun AlterationFieldContent(
+ modifier: Modifier = Modifier,
+ padding: PaddingValues,
+ spacing: Dp,
+ isOpen: State,
+ isError: State,
+ field: GMAlterationFieldItemUio,
+ onOpenStateChange: (Boolean) -> Unit,
+ onAlteration: (GMAlterationFieldItemUio.Field?) -> Unit,
+ onRemoveField: (String) -> Unit,
+) {
+ val horizontal = padding.calculateHorizontalPaddings()
+
+ Column(
+ modifier = Modifier
+ .animateContentSize()
+ .then(other = modifier),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(space = spacing),
+ ) {
+ ExposedDropdownMenuBox(
+ modifier = Modifier
+ .weight(weight = 1f)
+ .clip(
+ shape = MaterialTheme.shapes.small,
+ )
+ .background(
+ color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
+ )
+ .ribbon(
+ color = when (isError.value) {
+ null -> Color.Transparent
+ else -> MaterialTheme.lwa.colorScheme.base.error
+ }
+ ),
+ expanded = isOpen.value,
+ onExpandedChange = onOpenStateChange,
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(height = 48.dp)
+ .padding(paddingValues = horizontal),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ AnimatedContent(
+ modifier = Modifier.weight(1f),
+ targetState = field.value.value?.label,
+ transitionSpec = {
+ val enter = fadeIn()
+ val exit = fadeOut()
+ enter togetherWith exit using SizeTransform(clip = false)
+ },
+ ) {
+ Text(
+ style = MaterialTheme.typography.body1,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = it ?: "",
+ )
+ }
+ val rotation = animateFloatAsState(
+ targetValue = if (isOpen.value) -180f else 0f,
+ )
+ Icon(
+ modifier = Modifier.graphicsLayer { rotationZ = rotation.value },
+ imageVector = Icons.Default.ArrowDropDown,
+ contentDescription = null
+ )
+ }
+ ExposedDropdownMenu(
+ modifier = Modifier.heightIn(max = 480.dp),
+ expanded = isOpen.value,
+ onDismissRequest = { onOpenStateChange(false) }
+ ) {
+ field.menu.forEach {
+ DropdownMenuItem(
+ onClick = { onAlteration(it) },
+ enabled = true,
+ interactionSource = remember { MutableInteractionSource() },
+ content = {
+ Text(
+ color = MaterialTheme.colors.primary,
+ text = it.label,
+ )
+ },
+ )
+ }
+ }
+ }
+
+ IconButton(
+ modifier = Modifier
+ .size(size = 48.dp)
+ .background(
+ color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
+ shape = MaterialTheme.shapes.small,
+ ),
+ onClick = { onRemoveField(field.key) },
+ ) {
+ Icon(
+ imageVector = Icons.Default.Close,
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ }
+ }
+ isError.value?.let {
+ Text(
+ style = MaterialTheme.typography.caption,
+ color = MaterialTheme.colors.error,
+ text = it
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMItemEditFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMItemEditFactory.kt
index cb08b20..0aeeb8f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMItemEditFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/item/edit/GMItemEditFactory.kt
@@ -4,9 +4,16 @@ import com.pixelized.desktop.lwa.ui.composable.checkbox.LwaCheckBoxUio
import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextFieldFlow
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory
+import com.pixelized.shared.lwa.model.alteration.Alteration
import com.pixelized.shared.lwa.model.item.Item
import com.pixelized.shared.lwa.model.tag.Tag
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_description
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_id
@@ -14,6 +21,7 @@ import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_label
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_thumbnail
import org.jetbrains.compose.resources.getString
+import java.util.UUID
class GMItemEditFactory(
private val tagFactory: GMTagFactory,
@@ -21,6 +29,7 @@ class GMItemEditFactory(
suspend fun createForm(
item: Item?,
tags: Collection,
+ alterationFlow: StateFlow