Add Addable InventoryItem feature & quantity change.
This commit is contained in:
parent
48074f3d13
commit
9fce3f1cb8
28 changed files with 785 additions and 343 deletions
|
|
@ -190,6 +190,9 @@
|
||||||
<string name="character__inventory__remove_from_purse__title">Retirer de la bourse</string>
|
<string name="character__inventory__remove_from_purse__title">Retirer de la bourse</string>
|
||||||
<string name="character__inventory__filter_item_inventory__label">Filtrer les objets</string>
|
<string name="character__inventory__filter_item_inventory__label">Filtrer les objets</string>
|
||||||
<string name="character__inventory__inventory__dialog__title">Ajouter à l'inventaire</string>
|
<string name="character__inventory__inventory__dialog__title">Ajouter à l'inventaire</string>
|
||||||
|
<string name="character__inventory__inventory__dialog__action">Ajouter à l'inventaire</string>
|
||||||
|
<string name="character__inventory__inventory__dialog__count">Quantité</string>
|
||||||
|
<string name="character__inventory__inventory__dialog__count_action">Modifier</string>
|
||||||
<string name="character__inventory__description_empty__label">Cet objet n'a pas de description.</string>
|
<string name="character__inventory__description_empty__label">Cet objet n'a pas de description.</string>
|
||||||
|
|
||||||
<string name="tooltip__characteristics__characteristics">Les caractéristiques constituent les aptitudes innées d’un personnage comme son intelligence, sa force, son charisme, etc. Elles ne sont pas acquises, mais peuvent être parfois augmentées par un entraînement ou une utilisation réussie. Les caractéristiques des humains normaux varient de 2 (niveau extrêmement bas) à 20 (maximum du potentiel humain), avec une moyenne de 10 ou 11. Plus une caractéristique est élevée plus le personnage est puissant dans cette aptitude.\nÀ la création de votre personnage, répartissez les valeurs suivantes dans les différentes caractéristiques : 15, 15, 13, 11, 10, 9 et 7.</string>
|
<string name="tooltip__characteristics__characteristics">Les caractéristiques constituent les aptitudes innées d’un personnage comme son intelligence, sa force, son charisme, etc. Elles ne sont pas acquises, mais peuvent être parfois augmentées par un entraînement ou une utilisation réussie. Les caractéristiques des humains normaux varient de 2 (niveau extrêmement bas) à 20 (maximum du potentiel humain), avec une moyenne de 10 ou 11. Plus une caractéristique est élevée plus le personnage est puissant dans cette aptitude.\nÀ la création de votre personnage, répartissez les valeurs suivantes dans les différentes caractéristiques : 15, 15, 13, 11, 10, 9 et 7.</string>
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,15 @@ interface LwaClient {
|
||||||
): APIResponse<InventoryJson>
|
): APIResponse<InventoryJson>
|
||||||
|
|
||||||
suspend fun putInventory(
|
suspend fun putInventory(
|
||||||
json: InventoryJson,
|
inventory: InventoryJson,
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
): APIResponse<Unit>
|
): APIResponse<Unit>
|
||||||
|
|
||||||
|
suspend fun createInventoryItem(
|
||||||
|
characterSheetId: String,
|
||||||
|
itemId: String,
|
||||||
|
): APIResponse<String>
|
||||||
|
|
||||||
suspend fun deleteInventory(
|
suspend fun deleteInventory(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
): APIResponse<Unit>
|
): APIResponse<Unit>
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,14 @@ class LwaClientImpl(
|
||||||
}
|
}
|
||||||
.body<APIResponse<Unit>>()
|
.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
|
@Throws
|
||||||
override suspend fun deleteInventory(characterSheetId: String): APIResponse<Unit> = client
|
override suspend fun deleteInventory(characterSheetId: String): APIResponse<Unit> = client
|
||||||
.delete("$root/inventory/delete?characterSheetId=$characterSheetId")
|
.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 com.pixelized.shared.lwa.model.inventory.Inventory
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class InventoryRepository(
|
class InventoryRepository(
|
||||||
private val inventoryStore: InventoryStore,
|
private val inventoryStore: InventoryStore,
|
||||||
) {
|
) {
|
||||||
val inventoryFlow get() = inventoryStore.inventories
|
|
||||||
|
|
||||||
suspend fun updateInventoryFlow(characterSheetId: String) {
|
suspend fun updateInventoryFlow(characterSheetId: String) {
|
||||||
inventoryStore.updateInventoryFlow(characterSheetId = characterSheetId)
|
inventoryStore.updateInventoryFlow(characterSheetId = characterSheetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun inventoryFlow(): StateFlow<Map<String, Inventory>> {
|
||||||
|
return inventoryStore.inventories
|
||||||
|
}
|
||||||
|
|
||||||
fun inventory(
|
fun inventory(
|
||||||
characterSheetId: String?,
|
characterSheetId: String,
|
||||||
): Inventory? {
|
): Inventory {
|
||||||
return inventoryFlow.value[characterSheetId]
|
return inventoryStore.inventories.value[characterSheetId]
|
||||||
|
?: Inventory.empty(characterSheetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inventoryFlow(
|
fun inventoryFlow(
|
||||||
characterSheetId: String?,
|
characterSheetId: String,
|
||||||
): Flow<Inventory?> {
|
): Flow<Inventory> {
|
||||||
return inventoryFlow.map { it[characterSheetId] }
|
return inventoryStore.inventories
|
||||||
|
.map { it[characterSheetId] ?: Inventory.empty(characterSheetId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
|
|
@ -36,6 +41,17 @@ class InventoryRepository(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
suspend fun createInventoryItem(
|
||||||
|
characterSheetId: String,
|
||||||
|
itemId: String,
|
||||||
|
): String {
|
||||||
|
return inventoryStore.createInventoryItem(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
itemId = itemId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws
|
@Throws
|
||||||
suspend fun deleteItem(
|
suspend fun deleteItem(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class InventoryStore(
|
||||||
create: Boolean,
|
create: Boolean,
|
||||||
) {
|
) {
|
||||||
val request = client.putInventory(
|
val request = client.putInventory(
|
||||||
json = factory.convertToJson(inventory = inventory),
|
inventory = factory.convertToJson(inventory = inventory),
|
||||||
create = create,
|
create = create,
|
||||||
)
|
)
|
||||||
if (request.success.not()) {
|
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
|
@Throws
|
||||||
suspend fun deleteInventory(
|
suspend fun deleteInventory(
|
||||||
characterSheetId: String,
|
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.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Row
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.decoratedBox.DecoratedBox
|
||||||
import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage
|
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.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.composable.tooltip.TooltipLayout2
|
||||||
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
|
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
|
|
||||||
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__title
|
import lwacharactersheet.composeapp.generated.resources.character__inventory__inventory__dialog__title
|
||||||
|
|
@ -103,31 +100,33 @@ object InventoryDialogItemDefault {
|
||||||
fun InventoryDialog(
|
fun InventoryDialog(
|
||||||
dialog: State<InventoryDialogUio?>,
|
dialog: State<InventoryDialogUio?>,
|
||||||
paddings: PaddingValues = InventoryDialogDefault.paddings,
|
paddings: PaddingValues = InventoryDialogDefault.paddings,
|
||||||
|
spacing: Dp = InventoryDialogDefault.spacings,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onItem: (String) -> Unit,
|
onItem: (InventoryDialogUio, String) -> Unit,
|
||||||
) {
|
) {
|
||||||
dialog.value?.let {
|
LwaDialog(
|
||||||
Dialog(
|
state = dialog,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
content = {
|
onConfirm = onDismissRequest,
|
||||||
|
) {
|
||||||
InventoryDialogContent(
|
InventoryDialogContent(
|
||||||
dialog = it,
|
dialog = it,
|
||||||
paddings = paddings,
|
paddings = paddings,
|
||||||
|
spacing = spacing,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onItem = onItem,
|
onItem = onItem,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InventoryDialogContent(
|
private fun InventoryDialogContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
dialog: InventoryDialogUio,
|
dialog: InventoryDialogUio,
|
||||||
paddings: PaddingValues = InventoryDialogDefault.paddings,
|
paddings: PaddingValues = InventoryDialogDefault.paddings,
|
||||||
spacing: Dp = InventoryDialogDefault.spacings,
|
spacing: Dp = InventoryDialogDefault.spacings,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onItem: (String) -> Unit,
|
onItem: (InventoryDialogUio, String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
val start = remember(layoutDirection) { paddings.calculateStartPadding(layoutDirection) }
|
val start = remember(layoutDirection) { paddings.calculateStartPadding(layoutDirection) }
|
||||||
|
|
@ -135,24 +134,9 @@ private fun InventoryDialogContent(
|
||||||
val top = remember(layoutDirection) { paddings.calculateTopPadding() }
|
val top = remember(layoutDirection) { paddings.calculateTopPadding() }
|
||||||
val bottom = remember(layoutDirection) { paddings.calculateBottomPadding() }
|
val bottom = remember(layoutDirection) { paddings.calculateBottomPadding() }
|
||||||
|
|
||||||
Box(
|
Column(
|
||||||
modifier = Modifier
|
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 {
|
|
||||||
Column {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -221,14 +205,11 @@ private fun InventoryDialogContent(
|
||||||
InventoryDialogItem(
|
InventoryDialogItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
item = item,
|
item = item,
|
||||||
onItem = onItem,
|
onItem = { onItem(dialog, it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
package com.pixelized.desktop.lwa.ui.composable.character.item
|
package com.pixelized.desktop.lwa.ui.composable.character.item
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -29,106 +31,80 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
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.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.ui.theme.lwa
|
||||||
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
|
|
||||||
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
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 lwacharactersheet.composeapp.generated.resources.ic_close_24dp
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class ItemDetailDialogUio(
|
data class ItemDetailDialogUio(
|
||||||
|
val characterSheetId: String,
|
||||||
val inventoryId: String?,
|
val inventoryId: String?,
|
||||||
val itemId: String,
|
val itemId: String,
|
||||||
|
// meta
|
||||||
val label: String,
|
val label: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val image: String?,
|
val image: String?,
|
||||||
val option: OptionUio,
|
val count: Float,
|
||||||
) {
|
// options
|
||||||
@Stable
|
val countable: LwaTextFieldUio?,
|
||||||
data class OptionUio(
|
)
|
||||||
val equipable: Boolean,
|
|
||||||
val consumable: Boolean,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
object ItemDetailDialogDefault {
|
object ItemDetailDialogDefault {
|
||||||
@Stable
|
@Stable
|
||||||
val paddings = PaddingValues(all = 16.dp)
|
val paddings = PaddingValues(all = 16.dp)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
val spacings = 8.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ItemDetailDialog(
|
fun ItemDetailDialog(
|
||||||
dialog: State<ItemDetailDialogUio?>,
|
dialog: State<ItemDetailDialogUio?>,
|
||||||
paddings: PaddingValues = ItemDetailDialogDefault.paddings,
|
paddings: PaddingValues = ItemDetailDialogDefault.paddings,
|
||||||
|
spacings: Dp = ItemDetailDialogDefault.spacings,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
|
onConfirm: (ItemDetailDialogUio) -> Unit,
|
||||||
|
onAddItem: (ItemDetailDialogUio) -> Unit,
|
||||||
) {
|
) {
|
||||||
dialog.value?.let {
|
LwaDialog(
|
||||||
Dialog(
|
state = dialog,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
content = {
|
onConfirm = { dialog.value?.let(onConfirm) ?: onDismissRequest },
|
||||||
ItemDetailDialogContent(
|
) { state ->
|
||||||
dialog = it,
|
|
||||||
paddings = paddings,
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ItemDetailDialogContent(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
paddings: PaddingValues,
|
|
||||||
dialog: ItemDetailDialogUio,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
) {
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
val end = remember(layoutDirection, paddings) { paddings.calculateEndPadding(layoutDirection) }
|
val end = remember(layoutDirection, paddings) { paddings.calculateEndPadding(layoutDirection) }
|
||||||
val top = remember(paddings) { paddings.calculateTopPadding() }
|
val top = remember(paddings) { paddings.calculateTopPadding() }
|
||||||
|
|
||||||
Box(
|
takeIf { state.image?.isNotEmpty() == true }?.let {
|
||||||
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)
|
|
||||||
) {
|
|
||||||
takeIf { dialog.image?.isNotEmpty() == true }?.let {
|
|
||||||
DesaturatedAsyncImage(
|
DesaturatedAsyncImage(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(64.dp * 2)
|
.size(64.dp * 2)
|
||||||
.align(alignment = Alignment.TopEnd),
|
.align(alignment = Alignment.TopEnd),
|
||||||
colorFilter = rememberSaturationFilter(),
|
colorFilter = rememberSaturationFilter(),
|
||||||
model = dialog.image,
|
model = state.image,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
alignment = Alignment.TopCenter,
|
alignment = Alignment.TopCenter,
|
||||||
filterQuality = FilterQuality.High,
|
filterQuality = FilterQuality.High,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = Modifier.padding(paddingValues = paddings),
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
verticalArrangement = Arrangement.spacedBy(space = spacings)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -141,7 +117,7 @@ private fun ItemDetailDialogContent(
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
text = dialog.label,
|
text = state.label,
|
||||||
)
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.offset(x = end, y = -top),
|
modifier = Modifier.offset(x = end, y = -top),
|
||||||
|
|
@ -154,13 +130,68 @@ private fun ItemDetailDialogContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
style = MaterialTheme.typography.body1,
|
style = MaterialTheme.typography.body1,
|
||||||
text = dialog.description,
|
text = state.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier)
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = state.inventoryId,
|
||||||
|
) {
|
||||||
|
when (it) {
|
||||||
|
null -> Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onAddItem(state) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.character__inventory__inventory__dialog__action)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,31 @@
|
||||||
package com.pixelized.desktop.lwa.ui.composable.character.item
|
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 com.pixelized.shared.lwa.model.item.Item
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.character__inventory__description_empty__label
|
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 org.jetbrains.compose.resources.getString
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
class ItemDetailDialogFactory {
|
class ItemDetailDialogFactory {
|
||||||
|
|
||||||
|
private val floatChecker = Regex("""^\d*[.,]?\d*${'$'}""")
|
||||||
|
private val format = DecimalFormat("#.##")
|
||||||
|
|
||||||
suspend fun convertToDialogUio(
|
suspend fun convertToDialogUio(
|
||||||
|
characterSheetId: String?,
|
||||||
items: Map<String, Item>,
|
items: Map<String, Item>,
|
||||||
|
count: Float,
|
||||||
inventoryId: String?,
|
inventoryId: String?,
|
||||||
itemId: String?,
|
itemId: String?,
|
||||||
): ItemDetailDialogUio? {
|
): ItemDetailDialogUio? {
|
||||||
|
if (characterSheetId == null) return null
|
||||||
val item = itemId.let(items::get) ?: return null
|
val item = itemId.let(items::get) ?: return null
|
||||||
|
|
||||||
return ItemDetailDialogUio(
|
return ItemDetailDialogUio(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
inventoryId = inventoryId,
|
inventoryId = inventoryId,
|
||||||
itemId = item.id,
|
itemId = item.id,
|
||||||
label = item.metadata.label,
|
label = item.metadata.label,
|
||||||
|
|
@ -22,10 +33,42 @@ class ItemDetailDialogFactory {
|
||||||
getString(Res.string.character__inventory__description_empty__label)
|
getString(Res.string.character__inventory__description_empty__label)
|
||||||
},
|
},
|
||||||
image = item.metadata.image,
|
image = item.metadata.image,
|
||||||
option = ItemDetailDialogUio.OptionUio(
|
count = count,
|
||||||
equipable = item.options.equipable,
|
countable = takeIf { item.options.stackable }
|
||||||
consumable = item.options.consumable,
|
?.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.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.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.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
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.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
class ItemDetailDialogViewModel(
|
class ItemDetailDialogViewModel(
|
||||||
|
private val inventoryRepository: InventoryRepository,
|
||||||
itemRepository: ItemRepository,
|
itemRepository: ItemRepository,
|
||||||
factory: ItemDetailDialogFactory,
|
private val factory: ItemDetailDialogFactory,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _error = MutableSharedFlow<ErrorSnackUio>()
|
||||||
|
val error: SharedFlow<ErrorSnackUio> = _error
|
||||||
|
|
||||||
private val selectedItemId = MutableStateFlow<InventoryItemId?>(null)
|
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(
|
val itemDialog = combine(
|
||||||
itemRepository.itemFlow(), selectedItemId,
|
itemRepository.itemFlow(),
|
||||||
transform = { items, ids ->
|
selectedInventoryItem,
|
||||||
|
selectedItemId,
|
||||||
|
transform = { items, selectedInventoryItem, ids ->
|
||||||
factory.convertToDialogUio(
|
factory.convertToDialogUio(
|
||||||
|
characterSheetId = ids?.characterSheetId,
|
||||||
items = items,
|
items = items,
|
||||||
|
count = selectedInventoryItem?.count ?: 0f,
|
||||||
inventoryId = ids?.inventoryId,
|
inventoryId = ids?.inventoryId,
|
||||||
itemId = ids?.itemId,
|
itemId = ids?.itemId,
|
||||||
)
|
)
|
||||||
|
|
@ -30,9 +62,14 @@ class ItemDetailDialogViewModel(
|
||||||
initialValue = null
|
initialValue = null
|
||||||
)
|
)
|
||||||
|
|
||||||
fun showItemDialog(inventoryId: String?, itemId: String?) {
|
fun showItemDialog(
|
||||||
|
characterSheetId: String,
|
||||||
|
inventoryId: String?,
|
||||||
|
itemId: String?,
|
||||||
|
) {
|
||||||
selectedItemId.update {
|
selectedItemId.update {
|
||||||
InventoryItemId(
|
InventoryItemId(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
inventoryId = inventoryId,
|
inventoryId = inventoryId,
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
)
|
)
|
||||||
|
|
@ -43,7 +80,63 @@ class ItemDetailDialogViewModel(
|
||||||
selectedItemId.update { null }
|
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(
|
private data class InventoryItemId(
|
||||||
|
val characterSheetId: String,
|
||||||
val inventoryId: String?,
|
val inventoryId: String?,
|
||||||
val itemId: 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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
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.KeyEventType
|
||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
import androidx.compose.ui.input.key.type
|
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.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
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.decoratedBox.DecoratedBox
|
||||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||||
|
|
@ -66,27 +70,103 @@ data class PurseDialogUio(
|
||||||
val enableConfirm: StateFlow<Boolean>,
|
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
|
@Composable
|
||||||
fun PurseDialog(
|
fun PurseDialog(
|
||||||
dialog: State<PurseDialogUio?>,
|
dialog: State<PurseDialogUio?>,
|
||||||
|
paddings: PaddingValues = PurseDialogDefault.paddings,
|
||||||
|
spacings: DpSize = PurseDialogDefault.spacings,
|
||||||
onConfirm: (PurseDialogUio) -> Unit,
|
onConfirm: (PurseDialogUio) -> Unit,
|
||||||
onSwapSign: (PurseDialogUio) -> Unit,
|
onSwapSign: (PurseDialogUio) -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
dialog.value?.let {
|
LwaDialog(
|
||||||
Dialog(
|
state = dialog,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
content = {
|
onConfirm = { dialog.value?.let(onConfirm) }
|
||||||
PurseContent(
|
) { state ->
|
||||||
dialog = it,
|
val focusRequester = remember { FocusRequester() }
|
||||||
onConfirm = onConfirm,
|
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
||||||
onSwapSign = onSwapSign,
|
|
||||||
onDismissRequest = onDismissRequest,
|
Column(
|
||||||
)
|
modifier = Modifier.padding(paddingValues = paddings),
|
||||||
PurseDialogKeyHandler(
|
verticalArrangement = Arrangement.spacedBy(space = spacings.height),
|
||||||
onSwap = { onSwapSign(it) },
|
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,
|
onSwapSign: (PurseDialogUio) -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
LaunchedEffect(Unit) { focusRequester.requestFocus() }
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -119,76 +198,7 @@ private fun PurseContent(
|
||||||
) {
|
) {
|
||||||
DecoratedBox {
|
DecoratedBox {
|
||||||
Surface {
|
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
|
return false
|
||||||
}
|
}
|
||||||
// Get the player inventory
|
// Get the player inventory
|
||||||
val inventory = inventoryRepository
|
val inventory = inventoryRepository.inventory(characterSheetId = dialog.characterSheetId)
|
||||||
.inventory(characterSheetId = dialog.characterSheetId)
|
|
||||||
?: return false
|
|
||||||
// compute the new purse
|
// compute the new purse
|
||||||
val sign = if (dialog.add.value) 1 else -1
|
val sign = if (dialog.add.value) 1 else -1
|
||||||
val goldValue = dialog.gold.valueFlow.value.toIntOrNull() ?: 0
|
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.Box
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -27,6 +28,7 @@ fun <T> TooltipLayout2(
|
||||||
tooltip = {
|
tooltip = {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.width(width = 448.dp),
|
modifier = Modifier.width(width = 448.dp),
|
||||||
|
contentAlignment = Alignment.TopEnd,
|
||||||
content = { tooltip(tips) },
|
content = { tooltip(tips) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.LocalBlurController
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialog
|
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialog
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogViewModel
|
import com.pixelized.desktop.lwa.ui.composable.character.inventory.InventoryDialogViewModel
|
||||||
|
|
@ -104,11 +105,12 @@ fun CharacterDetailInventory(
|
||||||
characterSheetId = it,
|
characterSheetId = it,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onItem = {
|
onItem = { item ->
|
||||||
blur.show()
|
blur.show()
|
||||||
itemDetailDialogViewModel.showItemDialog(
|
itemDetailDialogViewModel.showItemDialog(
|
||||||
inventoryId = it.inventoryId,
|
characterSheetId = item.characterSheetId,
|
||||||
itemId = it.itemId,
|
inventoryId = item.inventoryId,
|
||||||
|
itemId = item.itemId,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onAddItem = {
|
onAddItem = {
|
||||||
|
|
@ -145,9 +147,10 @@ fun CharacterDetailInventory(
|
||||||
blur.hide()
|
blur.hide()
|
||||||
inventoryDialogViewModel.hideInventoryDialog()
|
inventoryDialogViewModel.hideInventoryDialog()
|
||||||
},
|
},
|
||||||
onItem = { itemId ->
|
onItem = { dialog, itemId ->
|
||||||
blur.show()
|
blur.show()
|
||||||
itemDetailDialogViewModel.showItemDialog(
|
itemDetailDialogViewModel.showItemDialog(
|
||||||
|
characterSheetId = dialog.characterSheetId,
|
||||||
inventoryId = null,
|
inventoryId = null,
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
)
|
)
|
||||||
|
|
@ -159,6 +162,25 @@ fun CharacterDetailInventory(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
blur.hide()
|
blur.hide()
|
||||||
itemDetailDialogViewModel.hideItemDialog()
|
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 },
|
key = { it.inventoryId },
|
||||||
) {
|
) {
|
||||||
InventoryItem(
|
InventoryItem(
|
||||||
modifier = Modifier
|
modifier = Modifier.animateItem(),
|
||||||
.animateItem()
|
|
||||||
.fillMaxWidth(),
|
|
||||||
item = it,
|
item = it,
|
||||||
onClick = { onItem(it) },
|
onClick = { onItem(it) },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ class CharacterDetailInventoryFactory(
|
||||||
?.mapNotNull {
|
?.mapNotNull {
|
||||||
val item = items[it.itemId] ?: return@mapNotNull null
|
val item = items[it.itemId] ?: return@mapNotNull null
|
||||||
InventoryItemUio(
|
InventoryItemUio(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
inventoryId = it.inventoryId,
|
inventoryId = it.inventoryId,
|
||||||
itemId = it.itemId,
|
itemId = it.itemId,
|
||||||
label = item.metadata.label,
|
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.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
|
@ -40,6 +41,7 @@ import com.pixelized.desktop.lwa.utils.rememberSaturationFilter
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class InventoryItemUio(
|
data class InventoryItemUio(
|
||||||
|
val characterSheetId: String,
|
||||||
val inventoryId: String,
|
val inventoryId: String,
|
||||||
val itemId: String,
|
val itemId: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
|
|
@ -74,6 +76,7 @@ fun InventoryItem(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
TooltipLayout2(
|
TooltipLayout2(
|
||||||
|
modifier = modifier,
|
||||||
delayMillis = 500,
|
delayMillis = 500,
|
||||||
tips = item.tooltips,
|
tips = item.tooltips,
|
||||||
tooltip = { tooltips ->
|
tooltip = { tooltips ->
|
||||||
|
|
@ -97,7 +100,7 @@ fun InventoryItem(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -128,6 +131,7 @@ fun InventoryItem(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.minimumInteractiveComponentSize()
|
.minimumInteractiveComponentSize()
|
||||||
|
.fillMaxWidth()
|
||||||
.padding(paddingValues = padding)
|
.padding(paddingValues = padding)
|
||||||
.then(other = modifier),
|
.then(other = modifier),
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
|
|
|
||||||
|
|
@ -49,4 +49,15 @@ class InventoryService(
|
||||||
fun delete(characterSheetId: String) {
|
fun delete(characterSheetId: String) {
|
||||||
inventoryStore.delete(characterSheetId = characterSheetId)
|
inventoryStore.delete(characterSheetId = characterSheetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
fun createItem(
|
||||||
|
characterSheetId: String,
|
||||||
|
itemId: String,
|
||||||
|
) : String {
|
||||||
|
return inventoryStore.createItem(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
itemId = itemId,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
class InventoryStore(
|
class InventoryStore(
|
||||||
private val pathProvider: PathProvider,
|
private val pathProvider: PathProvider,
|
||||||
|
|
@ -152,7 +153,49 @@ class InventoryStore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
fun createItem(
|
||||||
|
characterSheetId: String,
|
||||||
|
itemId: String,
|
||||||
|
): String {
|
||||||
|
val (updatedInventory, inventoryId) = inventoryFlow.value.toMutableMap().let { characters ->
|
||||||
|
// get the inventory of the character, if none create one.
|
||||||
|
val inventory = characters[characterSheetId]
|
||||||
|
?: Inventory.empty(characterSheetId = characterSheetId)
|
||||||
|
// create an inventoryId.
|
||||||
|
val inventoryId = inventory.items.createInventoryId()
|
||||||
|
// create an inventory Item.
|
||||||
|
val item = Inventory.Item(
|
||||||
|
inventoryId = inventoryId,
|
||||||
|
itemId = itemId,
|
||||||
|
count = 1f,
|
||||||
|
equipped = false,
|
||||||
|
)
|
||||||
|
// update the inventory with the updated item.
|
||||||
|
inventory.copy(
|
||||||
|
items = inventory.items.toMutableList().also {
|
||||||
|
it.add(item)
|
||||||
|
}
|
||||||
|
) to inventoryId
|
||||||
|
}
|
||||||
|
// save the inventory
|
||||||
|
save(
|
||||||
|
inventory = updatedInventory,
|
||||||
|
create = false,
|
||||||
|
)
|
||||||
|
// return the inventory ID.
|
||||||
|
return inventoryId
|
||||||
|
}
|
||||||
|
|
||||||
private fun inventoryFile(id: String): File {
|
private fun inventoryFile(id: String): File {
|
||||||
return File("${pathProvider.inventoryPath()}${id}.json")
|
return File("${pathProvider.inventoryPath()}${id}.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<Inventory.Item>.createInventoryId(): String {
|
||||||
|
var inventoryId: String
|
||||||
|
do {
|
||||||
|
inventoryId = UUID.randomUUID().toString()
|
||||||
|
} while (any { inventoryId == it.inventoryId })
|
||||||
|
return inventoryId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.pixelized.server.lwa.server.rest.character.putCharacterAlteration
|
||||||
import com.pixelized.server.lwa.server.rest.character.putCharacterDamage
|
import com.pixelized.server.lwa.server.rest.character.putCharacterDamage
|
||||||
import com.pixelized.server.lwa.server.rest.character.putCharacterDiminished
|
import com.pixelized.server.lwa.server.rest.character.putCharacterDiminished
|
||||||
import com.pixelized.server.lwa.server.rest.character.putCharacterFatigue
|
import com.pixelized.server.lwa.server.rest.character.putCharacterFatigue
|
||||||
|
import com.pixelized.server.lwa.server.rest.inventory.createInventory
|
||||||
import com.pixelized.server.lwa.server.rest.inventory.deleteInventory
|
import com.pixelized.server.lwa.server.rest.inventory.deleteInventory
|
||||||
import com.pixelized.server.lwa.server.rest.inventory.getInventory
|
import com.pixelized.server.lwa.server.rest.inventory.getInventory
|
||||||
import com.pixelized.server.lwa.server.rest.inventory.putInventory
|
import com.pixelized.server.lwa.server.rest.inventory.putInventory
|
||||||
|
|
@ -258,6 +259,10 @@ class LocalServer {
|
||||||
path = "/update",
|
path = "/update",
|
||||||
body = engine.putInventory()
|
body = engine.putInventory()
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
path = "/create",
|
||||||
|
body = engine.createInventory()
|
||||||
|
)
|
||||||
delete(
|
delete(
|
||||||
path = "/delete",
|
path = "/delete",
|
||||||
body = engine.deleteInventory()
|
body = engine.deleteInventory()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package com.pixelized.server.lwa.server.exception
|
package com.pixelized.server.lwa.server.exception
|
||||||
|
|
||||||
class MissingParameterException(name: String) :
|
import com.pixelized.shared.lwa.protocol.rest.APIResponse.ErrorCode
|
||||||
|
|
||||||
|
class MissingParameterException(name: String, val code: ErrorCode) :
|
||||||
ServerException(root = Exception("Missing '$name' parameter."))
|
ServerException(root = Exception("Missing '$name' parameter."))
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package com.pixelized.server.lwa.server.rest.character
|
package com.pixelized.server.lwa.server.rest.character
|
||||||
|
|
||||||
import com.pixelized.server.lwa.server.Engine
|
import com.pixelized.server.lwa.server.Engine
|
||||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
|
||||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||||
import com.pixelized.server.lwa.utils.extentions.exception
|
import com.pixelized.server.lwa.utils.extentions.exception
|
||||||
|
import com.pixelized.server.lwa.utils.extentions.param
|
||||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
|
|
@ -14,8 +14,10 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
|
||||||
try {
|
try {
|
||||||
// get the query parameter
|
// get the query parameter
|
||||||
val characterSheetId = call.queryParameters.characterSheetId
|
val characterSheetId = call.queryParameters.characterSheetId
|
||||||
val damage = call.queryParameters["damage"]?.toIntOrNull()
|
val damage: Int = call.queryParameters.param(
|
||||||
?: throw MissingParameterException(name = "damage")
|
name = "damage",
|
||||||
|
code = APIResponse.ErrorCode.Damage,
|
||||||
|
)
|
||||||
// fetch the character sheet
|
// fetch the character sheet
|
||||||
val characterSheet = characterService.character(characterSheetId)
|
val characterSheet = characterService.character(characterSheetId)
|
||||||
?: error("CharacterSheet with id:$characterSheetId not found.")
|
?: error("CharacterSheet with id:$characterSheetId not found.")
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package com.pixelized.server.lwa.server.rest.character
|
package com.pixelized.server.lwa.server.rest.character
|
||||||
|
|
||||||
import com.pixelized.server.lwa.server.Engine
|
import com.pixelized.server.lwa.server.Engine
|
||||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
|
||||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||||
import com.pixelized.server.lwa.utils.extentions.exception
|
import com.pixelized.server.lwa.utils.extentions.exception
|
||||||
|
import com.pixelized.server.lwa.utils.extentions.param
|
||||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
|
|
@ -14,8 +14,10 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit {
|
||||||
try {
|
try {
|
||||||
// get the query parameter
|
// get the query parameter
|
||||||
val characterSheetId = call.queryParameters.characterSheetId
|
val characterSheetId = call.queryParameters.characterSheetId
|
||||||
val diminished = call.queryParameters["diminished"]?.toIntOrNull()
|
val diminished: Int = call.queryParameters.param(
|
||||||
?: throw MissingParameterException(name = "diminished")
|
name = "diminished",
|
||||||
|
code = APIResponse.ErrorCode.Diminished,
|
||||||
|
)
|
||||||
// Update the character damage
|
// Update the character damage
|
||||||
characterService.updateDiminished(
|
characterService.updateDiminished(
|
||||||
characterSheetId = characterSheetId,
|
characterSheetId = characterSheetId,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.pixelized.server.lwa.server.Engine
|
||||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||||
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||||
import com.pixelized.server.lwa.utils.extentions.exception
|
import com.pixelized.server.lwa.utils.extentions.exception
|
||||||
|
import com.pixelized.server.lwa.utils.extentions.param
|
||||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
|
|
@ -14,8 +15,10 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit {
|
||||||
try {
|
try {
|
||||||
// get the query parameter
|
// get the query parameter
|
||||||
val characterSheetId = call.queryParameters.characterSheetId
|
val characterSheetId = call.queryParameters.characterSheetId
|
||||||
val fatigue = call.queryParameters["fatigue"]?.toIntOrNull()
|
val fatigue: Int = call.queryParameters.param(
|
||||||
?: throw MissingParameterException(name = "fatigue")
|
name = "fatigue",
|
||||||
|
code = APIResponse.ErrorCode.Fatigue,
|
||||||
|
)
|
||||||
// fetch the character sheet
|
// fetch the character sheet
|
||||||
val characterSheet = characterService.character(characterSheetId)
|
val characterSheet = characterService.character(characterSheetId)
|
||||||
?: error("CharacterSheet with id:$characterSheetId not found.")
|
?: error("CharacterSheet with id:$characterSheetId not found.")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.pixelized.server.lwa.server.rest.inventory
|
||||||
|
|
||||||
|
import com.pixelized.server.lwa.server.Engine
|
||||||
|
import com.pixelized.server.lwa.utils.extentions.characterSheetId
|
||||||
|
import com.pixelized.server.lwa.utils.extentions.exception
|
||||||
|
import com.pixelized.server.lwa.utils.extentions.itemId
|
||||||
|
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
|
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.RoutingContext
|
||||||
|
|
||||||
|
fun Engine.createInventory(): suspend RoutingContext.() -> Unit {
|
||||||
|
return {
|
||||||
|
try {
|
||||||
|
// get the query parameter
|
||||||
|
val characterSheetId = call.queryParameters.characterSheetId
|
||||||
|
val itemId = call.queryParameters.itemId
|
||||||
|
// add the item to the inventory.
|
||||||
|
val inventoryId = inventoryService.createItem(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
itemId = itemId,
|
||||||
|
)
|
||||||
|
// API & WebSocket responses.
|
||||||
|
call.respond(
|
||||||
|
message = APIResponse.success(
|
||||||
|
data = inventoryId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
webSocket.emit(
|
||||||
|
value = ApiSynchronisation.InventoryUpdate(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
call.exception(
|
||||||
|
exception = exception,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ fun Engine.deleteInventory(): suspend RoutingContext.() -> Unit {
|
||||||
try {
|
try {
|
||||||
// get the query parameter
|
// get the query parameter
|
||||||
val characterSheetId = call.queryParameters.characterSheetId
|
val characterSheetId = call.queryParameters.characterSheetId
|
||||||
// delete the alteration.
|
// delete the inventory.
|
||||||
inventoryService.delete(
|
inventoryService.delete(
|
||||||
characterSheetId = characterSheetId,
|
characterSheetId = characterSheetId,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,49 @@
|
||||||
package com.pixelized.server.lwa.utils.extentions
|
package com.pixelized.server.lwa.utils.extentions
|
||||||
|
|
||||||
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
import com.pixelized.server.lwa.server.exception.MissingParameterException
|
||||||
|
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
|
|
||||||
val Parameters.characterSheetId
|
inline fun <reified T> Parameters.param(
|
||||||
get() = "characterSheetId".let { param ->
|
name: String,
|
||||||
this[param] ?: throw MissingParameterException(name = param)
|
code: APIResponse.ErrorCode,
|
||||||
}
|
): T {
|
||||||
|
return when (T::class) {
|
||||||
|
String::class -> this[name] as? T
|
||||||
|
Boolean::class -> this[name]?.toBooleanStrictOrNull() as? T
|
||||||
|
else -> null
|
||||||
|
} ?: throw MissingParameterException(
|
||||||
|
name = name,
|
||||||
|
code = code,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val Parameters.alterationId
|
val Parameters.characterSheetId: String
|
||||||
get() = "alterationId".let { param ->
|
get() = param(
|
||||||
this[param] ?: throw MissingParameterException(name = param)
|
name = "characterSheetId",
|
||||||
}
|
code = APIResponse.ErrorCode.CharacterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
val Parameters.itemId
|
val Parameters.alterationId: String
|
||||||
get() = "itemId".let { param ->
|
get() = param(
|
||||||
this[param] ?: throw MissingParameterException(name = param)
|
name = "alterationId",
|
||||||
}
|
code = APIResponse.ErrorCode.AlterationId,
|
||||||
|
)
|
||||||
|
|
||||||
val Parameters.create
|
val Parameters.itemId: String
|
||||||
get() = "create".let { param ->
|
get() = param(
|
||||||
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
|
name = "itemId",
|
||||||
}
|
code = APIResponse.ErrorCode.ItemId,
|
||||||
|
)
|
||||||
|
|
||||||
val Parameters.active
|
val Parameters.create: Boolean
|
||||||
get() = "active".let { param ->
|
get() = param(
|
||||||
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
|
name = "create",
|
||||||
}
|
code = APIResponse.ErrorCode.Create,
|
||||||
|
)
|
||||||
|
|
||||||
|
val Parameters.active: Boolean
|
||||||
|
get() = param(
|
||||||
|
name = "active",
|
||||||
|
code = APIResponse.ErrorCode.Active,
|
||||||
|
)
|
||||||
|
|
@ -15,7 +15,7 @@ suspend inline fun <reified T : Exception> RoutingCall.exception(exception: T) {
|
||||||
message = APIResponse.error(
|
message = APIResponse.error(
|
||||||
status = APIResponse.BAD_REQUEST,
|
status = APIResponse.BAD_REQUEST,
|
||||||
message = exception.message ?: "?",
|
message = exception.message ?: "?",
|
||||||
code = APIResponse.ErrorCode.AlterationName,
|
code = exception.code,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ data class APIResponse<T>(
|
||||||
ItemId,
|
ItemId,
|
||||||
ItemName,
|
ItemName,
|
||||||
CharacterSheetId,
|
CharacterSheetId,
|
||||||
|
Create,
|
||||||
|
Active,
|
||||||
|
Damage,
|
||||||
|
Fatigue,
|
||||||
|
Diminished,
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue