Add alteration to the GMItem interface.
This commit is contained in:
parent
168ee27826
commit
8d7b63ab96
7 changed files with 487 additions and 37 deletions
|
|
@ -318,6 +318,7 @@
|
|||
<string name="game_master__item__edit_stackable">Empilable</string>
|
||||
<string name="game_master__item__edit_equipable">Équipable</string>
|
||||
<string name="game_master__item__edit_consumable">Consommable</string>
|
||||
<string name="game_master__item__edit_add_alteration">Ajouter une alteration</string>
|
||||
<string name="game_master__character_edit__title">Édition de personnage</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Field?>,
|
||||
val error: StateFlow<String?>,
|
||||
val menu: List<Field>,
|
||||
) {
|
||||
@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<Boolean>,
|
||||
isError: State<String?>,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Tag>,
|
||||
alterationFlow: StateFlow<Map<String, Alteration>>,
|
||||
): GMItemEditViewModel.GMItemEditForm {
|
||||
val idFlow = createLwaTextFieldFlow(
|
||||
label = getString(Res.string.game_master__item__edit_id),
|
||||
|
|
@ -51,6 +60,17 @@ class GMItemEditFactory(
|
|||
selectedTagIds = item?.tags ?: emptyList(),
|
||||
)
|
||||
)
|
||||
val alterations = MutableStateFlow(
|
||||
item?.alterations
|
||||
?.associate { alterationId ->
|
||||
val field = GMItemEditViewModel.GMItemEditForm.GMAlterationFieldItemFlow(
|
||||
errorFlow = MutableStateFlow(null),
|
||||
valueFlow = MutableStateFlow(alterationId)
|
||||
)
|
||||
field.key to field
|
||||
}
|
||||
?: emptyMap()
|
||||
)
|
||||
return GMItemEditViewModel.GMItemEditForm(
|
||||
idFlow = idFlow,
|
||||
labelFlow = labelFlow,
|
||||
|
|
@ -61,12 +81,15 @@ class GMItemEditFactory(
|
|||
equipableFlow = equipableFlow,
|
||||
consumableFlow = consumableFlow,
|
||||
tagFlow = tagFlow,
|
||||
alterationsFlow = alterations,
|
||||
)
|
||||
}
|
||||
|
||||
fun createForm(
|
||||
form: GMItemEditViewModel.GMItemEditForm,
|
||||
fun createPage(
|
||||
scope: CoroutineScope,
|
||||
originId: String?,
|
||||
alterations: Map<String, Alteration>,
|
||||
form: GMItemEditViewModel.GMItemEditForm,
|
||||
): GMItemEditPageUio {
|
||||
return GMItemEditPageUio(
|
||||
id = form.idFlow.createLwaTextField(enable = originId == null),
|
||||
|
|
@ -86,31 +109,82 @@ class GMItemEditFactory(
|
|||
checked = form.consumableFlow,
|
||||
onCheckedChange = { form.consumableFlow.value = it },
|
||||
),
|
||||
alterations = form.alterationsFlow.map { data ->
|
||||
data.map { entry ->
|
||||
createAlterationUio(
|
||||
key = entry.key,
|
||||
value = entry.value.valueFlow.mapNotNull { alterationId ->
|
||||
if (alterationId == null) return@mapNotNull null
|
||||
val alteration = alterations[alterationId] ?: return@mapNotNull null
|
||||
GMAlterationFieldItemUio.Field(
|
||||
id = alterationId,
|
||||
label = alteration.metadata.name,
|
||||
)
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null,
|
||||
),
|
||||
error = entry.value.errorFlow,
|
||||
alterations = alterations.values,
|
||||
)
|
||||
}
|
||||
}.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
),
|
||||
tags = form.tagFlow,
|
||||
)
|
||||
}
|
||||
|
||||
fun createItem(
|
||||
form: GMItemEditPageUio?,
|
||||
form: GMItemEditViewModel.GMItemEditForm?,
|
||||
): Item? {
|
||||
if (form == null) return null
|
||||
return Item(
|
||||
id = form.id.valueFlow.value,
|
||||
id = form.idFlow.valueFlow.value,
|
||||
metadata = Item.MetaData(
|
||||
label = form.label.valueFlow.value,
|
||||
description = form.description.valueFlow.value,
|
||||
image = form.image.valueFlow.value,
|
||||
thumbnail = form.thumbnail.valueFlow.value,
|
||||
label = form.labelFlow.valueFlow.value,
|
||||
description = form.descriptionFlow.valueFlow.value,
|
||||
image = form.imageFlow.valueFlow.value,
|
||||
thumbnail = form.thumbnailFlow.valueFlow.value,
|
||||
),
|
||||
options = Item.Options(
|
||||
stackable = form.stackable.checked.value,
|
||||
equipable = form.equipable.checked.value,
|
||||
consumable = form.consumable.checked.value,
|
||||
stackable = form.stackableFlow.value,
|
||||
equipable = form.equipableFlow.value,
|
||||
consumable = form.consumableFlow.value,
|
||||
),
|
||||
tags = form.tags.value
|
||||
tags = form.tagFlow.value
|
||||
.filter { it.highlight }
|
||||
.map { it.id },
|
||||
alterations = emptyList(), // TODO,
|
||||
alterations = form.alterationsFlow.value.values
|
||||
.mapNotNull { it.valueFlow.value },
|
||||
)
|
||||
}
|
||||
|
||||
fun createAlterationUio(
|
||||
key: String,
|
||||
value: StateFlow<GMAlterationFieldItemUio.Field?>,
|
||||
error: StateFlow<String?>,
|
||||
alterations: Collection<Alteration>,
|
||||
): GMAlterationFieldItemUio {
|
||||
return GMAlterationFieldItemUio(
|
||||
key = key,
|
||||
value = value,
|
||||
error = error,
|
||||
menu = alterations.map { alteration ->
|
||||
createAlterationFieldUio(alteration)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createAlterationFieldUio(
|
||||
alteration: Alteration,
|
||||
): GMAlterationFieldItemUio.Field {
|
||||
return GMAlterationFieldItemUio.Field(
|
||||
id = alteration.id,
|
||||
label = alteration.metadata.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,6 @@ 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.height
|
||||
|
|
@ -37,11 +35,11 @@ import androidx.compose.material.TextButton
|
|||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
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.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -49,7 +47,7 @@ import androidx.compose.ui.input.key.Key
|
|||
import androidx.compose.ui.input.key.KeyEventType
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
|
|
@ -65,15 +63,19 @@ import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagButton
|
|||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.utils.extention.calculateHorizontalPaddings
|
||||
import com.pixelized.desktop.lwa.utils.extention.calculateVerticalPaddings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__action__save
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__item__create
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_add_alteration
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_consumable
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_equipable
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_stackable
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__item__edit_consumable
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_save_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -89,6 +91,7 @@ data class GMItemEditPageUio(
|
|||
val stackable: LwaCheckBoxUio,
|
||||
val equipable: LwaCheckBoxUio,
|
||||
val consumable: LwaCheckBoxUio,
|
||||
val alterations: StateFlow<List<GMAlterationFieldItemUio>>,
|
||||
val tags: MutableStateFlow<List<GMTagUio>>,
|
||||
)
|
||||
|
||||
|
|
@ -102,6 +105,7 @@ fun GMItemEditPage(
|
|||
viewModel: GMItemEditViewModel = koinViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenController.current
|
||||
val focus = LocalFocusManager.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val form = viewModel.form.collectAsState()
|
||||
|
|
@ -110,16 +114,34 @@ fun GMItemEditPage(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
form = form,
|
||||
onBack = {
|
||||
focus.clearFocus(force = true)
|
||||
screen.navigateBack()
|
||||
},
|
||||
onSave = {
|
||||
focus.clearFocus(force = true)
|
||||
scope.launch {
|
||||
if (viewModel.save()) {
|
||||
screen.navigateBack()
|
||||
}
|
||||
}
|
||||
},
|
||||
onAddAlteration = {
|
||||
focus.clearFocus(force = true)
|
||||
viewModel.addAlteration()
|
||||
},
|
||||
onAlteration = { field, selected ->
|
||||
focus.clearFocus(force = true)
|
||||
viewModel.toggleAlteration(
|
||||
field = field,
|
||||
selected = selected,
|
||||
)
|
||||
},
|
||||
onRemoveAlteration = {
|
||||
focus.clearFocus(force = true)
|
||||
viewModel.removeAlteration(key = it)
|
||||
},
|
||||
onTag = { tag ->
|
||||
focus.clearFocus(force = true)
|
||||
viewModel.addTag(tag = tag)
|
||||
},
|
||||
)
|
||||
|
|
@ -144,21 +166,13 @@ private fun GMItemEditContent(
|
|||
paddings: PaddingValues = GMItemEditDefault.paddings,
|
||||
onBack: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
onAddAlteration: () -> Unit,
|
||||
onAlteration: (GMAlterationFieldItemUio, GMAlterationFieldItemUio.Field?) -> Unit,
|
||||
onRemoveAlteration: (String) -> Unit,
|
||||
onTag: (GMTagUio) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val verticalPadding = remember(paddings) {
|
||||
PaddingValues(
|
||||
top = paddings.calculateTopPadding(),
|
||||
bottom = paddings.calculateBottomPadding(),
|
||||
)
|
||||
}
|
||||
val horizontalPadding = remember(paddings, layoutDirection) {
|
||||
PaddingValues(
|
||||
start = paddings.calculateStartPadding(layoutDirection = layoutDirection),
|
||||
end = paddings.calculateEndPadding(layoutDirection = layoutDirection),
|
||||
)
|
||||
}
|
||||
val verticalPadding = paddings.calculateVerticalPaddings()
|
||||
val horizontalPadding = paddings.calculateHorizontalPaddings()
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
|
|
@ -216,6 +230,7 @@ private fun GMItemEditContent(
|
|||
|
||||
else -> {
|
||||
val tags = it.tags.collectAsState()
|
||||
val alterations = it.alterations.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -351,6 +366,22 @@ private fun GMItemEditContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = alterations.value,
|
||||
key = { field -> field.key },
|
||||
) { field ->
|
||||
GMAlterationFieldItem(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues = horizontalPadding),
|
||||
field = field,
|
||||
onAlteration = { alterationId ->
|
||||
onAlteration(field, alterationId)
|
||||
},
|
||||
onRemoveField = onRemoveAlteration,
|
||||
)
|
||||
}
|
||||
item(key = "Actions") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -359,6 +390,20 @@ private fun GMItemEditContent(
|
|||
.padding(paddingValues = horizontalPadding),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
onClick = onAddAlteration,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
text = stringResource(Res.string.game_master__item__edit_add_alteration),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
colors = LwaButtonColors(),
|
||||
shape = CircleShape,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.network.LwaNetworkException
|
||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.repository.tag.TagRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
|
||||
|
|
@ -20,9 +21,11 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
class GMItemEditViewModel(
|
||||
private val itemRepository: ItemRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val tagRepository: TagRepository,
|
||||
private val factory: GMItemEditFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
|
|
@ -35,9 +38,11 @@ class GMItemEditViewModel(
|
|||
private val _form = MutableStateFlow<GMItemEditForm?>(null)
|
||||
val form: StateFlow<GMItemEditPageUio?> = _form.map {
|
||||
if (it == null) return@map null
|
||||
factory.createForm(
|
||||
form = it,
|
||||
factory.createPage(
|
||||
scope = viewModelScope,
|
||||
originId = argument.id,
|
||||
form = it,
|
||||
alterations = alterationRepository.alterationFlow.value
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
|
|
@ -50,12 +55,15 @@ class GMItemEditViewModel(
|
|||
_form.value = factory.createForm(
|
||||
item = itemRepository.item(itemId = argument.id),
|
||||
tags = tagRepository.itemsTags(),
|
||||
alterationFlow = alterationRepository.alterationFlow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(): Boolean {
|
||||
val edited = factory.createItem(form = form.value) ?: return false
|
||||
if (!isFormValid()) return false
|
||||
|
||||
val edited = factory.createItem(form = _form.value) ?: return false
|
||||
|
||||
try {
|
||||
itemRepository.updateItem(
|
||||
|
|
@ -77,6 +85,29 @@ class GMItemEditViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun isFormValid(): Boolean {
|
||||
var isValid = true
|
||||
|
||||
// check for empty values
|
||||
_form.value?.alterationsFlow?.value?.values
|
||||
?.forEach { field ->
|
||||
if (field.valueFlow.value.isNullOrBlank()) {
|
||||
field.errorFlow.value = "Empty alteration are not allowed."
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
|
||||
// check for duplicate alterations id.
|
||||
_form.value?.alterationsFlow?.value?.values
|
||||
?.duplicates { it.valueFlow.value }
|
||||
?.forEach { field ->
|
||||
field.errorFlow.value = "Duplicate alteration are not allowed."
|
||||
isValid = false
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
fun addTag(tag: GMTagUio) {
|
||||
_form.value?.tagFlow?.update { tags ->
|
||||
tags.toMutableList().also {
|
||||
|
|
@ -88,6 +119,38 @@ class GMItemEditViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun toggleAlteration(
|
||||
field: GMAlterationFieldItemUio,
|
||||
selected: GMAlterationFieldItemUio.Field?,
|
||||
) {
|
||||
_form.value?.alterationsFlow?.update { alterations ->
|
||||
alterations.toMutableMap().also {
|
||||
it[field.key]?.valueFlow?.value = selected?.id
|
||||
it[field.key]?.errorFlow?.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addAlteration() {
|
||||
_form.value?.alterationsFlow?.update { alterations ->
|
||||
alterations.toMutableMap().also {
|
||||
val flow = GMItemEditForm.GMAlterationFieldItemFlow(
|
||||
errorFlow = MutableStateFlow(null),
|
||||
valueFlow = MutableStateFlow(null),
|
||||
)
|
||||
it[flow.key] = flow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAlteration(key: String) {
|
||||
_form.value?.alterationsFlow?.update { alterations ->
|
||||
alterations.toMutableMap().also {
|
||||
it.remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class GMItemEditForm(
|
||||
val idFlow: LwaTextFieldFlow,
|
||||
val labelFlow: LwaTextFieldFlow,
|
||||
|
|
@ -97,6 +160,23 @@ class GMItemEditViewModel(
|
|||
val stackableFlow: MutableStateFlow<Boolean>,
|
||||
val equipableFlow: MutableStateFlow<Boolean>,
|
||||
val consumableFlow: MutableStateFlow<Boolean>,
|
||||
val alterationsFlow: MutableStateFlow<Map<String, GMAlterationFieldItemFlow>>,
|
||||
val tagFlow: MutableStateFlow<List<GMTagUio>>,
|
||||
)
|
||||
) {
|
||||
data class GMAlterationFieldItemFlow(
|
||||
val key : String = "${UUID.randomUUID()}-${System.currentTimeMillis()}",
|
||||
val errorFlow: MutableStateFlow<String?>,
|
||||
val valueFlow: MutableStateFlow<String?>,
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <T, R> Iterable<T>.duplicates(selector: (T) -> R): List<T> {
|
||||
val set = HashSet<R>()
|
||||
val list = ArrayList<T>()
|
||||
for (e in this) {
|
||||
val key = selector(e)
|
||||
if (!set.add(key)) list.add(e)
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,27 @@ fun PaddingValues.calculatePaddings(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun PaddingValues.calculateVerticalPaddings(): PaddingValues {
|
||||
return PaddingValues(
|
||||
top = calculateTopPadding(),
|
||||
bottom = calculateBottomPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun PaddingValues.calculateHorizontalPaddings(
|
||||
layoutDirection: LayoutDirection = LocalLayoutDirection.current,
|
||||
): PaddingValues {
|
||||
return PaddingValues(
|
||||
start = calculateStartPadding(layoutDirection = layoutDirection),
|
||||
end = calculateEndPadding(layoutDirection = layoutDirection),
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Stable
|
||||
data class ComputedPaddingValues(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue