Add inventory item detail detail (basic)

This commit is contained in:
Thomas Andres Gomez 2025-04-17 22:37:44 +02:00
parent 05a376aea8
commit c94c820efb
28 changed files with 490 additions and 77 deletions

View file

@ -61,7 +61,6 @@ class DataSyncViewModel(
networkRepository.status
.filter { status -> status == NetworkRepository.Status.CONNECTED }
.flatMapLatest { campaignRepository.campaignFlow().map { it.instances } }
.distinctUntilChanged()
.onEach { instances ->
instances.forEach { characterSheetId ->
characterRepository.updateCharacterSheet(

View file

@ -25,6 +25,10 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogFactory
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogFactory
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogFactory
import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogFactory
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel
@ -149,6 +153,8 @@ val factoryDependencies
factoryOf(::CharacterSheetCharacteristicDialogFactory)
factoryOf(::CharacterSheetDiminishedDialogFactory)
factoryOf(::CharacterSheetAlterationDialogFactory)
factoryOf(::InventoryDialogFactory)
factoryOf(::ItemDetailDialogFactory)
factoryOf(::PurseDialogFactory)
factoryOf(::TextMessageFactory)
factoryOf(::LevelUpFactory)
@ -175,6 +181,8 @@ val viewModelDependencies
viewModelOf(::CharacterSheetDiminishedDialogViewModel)
viewModelOf(::CharacterSheetCharacteristicDialogViewModel)
viewModelOf(::CharacterSheetAlterationDialogViewModel)
viewModelOf(::InventoryDialogViewModel)
viewModelOf(::ItemDetailDialogViewModel)
viewModelOf(::PurseDialogViewModel)
viewModelOf(::CampaignChatViewModel)
viewModelOf(::SettingsViewModel)

View file

@ -8,7 +8,8 @@ import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.BlurredEdgeTreatment
@ -18,6 +19,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.color.LwaColorPalette
import kotlin.math.max
@Stable
class BlurContentController(
@ -25,15 +27,15 @@ class BlurContentController(
val blurredRadius: Dp = 8.dp,
val scrimColor: Color = LwaColorPalette.DefaultScrimColor,
) {
private val _blurred = mutableStateOf(blurred)
val isBlurred: State<Boolean> get() = _blurred
private val layer = mutableIntStateOf(if (blurred) 1 else 0)
val isBlurred: State<Boolean> = derivedStateOf { layer.value != 0 }
fun show() {
_blurred.value = true
layer.value += 1
}
fun hide() {
_blurred.value = false
layer.value = max(layer.value - 1, 0)
}
}

View file

@ -0,0 +1,8 @@
package com.pixelized.desktop.lwa.ui.composable.character.inventory
import androidx.compose.runtime.Composable
@Composable
fun InventoryDialog() {
}

View file

@ -0,0 +1,5 @@
package com.pixelized.desktop.lwa.ui.composable.character.inventory
class InventoryDialogFactory {
}

View file

@ -0,0 +1,7 @@
package com.pixelized.desktop.lwa.ui.composable.character.inventory
import androidx.lifecycle.ViewModel
class InventoryDialogViewModel: ViewModel() {
}

View file

@ -0,0 +1,128 @@
package com.pixelized.desktop.lwa.ui.composable.character.item
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.fillMaxSize
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.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
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.text.font.FontWeight
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.utils.extention.onPreviewEscape
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
@Stable
data class ItemDetailDialogUio(
val itemId: String,
val label: String,
val description: String,
val image: String?,
val option: OptionUio,
) {
@Stable
data class OptionUio(
val equipable: Boolean,
val consumable: Boolean,
)
}
@Stable
object ItemDetailDialogDefault {
@Stable
val paddings = PaddingValues(all = 16.dp)
}
@Composable
fun ItemDetailDialog(
dialog: State<ItemDetailDialogUio?>,
paddings: PaddingValues = ItemDetailDialogDefault.paddings,
onDismissRequest: () -> Unit,
) {
dialog.value?.let {
Dialog(
onDismissRequest = onDismissRequest,
content = {
ItemDetailDialogContent(
dialog = it,
paddings = paddings,
onDismissRequest = onDismissRequest,
)
}
)
}
}
@Composable
private fun ItemDetailDialogContent(
modifier: Modifier = Modifier,
paddings: PaddingValues,
dialog: ItemDetailDialogUio,
onDismissRequest: () -> Unit,
) {
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 {
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,
)
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
) {
Text(
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold,
text = dialog.label,
)
Text(
style = MaterialTheme.typography.body1,
text = dialog.description,
)
}
}
}
}
}
}

View file

@ -0,0 +1,24 @@
package com.pixelized.desktop.lwa.ui.composable.character.item
import com.pixelized.shared.lwa.model.item.Item
class ItemDetailDialogFactory {
fun convertToDialogUio(
items: Map<String, Item>,
itemId: String?,
): ItemDetailDialogUio? {
val item = itemId.let(items::get) ?: return null
return ItemDetailDialogUio(
itemId = item.id,
label = item.metadata.label,
description = item.metadata.description,
image = item.metadata.image,
option = ItemDetailDialogUio.OptionUio(
equipable = item.options.equipable,
consumable = item.options.consumable,
),
)
}
}

View file

@ -0,0 +1,34 @@
package com.pixelized.desktop.lwa.ui.composable.character.item
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.item.ItemRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
class ItemDetailDialogViewModel(
itemRepository: ItemRepository,
factory: ItemDetailDialogFactory,
) : ViewModel() {
private val selectedItemId = MutableStateFlow<String?>(null)
val itemDialog = combine(
itemRepository.itemFlow, selectedItemId,
transform = { items, itemId -> factory.convertToDialogUio(items, itemId) }
).stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = null
)
fun showItemDialog(itemId: String?) {
selectedItemId.update { itemId }
}
fun hideItemDialog() {
selectedItemId.update { null }
}
}

View file

@ -92,7 +92,8 @@ fun PurseDialog(
}
@Composable
fun PurseContent(
private fun PurseContent(
modifier: Modifier = Modifier,
dialog: PurseDialogUio,
onConfirm: (PurseDialogUio) -> Unit,
onSwapSign: (PurseDialogUio) -> Unit,
@ -119,7 +120,7 @@ fun PurseContent(
DecoratedBox {
Surface {
Column(
modifier = Modifier.padding(horizontal = 8.dp),
modifier = modifier.padding(horizontal = 8.dp),
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {

View file

@ -0,0 +1,57 @@
package com.pixelized.desktop.lwa.ui.composable.image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.DefaultAlpha
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImage
import coil3.compose.AsyncImagePainter.Companion.DefaultTransform
import coil3.compose.AsyncImagePainter.State
import com.pixelized.desktop.lwa.utils.rememberBackgroundGradient
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
@Composable
fun DesaturatedAsyncImage(
model: Any?,
contentDescription: String?,
modifier: Modifier = Modifier,
transform: (State) -> State = DefaultTransform,
onState: ((State) -> Unit)? = null,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
overlay: Brush? = rememberBackgroundGradient(),
colorFilter: ColorFilter? = rememberSaturationFilter(saturation = 0f),
filterQuality: FilterQuality = FilterQuality.Low,
clipToBounds: Boolean = true,
) {
Box(modifier = modifier) {
AsyncImage(
model = model,
contentDescription = contentDescription,
modifier = Modifier.matchParentSize(),
transform = transform,
onState = onState,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
filterQuality = filterQuality,
clipToBounds = clipToBounds,
)
if (overlay != null) {
Box(
modifier = Modifier
.matchParentSize()
.background(brush = overlay)
)
}
}
}

View file

@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
@ -31,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.item.ItemDetailDialog
import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialog
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler
@ -80,6 +81,7 @@ fun CharacterDetailInventory(
paddings: PaddingValues = CharacterDetailInventoryDefault.padding,
spacing: Dp = CharacterDetailInventoryDefault.spacing,
purseViewModel: PurseDialogViewModel = koinViewModel(),
itemDetailDialogViewModel: ItemDetailDialogViewModel = koinViewModel(),
inventory: State<CharacterDetailInventoryUio?>,
) {
val blur = LocalBlurController.current
@ -96,6 +98,10 @@ fun CharacterDetailInventory(
onPurse = {
blur.show()
purseViewModel.showPurseDialog(characterSheetId = it)
},
onItem = {
blur.show()
itemDetailDialogViewModel.showItemDialog(itemId = it.itemId)
}
)
}
@ -119,6 +125,14 @@ fun CharacterDetailInventory(
}
)
ItemDetailDialog(
dialog = itemDetailDialogViewModel.itemDialog.collectAsState(),
onDismissRequest = {
blur.hide()
itemDetailDialogViewModel.hideItemDialog()
}
)
ErrorSnackHandler(
error = purseViewModel.error,
)
@ -131,6 +145,7 @@ private fun CharacterDetailInventoryContent(
spacing: Dp,
inventory: CharacterDetailInventoryUio,
onPurse: (String) -> Unit,
onItem: (InventoryItemUio) -> Unit,
) {
Box(
modifier = modifier,
@ -191,7 +206,7 @@ private fun CharacterDetailInventoryContent(
.animateItem()
.fillMaxWidth(),
item = it,
onClick = { },
onClick = { onItem(it) },
)
}
}

View file

@ -50,7 +50,7 @@ class CharacterDetailInventoryFactory(
filter = filterField,
purse = inventory?.purse,
inventory = inventory?.items,
items = items.filterValues { it.metadata.name.unAccent().contains(filter, true) },
items = items.filterValues { it.metadata.label.unAccent().contains(filter, true) },
)
}.stateIn(
scope = scope,
@ -78,10 +78,12 @@ class CharacterDetailInventoryFactory(
filter = filter,
items = inventory
?.mapNotNull {
val label = items[it.itemId]?.metadata?.name ?: return@mapNotNull null
val item = items[it.itemId] ?: return@mapNotNull null
InventoryItemUio(
inventoryId = it.inventoryId,
label = label,
itemId = it.itemId,
label = item.metadata.label,
count = it.count,
equipped = it.equipped,
)
}

View file

@ -1,7 +1,16 @@
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.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
@ -10,9 +19,13 @@ import androidx.compose.material.Text
import androidx.compose.material.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
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.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.theme.lwa
import com.pixelized.desktop.lwa.utils.extention.ribbon
@ -20,20 +33,26 @@ import com.pixelized.desktop.lwa.utils.extention.ribbon
@Stable
data class InventoryItemUio(
val inventoryId: String,
val itemId: String,
val label: String,
val count: Int,
val equipped: Boolean,
)
@Stable
object GMCharacterPreviewDefault {
@Stable
val paddings = PaddingValues(horizontal = 16.dp)
val paddings = PaddingValues(horizontal = 16.dp, vertical = 4.dp)
@Stable
val spacing: Dp = 4.dp
}
@Composable
fun InventoryItem(
modifier: Modifier = Modifier,
padding: PaddingValues = GMCharacterPreviewDefault.paddings,
spacing: Dp = GMCharacterPreviewDefault.spacing,
item: InventoryItemUio,
onClick: () -> Unit,
) {
@ -51,10 +70,39 @@ fun InventoryItem(
.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)
}
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.base.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "x${it}",
)
}
}
}
}

View file

@ -2,7 +2,9 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button
@ -25,6 +27,7 @@ data class GMTagUio(
val id: String,
val label: String,
val highlight: Boolean,
val meta: Boolean,
)
@Stable
@ -52,10 +55,24 @@ fun GMTag(
shape = shape,
elevation = elevation,
) {
Text(
Row(
modifier = Modifier
.clickable(enabled = onTag != null) { onTag?.invoke() }
.padding(paddingValues = padding),
horizontalArrangement = Arrangement.spacedBy(space = 1.dp),
) {
if (tag.meta) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.base.caption,
color = animatedColor.value,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "",
)
}
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.base.caption,
color = animatedColor.value,
overflow = TextOverflow.Ellipsis,
@ -63,6 +80,7 @@ fun GMTag(
text = tag.label,
)
}
}
}
@ -87,12 +105,26 @@ fun GMTagButton(
enabled = onTag != null,
onClick = { onTag?.invoke() },
) {
Text(
Row(
modifier = Modifier.padding(paddingValues = padding),
horizontalArrangement = Arrangement.spacedBy(space = 1.dp),
) {
if (tag.meta) {
Text(
modifier = Modifier.alignByBaseline(),
color = animatedColor.value,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "",
)
}
Text(
modifier = Modifier.alignByBaseline(),
color = animatedColor.value,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = tag.label,
)
}
}
}

View file

@ -15,6 +15,7 @@ class GMTagFactory {
id = tag.id,
label = tag.label,
highlight = selectedTagIds.contains(tag.id),
meta = tag.meta,
)
}
.sortedWith(
@ -32,6 +33,7 @@ class GMTagFactory {
id = tag.id,
label = tag.label,
highlight = tag.id == selectedTagId,
meta = tag.meta,
)
}
.sortedWith(
@ -47,6 +49,7 @@ class GMTagFactory {
id = tag.id,
label = tag.label,
highlight = tag.id == selectedTagId,
meta = tag.meta,
)
}
}

View file

@ -23,14 +23,14 @@ class GMItemEditFactory(
item: Item?,
tags: Collection<Tag>,
): GMItemEditPageUio {
val idFlow = MutableStateFlow(item?.id ?: "")
val labelFlow = MutableStateFlow(item?.metadata?.name ?: "")
val descriptionFlow = MutableStateFlow(item?.metadata?.description ?: "")
val imageFlow = MutableStateFlow(item?.metadata?.image ?: "")
val thumbnailFlow = MutableStateFlow(item?.metadata?.thumbnail ?: "")
val stackableFlow = MutableStateFlow(item?.options?.stackable ?: false)
val equipableFlow = MutableStateFlow(item?.options?.equipable ?: false)
val consumableFlow = MutableStateFlow(item?.options?.consumable ?: false)
val idFlow = createFlows(initialValue = item?.id ?: "")
val labelFlow = createFlows(initialValue = item?.metadata?.label ?: "")
val descriptionFlow = createFlows(initialValue = item?.metadata?.description ?: "")
val imageFlow = createFlows(initialValue = item?.metadata?.image ?: "")
val thumbnailFlow = createFlows(initialValue = item?.metadata?.thumbnail ?: "")
val stackableFlow = MutableStateFlow(value = item?.options?.stackable ?: false)
val equipableFlow = MutableStateFlow(value = item?.options?.equipable ?: false)
val consumableFlow = MutableStateFlow(value = item?.options?.consumable ?: false)
val tagFlow = MutableStateFlow(
tagFactory.convertToGMTagItemUio(
@ -40,45 +40,21 @@ class GMItemEditFactory(
)
return GMItemEditPageUio(
id = LwaTextFieldUio(
id = idFlow.createLwaTextField(
enable = originId == null,
isError = MutableStateFlow(false),
label = getString(Res.string.game_master__item__edit_id),
valueFlow = idFlow,
placeHolder = null,
onValueChange = { idFlow.value = it },
),
label = LwaTextFieldUio(
enable = true,
isError = MutableStateFlow(false),
label = labelFlow.createLwaTextField(
label = getString(Res.string.game_master__item__edit_label),
valueFlow = labelFlow,
placeHolder = null,
onValueChange = { labelFlow.value = it },
),
description = LwaTextFieldUio(
enable = true,
isError = MutableStateFlow(false),
description = descriptionFlow.createLwaTextField(
label = getString(Res.string.game_master__item__edit_description),
valueFlow = descriptionFlow,
placeHolder = null,
onValueChange = { descriptionFlow.value = it },
),
image = LwaTextFieldUio(
enable = true,
isError = MutableStateFlow(false),
image = imageFlow.createLwaTextField(
label = getString(Res.string.game_master__item__edit_image),
valueFlow = imageFlow,
placeHolder = null,
onValueChange = { descriptionFlow.value = it },
),
thumbnail = LwaTextFieldUio(
enable = true,
isError = MutableStateFlow(false),
thumbnail = thumbnailFlow.createLwaTextField(
label = getString(Res.string.game_master__item__edit_thumbnail),
valueFlow = thumbnailFlow,
placeHolder = null,
onValueChange = { descriptionFlow.value = it },
),
equipable = LwaCheckBoxUio(
checked = equipableFlow,
@ -104,7 +80,7 @@ class GMItemEditFactory(
return Item(
id = form.id.valueFlow.value,
metadata = Item.MetaData(
name = form.label.valueFlow.value,
label = form.label.valueFlow.value,
description = form.description.valueFlow.value,
image = form.image.valueFlow.value,
thumbnail = form.thumbnail.valueFlow.value,
@ -120,4 +96,25 @@ class GMItemEditFactory(
alterations = emptyList(), // TODO,
)
}
private fun createFlows(
initialValue: String = "",
initialError: Boolean = false,
): Pair<MutableStateFlow<String>, MutableStateFlow<Boolean>> {
return MutableStateFlow(value = initialValue) to MutableStateFlow(value = initialError)
}
private fun Pair<MutableStateFlow<String>, MutableStateFlow<Boolean>>.createLwaTextField(
enable: Boolean = true,
label: String,
): LwaTextFieldUio {
return LwaTextFieldUio(
enable = enable,
isError = second,
label = label,
valueFlow = first,
placeHolder = null,
onValueChange = { first.value = it },
)
}
}

View file

@ -245,7 +245,7 @@ private fun GMItemEditContent(
.animateItem()
.fillMaxWidth(),
field = it.image,
singleLine = false,
singleLine = true,
)
}
item(
@ -256,7 +256,7 @@ private fun GMItemEditContent(
.animateItem()
.fillMaxWidth(),
field = it.thumbnail,
singleLine = false,
singleLine = true,
)
}
item(

View file

@ -15,7 +15,7 @@ class GMItemFactory(
selectedTagId: String?,
): List<Item> {
return items.filter {
val matchName = it.metadata.name.unAccent().contains(
val matchName = it.metadata.label.unAccent().contains(
other = unAccentFilter,
ignoreCase = true
)
@ -35,7 +35,7 @@ class GMItemFactory(
.map { item ->
GMItemUio(
itemId = item.id,
label = item.metadata.name,
label = item.metadata.label,
tags = item.tags.mapNotNull {
tags[it]?.let { tag ->
tagFactory.convertToGMTagItemUio(

View file

@ -7,7 +7,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
@Stable
data class LwaSize(
val portrait: Portrait,
val sheet: Sheet,

View file

@ -0,0 +1,22 @@
package com.pixelized.desktop.lwa.utils
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Brush
@Composable
fun rememberBackgroundGradient(
from: Float = 0.5f,
to: Float = 1.0f,
): Brush {
val colorScheme = MaterialTheme.colors
return remember(colorScheme) {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = from),
colorScheme.surface.copy(alpha = to),
)
)
}
}

View file

@ -0,0 +1,17 @@
package com.pixelized.desktop.lwa.utils
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
@Composable
fun rememberSaturationFilter(
saturation: Float = 0f,
): ColorFilter {
return remember(saturation) {
ColorFilter.colorMatrix(
ColorMatrix().also { it.setToSaturation(saturation) }
)
}
}

View file

@ -80,7 +80,7 @@ class ItemStore(
throw JsonConversionException(root = exception)
}
}
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.label })
?: emptyList()
}
@ -113,7 +113,7 @@ class ItemStore(
code = APIResponse.ErrorCode.ItemId,
)
}
if (item.metadata.name.isEmpty()) {
if (item.metadata.label.isEmpty()) {
throw BusinessException(
message = "Item 'name' is a mandatory field.",
code = APIResponse.ErrorCode.ItemName,
@ -146,7 +146,7 @@ class ItemStore(
}
}
.sortedWith(compareBy(Collator.getInstance()) {
it.metadata.name
it.metadata.label
})
}
}
@ -173,7 +173,7 @@ class ItemStore(
item.removeIf { it.id == id }
}
.sortedWith(compareBy(Collator.getInstance()) {
it.metadata.name
it.metadata.label
})
}
}

View file

@ -8,7 +8,7 @@ data class Item(
val alterations: List<String>,
) {
data class MetaData(
val name: String,
val label: String,
val description: String,
val thumbnail: String?,
val image: String?,

View file

@ -9,7 +9,7 @@ class ItemJsonFactoryV1 {
return Item(
id = json.id,
metadata = Item.MetaData(
name = json.metadata.name,
label = json.metadata.name,
description = json.metadata.description,
image = json.metadata.image,
thumbnail = json.metadata.thumbnail,
@ -28,7 +28,7 @@ class ItemJsonFactoryV1 {
return ItemJsonV1(
id = item.id,
metadata = ItemJsonV1.ItemMetadataJsonV1(
name = item.metadata.name,
name = item.metadata.label,
description = item.metadata.description,
image = item.metadata.image,
thumbnail = item.metadata.thumbnail,

View file

@ -3,4 +3,5 @@ package com.pixelized.shared.lwa.model.tag
data class Tag(
val id: String,
val label: String,
val meta: Boolean,
)

View file

@ -9,6 +9,7 @@ class TagJsonFactory {
is TagJsonV1 -> Tag(
id = json.id,
label = json.label,
meta = json.meta ?: false,
)
}
}
@ -19,6 +20,7 @@ class TagJsonFactory {
return TagJsonV1(
id = tag.id,
label = tag.label,
meta = tag.meta.takeIf { it },
)
}
}

View file

@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
data class TagJsonV1(
override val id: String,
val label: String,
val meta: Boolean?,
) : TagJson