Add purse update chatlog message

This commit is contained in:
Thomas Andres Gomez 2025-05-09 15:18:39 +02:00
parent 06c5802d7a
commit 5b633de981
16 changed files with 327 additions and 16 deletions

View file

@ -256,6 +256,8 @@
<string name="chat__characteristic_change__hp_up">%1$s récupère %2$d point(s) de vie</string>
<string name="chat__characteristic_change__pp_down">%1$s utilise %2$d point(s) de pouvoir</string>
<string name="chat__characteristic_change__pp_up">%1$s récupère %2$d point(s) de pouvoir</string>
<string name="chat__purse_change__spend">dépense</string>
<string name="chat__purse_change__earn">empoche</string>
<string name="settings__title">Paramètres de l'application</string>
<string name="settings__reset_action">Paramètres par défault</string>

View file

@ -7,6 +7,7 @@ import com.pixelized.shared.lwa.model.inventory.InventoryJson
import com.pixelized.shared.lwa.model.item.ItemJson
import com.pixelized.shared.lwa.model.tag.TagJson
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.rest.ApiPurseJson
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
interface LwaClient {
@ -116,6 +117,10 @@ interface LwaClient {
create: Boolean,
): APIResponse<Unit>
suspend fun putInventoryPurse(
purse: ApiPurseJson,
): APIResponse<Unit>
suspend fun deleteInventory(
characterSheetId: String,
): APIResponse<Unit>

View file

@ -8,6 +8,7 @@ import com.pixelized.shared.lwa.model.inventory.InventoryJson
import com.pixelized.shared.lwa.model.item.ItemJson
import com.pixelized.shared.lwa.model.tag.TagJson
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.rest.ApiPurseJson
import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson
import io.ktor.client.HttpClient
import io.ktor.client.call.body
@ -199,6 +200,16 @@ class LwaClientImpl(
}
.body<APIResponse<Unit>>()
@Throws
override suspend fun putInventoryPurse(
purse: ApiPurseJson,
): APIResponse<Unit> = client
.put("$root/inventory/update/purse") {
contentType(ContentType.Application.Json)
setBody(purse)
}
.body<APIResponse<Unit>>()
@Throws
override suspend fun deleteInventory(characterSheetId: String): APIResponse<Unit> = client
.delete("$root/inventory/delete?characterSheetId=$characterSheetId")

View file

@ -3,7 +3,6 @@ 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.filter
import kotlinx.coroutines.flow.map
class InventoryRepository(
@ -50,6 +49,17 @@ class InventoryRepository(
)
}
@Throws
suspend fun updateInventoryPurse(
characterSheetId: String,
purse: Inventory.Purse,
) {
inventoryStore.putInventoryPurse(
characterSheetId = characterSheetId,
purse = purse,
)
}
@Throws
suspend fun createInventoryItem(
characterSheetId: String,

View file

@ -73,6 +73,22 @@ class InventoryStore(
}
}
@Throws
suspend fun putInventoryPurse(
characterSheetId: String,
purse: Inventory.Purse,
) {
val request = client.putInventoryPurse(
purse = factory.convertToApiPurse(
characterSheetId = characterSheetId,
purse = purse,
),
)
if (request.success.not()) {
LwaClient.error(error = request)
}
}
@Throws
suspend fun deleteInventory(
characterSheetId: String,
@ -163,6 +179,20 @@ class InventoryStore(
private suspend fun handleMessage(message: SocketMessage) {
when (message) {
is ApiSynchronisation.InventoryApiSynchronisation -> when (message) {
is ApiSynchronisation.PurseUpdate -> _inventories.update {
it.toMutableMap().also { inventories ->
inventories[message.characterSheetId]?.let { inventory ->
inventories[message.characterSheetId] = inventory.copy(
purse = Inventory.Purse(
gold = inventory.purse.gold + message.gold,
silver = inventory.purse.silver + message.silver,
copper = inventory.purse.copper + message.copper,
)
)
}
}
}
is ApiSynchronisation.InventoryUpdate -> updateInventoryFlow(
characterSheetId = message.characterSheetId,
)

View file

@ -55,29 +55,26 @@ class PurseDialogViewModel(
if (dialog.enableConfirm.value.not()) {
return false
}
// Get the player inventory
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
val silverValue = dialog.silver.valueFlow.value.toIntOrNull() ?: 0
val copperValue = dialog.copper.valueFlow.value.toIntOrNull() ?: 0
val purse = Inventory.Purse(
gold = inventory.purse.gold + goldValue * sign,
silver = inventory.purse.silver + silverValue * sign,
copper = inventory.purse.copper + copperValue * sign,
)
// guard case: check if the purse change, not an error case, but avoid useless API call.
if (inventory.purse == purse) {
if (goldValue == 0 && silverValue == 0 && copperValue == 0) {
return true
}
// build a purse delta
val purse = Inventory.Purse(
gold = goldValue * sign,
silver = silverValue * sign,
copper = copperValue * sign,
)
// API call.
return try {
inventoryRepository.updateInventory(
inventory = inventory.copy(
purse = purse
),
create = false,
inventoryRepository.updateInventoryPurse(
characterSheetId = dialog.characterSheetId,
purse = purse,
)
true
} catch (exception: Exception) {

View file

@ -39,6 +39,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.Characteristic
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessage
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.PurseTextMessage
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.PurseTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessage
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
@ -123,6 +125,7 @@ fun CampaignChat(
) {
when (it) {
is RollTextMessageUio -> RollTextMessage(message = it)
is PurseTextMessageUio -> PurseTextMessage(message = it)
is DiminishedTextMessageUio -> DiminishedTextMessage(message = it)
is CharacteristicTextMessageUio -> CharacteristicTextMessage(message = it)
}

View file

@ -4,6 +4,7 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.CharacteristicTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.DiminishedTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.PurseTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage
import com.pixelized.shared.lwa.model.campaign.Campaign
@ -19,6 +20,8 @@ import lwacharactersheet.composeapp.generated.resources.chat__characteristic_cha
import lwacharactersheet.composeapp.generated.resources.chat__characteristic_change__hp_up
import lwacharactersheet.composeapp.generated.resources.chat__characteristic_change__pp_down
import lwacharactersheet.composeapp.generated.resources.chat__characteristic_change__pp_up
import lwacharactersheet.composeapp.generated.resources.chat__purse_change__earn
import lwacharactersheet.composeapp.generated.resources.chat__purse_change__spend
import java.text.SimpleDateFormat
import kotlin.math.abs
@ -143,7 +146,33 @@ class TextMessageFactory(
is GameAdminEvent -> null
is ApiSynchronisation -> null
is ApiSynchronisation -> when (message) {
is ApiSynchronisation.PurseUpdate -> {
// only display the message if the character is in the party.
val isInParty = campaign.characters.contains(message.characterSheetId)
if (isInParty.not()) return null
// get the character sheet
val sheet = characterSheetRepository
.characterDetail(characterSheetId = message.characterSheetId)
?: return null
PurseTextMessageUio(
id = "${message.timestamp}-${message.characterSheetId}-Purse",
timestamp = formatTime.format(message.timestamp),
character = sheet.name,
action = when (message.add) {
true -> Res.string.chat__purse_change__earn
else -> Res.string.chat__purse_change__spend
},
gold = message.gold.takeIf { it != 0 }?.let { abs(it) },
silver = message.silver.takeIf { it != 0 }?.let { abs(it) },
copper = message.copper.takeIf { it != 0 }?.let { abs(it) },
)
}
else -> null
}
}
}
}

View file

@ -0,0 +1,124 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.text.messages
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_copper_32px
import lwacharactersheet.composeapp.generated.resources.ic_gold_32px
import lwacharactersheet.composeapp.generated.resources.ic_silver_32px
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@Stable
data class PurseTextMessageUio(
override val id: String,
override val timestamp: String,
val character: String,
val action: StringResource,
val gold: Int?,
val silver: Int?,
val copper: Int?,
) : TextMessage
@Composable
fun PurseTextMessage(
modifier: Modifier = Modifier,
message: PurseTextMessageUio,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 3.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.timestamp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = message.timestamp,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.timestamp,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = ">",
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = message.character,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = stringResource(message.action),
)
message.gold?.let {
Row {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "$it",
)
Image(
painter = painterResource(Res.drawable.ic_gold_32px),
modifier = Modifier.padding(bottom = 2.dp).size(size = 16.dp),
contentDescription = null,
)
}
}
message.silver?.let {
Row {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "$it",
)
Image(
painter = painterResource(Res.drawable.ic_silver_32px),
modifier = Modifier.padding(bottom = 2.dp).size(size = 16.dp),
contentDescription = null,
)
}
}
message.copper?.let {
Row {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = "$it",
)
Image(
painter = painterResource(Res.drawable.ic_copper_32px),
modifier = Modifier.padding(bottom = 2.dp).size(size = 16.dp),
contentDescription = null,
)
}
}
}
}

View file

@ -88,7 +88,7 @@ fun GMActionContent(
GMAction(
modifier = Modifier.fillMaxWidth(),
icon = Res.drawable.ic_sync_24dp,
label = "Syncrhonization du serveur",
label = "Synchronization du serveur",
onAction = onServerSync,
)
GMAction(