Add Addable InventoryItem feature & quantity change.
This commit is contained in:
parent
48074f3d13
commit
9fce3f1cb8
28 changed files with 785 additions and 343 deletions
|
|
@ -111,10 +111,15 @@ interface LwaClient {
|
|||
): APIResponse<InventoryJson>
|
||||
|
||||
suspend fun putInventory(
|
||||
json: InventoryJson,
|
||||
inventory: InventoryJson,
|
||||
create: Boolean,
|
||||
): APIResponse<Unit>
|
||||
|
||||
suspend fun createInventoryItem(
|
||||
characterSheetId: String,
|
||||
itemId: String,
|
||||
): APIResponse<String>
|
||||
|
||||
suspend fun deleteInventory(
|
||||
characterSheetId: String,
|
||||
): APIResponse<Unit>
|
||||
|
|
|
|||
|
|
@ -192,6 +192,14 @@ class LwaClientImpl(
|
|||
}
|
||||
.body<APIResponse<Unit>>()
|
||||
|
||||
@Throws
|
||||
override suspend fun createInventoryItem(
|
||||
characterSheetId: String,
|
||||
itemId: String,
|
||||
): APIResponse<String> = client
|
||||
.put("$root/inventory/create?characterSheetId=$characterSheetId&itemId=$itemId")
|
||||
.body<APIResponse<String>>()
|
||||
|
||||
@Throws
|
||||
override suspend fun deleteInventory(characterSheetId: String): APIResponse<Unit> = client
|
||||
.delete("$root/inventory/delete?characterSheetId=$characterSheetId")
|
||||
|
|
|
|||
|
|
@ -2,27 +2,32 @@ package com.pixelized.desktop.lwa.repository.inventory
|
|||
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class InventoryRepository(
|
||||
private val inventoryStore: InventoryStore,
|
||||
) {
|
||||
val inventoryFlow get() = inventoryStore.inventories
|
||||
|
||||
suspend fun updateInventoryFlow(characterSheetId: String) {
|
||||
inventoryStore.updateInventoryFlow(characterSheetId = characterSheetId)
|
||||
}
|
||||
|
||||
fun inventoryFlow(): StateFlow<Map<String, Inventory>> {
|
||||
return inventoryStore.inventories
|
||||
}
|
||||
|
||||
fun inventory(
|
||||
characterSheetId: String?,
|
||||
): Inventory? {
|
||||
return inventoryFlow.value[characterSheetId]
|
||||
characterSheetId: String,
|
||||
): Inventory {
|
||||
return inventoryStore.inventories.value[characterSheetId]
|
||||
?: Inventory.empty(characterSheetId)
|
||||
}
|
||||
|
||||
fun inventoryFlow(
|
||||
characterSheetId: String?,
|
||||
): Flow<Inventory?> {
|
||||
return inventoryFlow.map { it[characterSheetId] }
|
||||
characterSheetId: String,
|
||||
): Flow<Inventory> {
|
||||
return inventoryStore.inventories
|
||||
.map { it[characterSheetId] ?: Inventory.empty(characterSheetId) }
|
||||
}
|
||||
|
||||
@Throws
|
||||
|
|
@ -36,6 +41,17 @@ class InventoryRepository(
|
|||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun createInventoryItem(
|
||||
characterSheetId: String,
|
||||
itemId: String,
|
||||
): String {
|
||||
return inventoryStore.createInventoryItem(
|
||||
characterSheetId = characterSheetId,
|
||||
itemId = itemId,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteItem(
|
||||
characterSheetId: String,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class InventoryStore(
|
|||
create: Boolean,
|
||||
) {
|
||||
val request = client.putInventory(
|
||||
json = factory.convertToJson(inventory = inventory),
|
||||
inventory = factory.convertToJson(inventory = inventory),
|
||||
create = create,
|
||||
)
|
||||
if (request.success.not()) {
|
||||
|
|
@ -73,6 +73,21 @@ class InventoryStore(
|
|||
}
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun createInventoryItem(
|
||||
characterSheetId: String,
|
||||
itemId: String,
|
||||
): String {
|
||||
val request = client.createInventoryItem(
|
||||
characterSheetId = characterSheetId,
|
||||
itemId = itemId,
|
||||
)
|
||||
if (request.success.not()) {
|
||||
LwaClient.error(error = request)
|
||||
}
|
||||
return request.data!!
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun deleteInventory(
|
||||
characterSheetId: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
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.Color
|
||||
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.utils.extention.onPreviewEscape
|
||||
|
||||
@Stable
|
||||
object LwaDialogDefault {
|
||||
val paddings = PaddingValues(vertical = 32.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> LwaDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings: PaddingValues = LwaDialogDefault.paddings,
|
||||
color: Color = MaterialTheme.colors.surface,
|
||||
state: State<T?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
content: @Composable BoxScope.(T) -> Unit,
|
||||
) {
|
||||
state.value?.let { dialog ->
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = onConfirm,
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues = paddings)
|
||||
.then(other = modifier),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = { },
|
||||
)
|
||||
) {
|
||||
Surface(
|
||||
color = color,
|
||||
) {
|
||||
Box {
|
||||
content(dialog)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import androidx.compose.animation.fadeOut
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -14,7 +13,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -43,7 +41,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
|
|
@ -51,7 +49,6 @@ import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
|||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout2
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
|
||||
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__title
|
||||
|
|
@ -103,31 +100,33 @@ object InventoryDialogItemDefault {
|
|||
fun InventoryDialog(
|
||||
dialog: State<InventoryDialogUio?>,
|
||||
paddings: PaddingValues = InventoryDialogDefault.paddings,
|
||||
spacing: Dp = InventoryDialogDefault.spacings,
|
||||
onDismissRequest: () -> Unit,
|
||||
onItem: (String) -> Unit,
|
||||
onItem: (InventoryDialogUio, String) -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
Dialog(
|
||||
LwaDialog(
|
||||
state = dialog,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = onDismissRequest,
|
||||
) {
|
||||
InventoryDialogContent(
|
||||
dialog = it,
|
||||
paddings = paddings,
|
||||
spacing = spacing,
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = {
|
||||
InventoryDialogContent(
|
||||
dialog = it,
|
||||
paddings = paddings,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onItem = onItem,
|
||||
)
|
||||
}
|
||||
onItem = onItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InventoryDialogContent(
|
||||
modifier: Modifier = Modifier,
|
||||
dialog: InventoryDialogUio,
|
||||
paddings: PaddingValues = InventoryDialogDefault.paddings,
|
||||
spacing: Dp = InventoryDialogDefault.spacings,
|
||||
onDismissRequest: () -> Unit,
|
||||
onItem: (String) -> Unit,
|
||||
onItem: (InventoryDialogUio, String) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val start = remember(layoutDirection) { paddings.calculateStartPadding(layoutDirection) }
|
||||
|
|
@ -135,98 +134,80 @@ private fun InventoryDialogContent(
|
|||
val top = remember(layoutDirection) { paddings.calculateTopPadding() }
|
||||
val bottom = remember(layoutDirection) { paddings.calculateBottomPadding() }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.onPreviewEscape(
|
||||
escape = onDismissRequest,
|
||||
enter = onDismissRequest,
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(all = 32.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
DecoratedBox {
|
||||
Surface {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = top, start = start, bottom = spacing, end = end),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = top, start = start, bottom = spacing, end = end),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
style = MaterialTheme.typography.h5,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(Res.string.character__inventory__inventory__dialog__title),
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.offset(x = end, y = -top),
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_close_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
LwaTextField(
|
||||
colors = LwaTextFieldColors(backgroundColor = Color.Transparent),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = dialog.filter,
|
||||
trailingIcon = {
|
||||
val value = dialog.filter.valueFlow.collectAsState()
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { dialog.filter.onValueChange.invoke("") },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
style = MaterialTheme.typography.h5,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(Res.string.character__inventory__inventory__dialog__title),
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_cancel_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.offset(x = end, y = -top),
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_close_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
LwaTextField(
|
||||
colors = LwaTextFieldColors(backgroundColor = Color.Transparent),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = dialog.filter,
|
||||
trailingIcon = {
|
||||
val value = dialog.filter.valueFlow.collectAsState()
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { dialog.filter.onValueChange.invoke("") },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_cancel_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(weight = 1f),
|
||||
contentPadding = PaddingValues(
|
||||
start = start,
|
||||
top = spacing,
|
||||
end = end,
|
||||
bottom = bottom
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
items(
|
||||
items = dialog.items,
|
||||
key = { it.itemId },
|
||||
) { item ->
|
||||
InventoryDialogItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
item = item,
|
||||
onItem = onItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(weight = 1f),
|
||||
contentPadding = PaddingValues(
|
||||
start = start,
|
||||
top = spacing,
|
||||
end = end,
|
||||
bottom = bottom
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
items(
|
||||
items = dialog.items,
|
||||
key = { it.itemId },
|
||||
) { item ->
|
||||
InventoryDialogItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
item = item,
|
||||
onItem = { onItem(dialog, it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.item
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -29,135 +31,164 @@ import androidx.compose.ui.layout.ContentScale
|
|||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
|
||||
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__action
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__count_action
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@Stable
|
||||
data class ItemDetailDialogUio(
|
||||
val characterSheetId: String,
|
||||
val inventoryId: String?,
|
||||
val itemId: String,
|
||||
// meta
|
||||
val label: String,
|
||||
val description: String,
|
||||
val image: String?,
|
||||
val option: OptionUio,
|
||||
) {
|
||||
@Stable
|
||||
data class OptionUio(
|
||||
val equipable: Boolean,
|
||||
val consumable: Boolean,
|
||||
)
|
||||
}
|
||||
val count: Float,
|
||||
// options
|
||||
val countable: LwaTextFieldUio?,
|
||||
)
|
||||
|
||||
@Stable
|
||||
object ItemDetailDialogDefault {
|
||||
@Stable
|
||||
val paddings = PaddingValues(all = 16.dp)
|
||||
|
||||
@Stable
|
||||
val spacings = 8.dp
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ItemDetailDialog(
|
||||
dialog: State<ItemDetailDialogUio?>,
|
||||
paddings: PaddingValues = ItemDetailDialogDefault.paddings,
|
||||
spacings: Dp = ItemDetailDialogDefault.spacings,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (ItemDetailDialogUio) -> Unit,
|
||||
onAddItem: (ItemDetailDialogUio) -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = {
|
||||
ItemDetailDialogContent(
|
||||
dialog = it,
|
||||
paddings = paddings,
|
||||
onDismissRequest = onDismissRequest,
|
||||
LwaDialog(
|
||||
state = dialog,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { dialog.value?.let(onConfirm) ?: onDismissRequest },
|
||||
) { state ->
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val end = remember(layoutDirection, paddings) { paddings.calculateEndPadding(layoutDirection) }
|
||||
val top = remember(paddings) { paddings.calculateTopPadding() }
|
||||
|
||||
takeIf { state.image?.isNotEmpty() == true }?.let {
|
||||
DesaturatedAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(64.dp * 2)
|
||||
.align(alignment = Alignment.TopEnd),
|
||||
colorFilter = rememberSaturationFilter(),
|
||||
model = state.image,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.TopCenter,
|
||||
filterQuality = FilterQuality.High,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues = paddings),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacings)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
style = MaterialTheme.typography.h5,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = state.label,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ItemDetailDialogContent(
|
||||
modifier: Modifier = Modifier,
|
||||
paddings: PaddingValues,
|
||||
dialog: ItemDetailDialogUio,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val end = remember(layoutDirection, paddings) { paddings.calculateEndPadding(layoutDirection) }
|
||||
val top = remember(paddings) { paddings.calculateTopPadding() }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
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)
|
||||
IconButton(
|
||||
modifier = Modifier.offset(x = end, y = -top),
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
takeIf { dialog.image?.isNotEmpty() == true }?.let {
|
||||
DesaturatedAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(64.dp * 2)
|
||||
.align(alignment = Alignment.TopEnd),
|
||||
colorFilter = rememberSaturationFilter(),
|
||||
model = dialog.image,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.TopCenter,
|
||||
filterQuality = FilterQuality.High,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_close_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
text = state.description,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = state.inventoryId,
|
||||
) {
|
||||
when (it) {
|
||||
null -> Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
TextButton(
|
||||
onClick = { onAddItem(state) },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
style = MaterialTheme.typography.h5,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = dialog.label,
|
||||
text = stringResource(Res.string.character__inventory__inventory__dialog__action)
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.offset(x = end, y = -top),
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_close_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
}
|
||||
}
|
||||
|
||||
else -> Column(
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacings)
|
||||
) {
|
||||
if (state.countable != null) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = spacings,
|
||||
Alignment.End,
|
||||
)
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(width = 128.dp),
|
||||
colors = LwaTextFieldColors(
|
||||
backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base2dp,
|
||||
),
|
||||
field = state.countable,
|
||||
)
|
||||
TextButton(
|
||||
modifier = Modifier
|
||||
.height(height = 56.dp)
|
||||
.background(
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base2dp,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
),
|
||||
enabled = state.countable.isError.collectAsState().value.not(),
|
||||
onClick = { onConfirm(state) }
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.character__inventory__inventory__dialog__count_action),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
text = dialog.description,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,31 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.character.item
|
||||
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||
import com.pixelized.shared.lwa.model.item.Item
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__description_empty__label
|
||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__count
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import java.text.DecimalFormat
|
||||
|
||||
class ItemDetailDialogFactory {
|
||||
|
||||
private val floatChecker = Regex("""^\d*[.,]?\d*${'$'}""")
|
||||
private val format = DecimalFormat("#.##")
|
||||
|
||||
suspend fun convertToDialogUio(
|
||||
characterSheetId: String?,
|
||||
items: Map<String, Item>,
|
||||
count: Float,
|
||||
inventoryId: String?,
|
||||
itemId: String?,
|
||||
): ItemDetailDialogUio? {
|
||||
if (characterSheetId == null) return null
|
||||
val item = itemId.let(items::get) ?: return null
|
||||
|
||||
return ItemDetailDialogUio(
|
||||
characterSheetId = characterSheetId,
|
||||
inventoryId = inventoryId,
|
||||
itemId = item.id,
|
||||
label = item.metadata.label,
|
||||
|
|
@ -22,10 +33,42 @@ class ItemDetailDialogFactory {
|
|||
getString(Res.string.character__inventory__description_empty__label)
|
||||
},
|
||||
image = item.metadata.image,
|
||||
option = ItemDetailDialogUio.OptionUio(
|
||||
equipable = item.options.equipable,
|
||||
consumable = item.options.consumable,
|
||||
),
|
||||
count = count,
|
||||
countable = takeIf { item.options.stackable }
|
||||
?.let { createFieldFlow(value = format.format(count)) }
|
||||
?.createTextField(label = getString(Res.string.character__inventory__inventory__dialog__count)),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFieldFlow(
|
||||
value: String = "",
|
||||
error: Boolean = false,
|
||||
): Pair<MutableStateFlow<String>, MutableStateFlow<Boolean>> {
|
||||
return MutableStateFlow(value) to MutableStateFlow(error)
|
||||
}
|
||||
|
||||
fun parse(
|
||||
quantity: String,
|
||||
): Float? = try {
|
||||
format.parse(quantity).toFloat()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
private fun isError(value: String): Boolean = floatChecker.matches(value).not()
|
||||
|
||||
private fun Pair<MutableStateFlow<String>, MutableStateFlow<Boolean>>.createTextField(
|
||||
enable: Boolean = true,
|
||||
label: String,
|
||||
) = LwaTextFieldUio(
|
||||
enable = enable,
|
||||
isError = second,
|
||||
valueFlow = first,
|
||||
label = label,
|
||||
placeHolder = null,
|
||||
onValueChange = {
|
||||
second.value = isError(value = it)
|
||||
first.value = it
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -2,24 +2,56 @@ package com.pixelized.desktop.lwa.ui.composable.character.item
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.item.ItemRepository
|
||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio
|
||||
import com.pixelized.shared.lwa.model.inventory.Inventory
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class ItemDetailDialogViewModel(
|
||||
private val inventoryRepository: InventoryRepository,
|
||||
itemRepository: ItemRepository,
|
||||
factory: ItemDetailDialogFactory,
|
||||
private val factory: ItemDetailDialogFactory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _error = MutableSharedFlow<ErrorSnackUio>()
|
||||
val error: SharedFlow<ErrorSnackUio> = _error
|
||||
|
||||
private val selectedItemId = MutableStateFlow<InventoryItemId?>(null)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val selectedInventoryItem: Flow<Inventory.Item?> = selectedItemId
|
||||
.flatMapLatest { selectedIds ->
|
||||
when (selectedIds?.inventoryId) {
|
||||
null -> flowOf(null)
|
||||
else -> inventoryRepository
|
||||
.inventoryFlow(characterSheetId = selectedIds.characterSheetId)
|
||||
.mapNotNull { inventory ->
|
||||
inventory.items.firstOrNull { it.inventoryId == selectedIds.inventoryId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val itemDialog = combine(
|
||||
itemRepository.itemFlow(), selectedItemId,
|
||||
transform = { items, ids ->
|
||||
itemRepository.itemFlow(),
|
||||
selectedInventoryItem,
|
||||
selectedItemId,
|
||||
transform = { items, selectedInventoryItem, ids ->
|
||||
factory.convertToDialogUio(
|
||||
characterSheetId = ids?.characterSheetId,
|
||||
items = items,
|
||||
count = selectedInventoryItem?.count ?: 0f,
|
||||
inventoryId = ids?.inventoryId,
|
||||
itemId = ids?.itemId,
|
||||
)
|
||||
|
|
@ -30,9 +62,14 @@ class ItemDetailDialogViewModel(
|
|||
initialValue = null
|
||||
)
|
||||
|
||||
fun showItemDialog(inventoryId: String?, itemId: String?) {
|
||||
fun showItemDialog(
|
||||
characterSheetId: String,
|
||||
inventoryId: String?,
|
||||
itemId: String?,
|
||||
) {
|
||||
selectedItemId.update {
|
||||
InventoryItemId(
|
||||
characterSheetId = characterSheetId,
|
||||
inventoryId = inventoryId,
|
||||
itemId = itemId,
|
||||
)
|
||||
|
|
@ -43,7 +80,63 @@ class ItemDetailDialogViewModel(
|
|||
selectedItemId.update { null }
|
||||
}
|
||||
|
||||
suspend fun onAddInventoryItem(
|
||||
characterSheetId: String,
|
||||
itemId: String,
|
||||
) {
|
||||
try {
|
||||
// create the inventory item on the server, get the newly create id from that.
|
||||
val inventoryId = inventoryRepository.createInventoryItem(
|
||||
characterSheetId = characterSheetId,
|
||||
itemId = itemId,
|
||||
)
|
||||
// update the dialog with the id only if this dialog still correspond to this item. (should always be the case but hey).
|
||||
if (selectedItemId.value?.let { it.itemId == itemId && it.characterSheetId == characterSheetId } == true) {
|
||||
selectedItemId.update {
|
||||
it?.copy(inventoryId = inventoryId)
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception)
|
||||
_error.emit(message)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun changeInventoryItemQuantity(
|
||||
dialog: ItemDetailDialogUio
|
||||
) : Boolean {
|
||||
if (dialog.countable?.isError?.value == true) return false
|
||||
|
||||
val characterSheetId = dialog.characterSheetId
|
||||
val inventoryId = dialog.inventoryId ?: return false
|
||||
val quantity = dialog.countable?.valueFlow?.value ?: return false
|
||||
|
||||
val count = factory.parse(quantity = quantity) ?: return false
|
||||
|
||||
val inventory = inventoryRepository.inventory(characterSheetId = characterSheetId)
|
||||
|
||||
try {
|
||||
inventoryRepository.updateInventory(
|
||||
inventory = inventory.copy(
|
||||
items = inventory.items.toMutableList().also { items ->
|
||||
val index = items.indexOfFirst { item -> item.inventoryId == inventoryId }
|
||||
items[index] = items[index].copy(
|
||||
count = count,
|
||||
)
|
||||
}
|
||||
),
|
||||
create = false,
|
||||
)
|
||||
return true
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception)
|
||||
_error.emit(message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private data class InventoryItemId(
|
||||
val characterSheetId: String,
|
||||
val inventoryId: String?,
|
||||
val itemId: String?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -39,8 +40,11 @@ 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.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
|
|
@ -66,27 +70,103 @@ data class PurseDialogUio(
|
|||
val enableConfirm: StateFlow<Boolean>,
|
||||
)
|
||||
|
||||
@Stable
|
||||
object PurseDialogDefault {
|
||||
@Stable
|
||||
val paddings = PaddingValues(top = 16.dp, start = 8.dp, end = 8.dp, bottom = 8.dp)
|
||||
|
||||
@Stable
|
||||
val spacings = DpSize(width = 4.dp, height = 8.dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PurseDialog(
|
||||
dialog: State<PurseDialogUio?>,
|
||||
paddings: PaddingValues = PurseDialogDefault.paddings,
|
||||
spacings: DpSize = PurseDialogDefault.spacings,
|
||||
onConfirm: (PurseDialogUio) -> Unit,
|
||||
onSwapSign: (PurseDialogUio) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = {
|
||||
PurseContent(
|
||||
dialog = it,
|
||||
onConfirm = onConfirm,
|
||||
onSwapSign = onSwapSign,
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
PurseDialogKeyHandler(
|
||||
onSwap = { onSwapSign(it) },
|
||||
LwaDialog(
|
||||
state = dialog,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { dialog.value?.let(onConfirm) }
|
||||
) { state ->
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues = paddings),
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacings.height),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AnimatedContent(
|
||||
modifier = Modifier,
|
||||
targetState = state.label.collectAsState().value,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = spacings.width),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
SignButton(
|
||||
modifier = Modifier
|
||||
.size(size = 56.dp)
|
||||
.background(
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
),
|
||||
add = state.add,
|
||||
onClick = { onSwapSign(state) },
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.focusRequester(focusRequester = focusRequester)
|
||||
.width(100.dp),
|
||||
field = state.gold,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(100.dp),
|
||||
field = state.silver,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(100.dp),
|
||||
field = state.copper,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.align(alignment = Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = spacings.width,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Text(
|
||||
color = MaterialTheme.colors.primary.copy(alpha = .7f),
|
||||
text = stringResource(Res.string.dialog__cancel_action)
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
enabled = state.enableConfirm.collectAsState().value,
|
||||
onClick = { onConfirm(state) },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.dialog__confirm_action)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PurseDialogKeyHandler(
|
||||
onSwap = { onSwapSign(state) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -99,8 +179,7 @@ private fun PurseContent(
|
|||
onSwapSign: (PurseDialogUio) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -119,76 +198,7 @@ private fun PurseContent(
|
|||
) {
|
||||
DecoratedBox {
|
||||
Surface {
|
||||
Column(
|
||||
modifier = modifier.padding(horizontal = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AnimatedContent(
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
|
||||
targetState = dialog.label.collectAsState().value,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
SignButton(
|
||||
modifier = Modifier
|
||||
.size(size = 56.dp)
|
||||
.background(
|
||||
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
),
|
||||
add = dialog.add,
|
||||
onClick = { onSwapSign(dialog) },
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.focusRequester(focusRequester = focusRequester)
|
||||
.width(100.dp),
|
||||
field = dialog.gold,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(100.dp),
|
||||
field = dialog.silver,
|
||||
)
|
||||
LwaTextField(
|
||||
modifier = Modifier.width(100.dp),
|
||||
field = dialog.copper,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 4.dp)
|
||||
.align(alignment = Alignment.End),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 4.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onDismissRequest,
|
||||
) {
|
||||
Text(
|
||||
color = MaterialTheme.colors.primary.copy(alpha = .7f),
|
||||
text = stringResource(Res.string.dialog__cancel_action)
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
enabled = dialog.enableConfirm.collectAsState().value,
|
||||
onClick = { onConfirm(dialog) },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(Res.string.dialog__confirm_action)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ class PurseDialogViewModel(
|
|||
return false
|
||||
}
|
||||
// Get the player inventory
|
||||
val inventory = inventoryRepository
|
||||
.inventory(characterSheetId = dialog.characterSheetId)
|
||||
?: return false
|
||||
val inventory = inventoryRepository.inventory(characterSheetId = dialog.characterSheetId)
|
||||
// compute the new purse
|
||||
val sign = if (dialog.add.value) 1 else -1
|
||||
val goldValue = dialog.gold.valueFlow.value.toIntOrNull() ?: 0
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.TooltipPlacement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -27,6 +28,7 @@ fun <T> TooltipLayout2(
|
|||
tooltip = {
|
||||
Box(
|
||||
modifier = Modifier.width(width = 448.dp),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
content = { tooltip(tips) },
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.rememberDrawScopeSizeResolver
|
||||
import com.pixelized.desktop.lwa.LocalBlurController
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialog
|
||||
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogViewModel
|
||||
|
|
@ -104,11 +105,12 @@ fun CharacterDetailInventory(
|
|||
characterSheetId = it,
|
||||
)
|
||||
},
|
||||
onItem = {
|
||||
onItem = { item ->
|
||||
blur.show()
|
||||
itemDetailDialogViewModel.showItemDialog(
|
||||
inventoryId = it.inventoryId,
|
||||
itemId = it.itemId,
|
||||
characterSheetId = item.characterSheetId,
|
||||
inventoryId = item.inventoryId,
|
||||
itemId = item.itemId,
|
||||
)
|
||||
},
|
||||
onAddItem = {
|
||||
|
|
@ -145,9 +147,10 @@ fun CharacterDetailInventory(
|
|||
blur.hide()
|
||||
inventoryDialogViewModel.hideInventoryDialog()
|
||||
},
|
||||
onItem = { itemId ->
|
||||
onItem = { dialog, itemId ->
|
||||
blur.show()
|
||||
itemDetailDialogViewModel.showItemDialog(
|
||||
characterSheetId = dialog.characterSheetId,
|
||||
inventoryId = null,
|
||||
itemId = itemId,
|
||||
)
|
||||
|
|
@ -159,6 +162,25 @@ fun CharacterDetailInventory(
|
|||
onDismissRequest = {
|
||||
blur.hide()
|
||||
itemDetailDialogViewModel.hideItemDialog()
|
||||
},
|
||||
onAddItem = { dialog ->
|
||||
scope.launch {
|
||||
itemDetailDialogViewModel.onAddInventoryItem(
|
||||
characterSheetId = dialog.characterSheetId,
|
||||
itemId = dialog.itemId,
|
||||
)
|
||||
}
|
||||
},
|
||||
onConfirm = { dialog ->
|
||||
scope.launch {
|
||||
val result = itemDetailDialogViewModel.changeInventoryItemQuantity(
|
||||
dialog = dialog,
|
||||
)
|
||||
if (result) {
|
||||
blur.hide()
|
||||
itemDetailDialogViewModel.hideItemDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -232,9 +254,7 @@ private fun CharacterDetailInventoryContent(
|
|||
key = { it.inventoryId },
|
||||
) {
|
||||
InventoryItem(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth(),
|
||||
modifier = Modifier.animateItem(),
|
||||
item = it,
|
||||
onClick = { onItem(it) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ class CharacterDetailInventoryFactory(
|
|||
?.mapNotNull {
|
||||
val item = items[it.itemId] ?: return@mapNotNull null
|
||||
InventoryItemUio(
|
||||
characterSheetId = characterSheetId,
|
||||
inventoryId = it.inventoryId,
|
||||
itemId = it.itemId,
|
||||
label = item.metadata.label,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -40,6 +41,7 @@ import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
|||
|
||||
@Stable
|
||||
data class InventoryItemUio(
|
||||
val characterSheetId: String,
|
||||
val inventoryId: String,
|
||||
val itemId: String,
|
||||
val label: String,
|
||||
|
|
@ -74,6 +76,7 @@ fun InventoryItem(
|
|||
onClick: () -> Unit,
|
||||
) {
|
||||
TooltipLayout2(
|
||||
modifier = modifier,
|
||||
delayMillis = 500,
|
||||
tips = item.tooltips,
|
||||
tooltip = { tooltips ->
|
||||
|
|
@ -97,7 +100,7 @@ fun InventoryItem(
|
|||
)
|
||||
}
|
||||
Column(
|
||||
modifier = modifier,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -128,6 +131,7 @@ fun InventoryItem(
|
|||
}
|
||||
)
|
||||
.minimumInteractiveComponentSize()
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue