Add inventory item detail detail (basic)
This commit is contained in:
parent
05a376aea8
commit
c94c820efb
28 changed files with 490 additions and 77 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.inventory
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun InventoryDialog() {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.inventory
|
||||
|
||||
class InventoryDialogFactory {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.inventory
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class InventoryDialogViewModel: ViewModel() {
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,16 +55,31 @@ fun GMTag(
|
|||
shape = shape,
|
||||
elevation = elevation,
|
||||
) {
|
||||
Text(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(enabled = onTag != null) { onTag?.invoke() }
|
||||
.padding(paddingValues = padding),
|
||||
style = MaterialTheme.lwa.typography.base.caption,
|
||||
color = animatedColor.value,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = tag.label,
|
||||
)
|
||||
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,
|
||||
maxLines = 1,
|
||||
text = tag.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,12 +105,26 @@ fun GMTagButton(
|
|||
enabled = onTag != null,
|
||||
onClick = { onTag?.invoke() },
|
||||
) {
|
||||
Text(
|
||||
Row(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
color = animatedColor.value,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = tag.label,
|
||||
)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ package com.pixelized.shared.lwa.model.tag
|
|||
data class Tag(
|
||||
val id: String,
|
||||
val label: String,
|
||||
val meta: Boolean,
|
||||
)
|
||||
|
|
@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
|
|||
data class TagJsonV1(
|
||||
override val id: String,
|
||||
val label: String,
|
||||
val meta: Boolean?,
|
||||
) : TagJson
|
||||
Loading…
Add table
Add a link
Reference in a new issue