diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 65947ed..0bd91d4 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -9,7 +9,7 @@ plugins {
alias(libs.plugins.buildkonfig)
}
-fun getVersion() = "1.4.0"
+fun getLwaVersion() = "1.5.1"
kotlin {
jvm("desktop")
@@ -67,7 +67,7 @@ buildkonfig {
packageName = "com.pixelized.desktop.lwa"
defaultConfigs {
- buildConfigField(STRING, "version", getVersion())
+ buildConfigField(STRING, "version", getLwaVersion())
}
}
@@ -80,7 +80,7 @@ compose.desktop {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "Table de Lwa"
- packageVersion = getVersion()
+ packageVersion = getLwaVersion()
description = "Application de support au jeux de rôle dans l'univers de Lwa."
copyright = "© 2020 Pixelized. All rights reserved."
vendor = "Pixelized"
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_filter_list_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_filter_list_24dp.xml
new file mode 100644
index 0000000..621e515
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_filter_list_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 0de75f5..4f9d158 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -84,6 +84,7 @@
Points de vie maximum
Points de vie
Points de pouvoir maximum
+ Utiliser l'armure
Points de pouvoir
Bonus aux dégats
Armure
@@ -253,6 +254,7 @@
%1$s passe à %2$d d'état diminuée
%1$s subit %2$d point(s) de dommage
+ %1$s subit %2$d point(s) de dommage (armure : %3$d)
%1$s récupère %2$d point(s) de vie
%1$s utilise %2$d point(s) de pouvoir
%1$s récupère %2$d point(s) de pouvoir
@@ -284,6 +286,9 @@
Passage du niveau %1$d ▸ %2$d
niv : %1$d -
+ Lien vers l'image
+ Fermer
+
Admin
GameMaster
Sauvegarder
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt
index 74b9ec4..9e7ab9a 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt
@@ -27,8 +27,8 @@ class CharacterSheetRepository(
store.updateCharacterSheetDetailFlow(characterSheetId = characterSheetId)
}
- fun characterPreview(characterId: String?): CharacterSheetPreview? {
- return store.previewFlow.value.firstOrNull { it.characterSheetId == characterId }
+ fun characterPreview(characterSheetId: String?): CharacterSheetPreview? {
+ return store.previewFlow.value.firstOrNull { it.characterSheetId == characterSheetId }
}
fun characterDetail(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/button/SignButton.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/button/SignButton.kt
new file mode 100644
index 0000000..5fab8d5
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/button/SignButton.kt
@@ -0,0 +1,58 @@
+package com.pixelized.desktop.lwa.ui.composable.button
+
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import com.pixelized.desktop.lwa.ui.theme.lwa
+import kotlinx.coroutines.flow.StateFlow
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.ic_remove_24dp
+import org.jetbrains.compose.resources.painterResource
+
+@Composable
+fun SignButton(
+ modifier: Modifier = Modifier,
+ interactionSource: MutableInteractionSource? = null,
+ enabled: Boolean = true,
+ add: StateFlow,
+ onClick: () -> Unit,
+) {
+ IconButton(
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ interactionSource = interactionSource,
+ ) {
+ val rotation = animateFloatAsState(
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ targetValue = when (add.collectAsState().value) {
+ true -> 90f
+ else -> 0f
+ }
+ )
+ Icon(
+ modifier = Modifier.graphicsLayer {
+ this.rotationZ = rotation.value * 2f
+ },
+ painter = painterResource(Res.drawable.ic_remove_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null
+ )
+ Icon(
+ modifier = Modifier.graphicsLayer {
+ this.rotationZ = rotation.value * 3f
+ },
+ painter = painterResource(Res.drawable.ic_remove_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null
+ )
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialog.kt
index e4c0dbe..c19946f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialog.kt
@@ -1,40 +1,43 @@
package com.pixelized.desktop.lwa.ui.composable.character.characteristic
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
+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.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
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.LaunchedEffect
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
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.text.style.TextAlign
+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.DpSize
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
+import com.pixelized.desktop.lwa.ui.composable.button.SignButton
+import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog
+import com.pixelized.desktop.lwa.ui.composable.checkbox.LwaCheckBox
+import com.pixelized.desktop.lwa.ui.composable.checkbox.LwaCheckBoxUio
+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.LwaTextFieldUio
+import com.pixelized.desktop.lwa.ui.theme.lwa
+import kotlinx.coroutines.flow.StateFlow
import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__use_armor
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
import org.jetbrains.compose.resources.stringResource
@@ -44,9 +47,10 @@ data class CharacterSheetCharacteristicDialogUio(
val characterSheetId: String,
val characteristic: Characteristic,
val label: String,
- val value: () -> TextFieldValue,
- val onValueChange: (TextFieldValue) -> Unit,
- val maxValue: String,
+ val add: StateFlow,
+ val value: LwaTextFieldUio,
+ val enableArmor: LwaCheckBoxUio?,
+ val enableConfirm: StateFlow,
) {
@Stable
enum class Characteristic {
@@ -55,125 +59,131 @@ data class CharacterSheetCharacteristicDialogUio(
}
}
-@Composable
-fun CharacterSheetCharacteristicDialog(
- dialog: State,
- onConfirm: (CharacterSheetCharacteristicDialogUio) -> Unit,
- onDismissRequest: () -> Unit,
-) {
- dialog.value?.let {
- Dialog(
- onDismissRequest = onDismissRequest,
- content = {
- CharacterSheetCharacteristicContent(
- dialog = it,
- onConfirm = onConfirm,
- onDismissRequest = onDismissRequest,
- )
- }
- )
- }
+@Stable
+object CharacterSheetCharacteristicDialogDefault {
+ @Stable
+ val paddings: PaddingValues =
+ PaddingValues(top = 16.dp, start = 8.dp, end = 8.dp, bottom = 8.dp)
+
+ @Stable
+ val spacings: DpSize = DpSize(width = 4.dp, height = 8.dp)
}
@Composable
-private fun CharacterSheetCharacteristicContent(
- dialog: CharacterSheetCharacteristicDialogUio,
+fun CharacterSheetCharacteristicDialog(
+ dialog: State,
+ paddings: PaddingValues = CharacterSheetCharacteristicDialogDefault.paddings,
+ spacings: DpSize = CharacterSheetCharacteristicDialogDefault.spacings,
onConfirm: (CharacterSheetCharacteristicDialogUio) -> Unit,
+ onSwapSign: (CharacterSheetCharacteristicDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
- val typography = MaterialTheme.typography
- val colors = MaterialTheme.colors
+ LwaDialog(
+ state = dialog,
+ onDismissRequest = onDismissRequest,
+ onConfirm = { dialog.value?.let(onConfirm) }
+ ) { state ->
+ val focusRequester = remember { FocusRequester() }
+ LaunchedEffect(Unit) { focusRequester.requestFocus() }
- val focusRequester = remember { FocusRequester() }
- LaunchedEffect(Unit) {
- focusRequester.requestFocus()
- }
+ CharacterSheetCharacteristicDialogKeyHandler(
+ onSwap = { onSwapSign(state) },
+ )
- Box(
- modifier = Modifier
- .clickable(
- interactionSource = remember { MutableInteractionSource() },
- indication = null,
- onClick = onDismissRequest,
+ Column(
+ modifier = Modifier.padding(paddingValues = paddings),
+ verticalArrangement = Arrangement.spacedBy(space = spacings.height),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ style = MaterialTheme.typography.caption,
+ text = state.label,
)
- .onPreviewEscape(
- escape = onDismissRequest,
- enter = { onConfirm(dialog) },
- )
- .fillMaxSize()
- .padding(all = 32.dp),
- contentAlignment = Alignment.Center,
- ) {
- DecoratedBox {
- Surface {
- Column(
- verticalArrangement = Arrangement.spacedBy(space = 8.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
+
+ 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(192.dp),
+ field = state.value,
+ )
+ }
+ state.enableArmor?.let {
+ Row (
+ modifier = Modifier.align(alignment = Alignment.End),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(
+ space = spacings.width,
+ alignment = Alignment.End
+ ),
) {
Text(
- modifier = Modifier.padding(top = 16.dp, start = 24.dp, end = 24.dp),
style = MaterialTheme.typography.caption,
- text = dialog.label,
+ text = stringResource(Res.string.character_sheet_edit__use_armor),
+ )
+ LwaCheckBox(
+ field = it,
+ )
+ }
+ }
+ 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)
)
- Row(
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- ) {
- BasicTextField(
- modifier = Modifier
- .focusRequester(focusRequester = focusRequester)
- .alignByBaseline()
- .width(width = 120.dp),
- textStyle = remember {
- typography.h5.copy(
- color = colors.primary,
- textAlign = TextAlign.End,
- fontWeight = FontWeight.Bold
- )
- },
- cursorBrush = SolidColor(MaterialTheme.colors.primary),
- singleLine = true,
- keyboardActions = KeyboardActions { onConfirm(dialog) },
- keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
- value = dialog.value(),
- onValueChange = dialog.onValueChange,
- )
- Text(
- modifier = Modifier.alignByBaseline(),
- text = "/",
- )
- Text(
- modifier = Modifier.alignByBaseline(),
- text = dialog.maxValue,
- )
- }
- Row(
- modifier = Modifier
- .padding(bottom = 4.dp)
- .padding(horizontal = 16.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(
- onClick = { onConfirm(dialog) },
- ) {
- Text(
- text = stringResource(Res.string.dialog__confirm_action)
- )
- }
- }
}
}
}
}
+}
+
+
+@Composable
+private fun CharacterSheetCharacteristicDialogKeyHandler(
+ onSwap: () -> Unit,
+) {
+ KeyHandler {
+ if (it.type == KeyEventType.KeyDown) {
+ when (it.key) {
+ Key.AltLeft, Key.AltRight -> {
+ onSwap()
+ true
+ }
+
+ else -> false
+ }
+ } else {
+ false
+ }
+ }
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogFactory.kt
index 34746a4..c995855 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogFactory.kt
@@ -1,15 +1,22 @@
package com.pixelized.desktop.lwa.ui.composable.character.characteristic
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.TextFieldValue
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogUio.Characteristic
+import com.pixelized.desktop.lwa.ui.composable.checkbox.createLwaCheckBox
+import com.pixelized.desktop.lwa.ui.composable.checkbox.createLwaCheckBoxFlow
+import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextField
+import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextFieldFlow
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.character__inventory__add_to_purse__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__power_point
import org.jetbrains.compose.resources.getString
@@ -19,9 +26,13 @@ class CharacterSheetCharacteristicDialogFactory(
private val alterationRepository: AlterationRepository,
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
) {
+ private val decimalChecker = Regex("""^\s*\d*\s*${'$'}""")
+
suspend fun convertToDialogUio(
+ scope: CoroutineScope,
characterSheetId: String?,
characteristic: Characteristic,
+ signFlow: StateFlow,
): CharacterSheetCharacteristicDialogUio? {
if (characterSheetId == null) return null
@@ -32,10 +43,10 @@ class CharacterSheetCharacteristicDialogFactory(
if (characterSheet == null) return null
- val alterations: Map> = alterationRepository.activeFieldAlterations(
- characterSheetId = characterSheetId,
- )
-
+ val alterations: Map> =
+ alterationRepository.activeFieldAlterations(
+ characterSheetId = characterSheetId,
+ )
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
characterSheet = characterSheet,
alterations = alterations,
@@ -43,36 +54,61 @@ class CharacterSheetCharacteristicDialogFactory(
return when (characteristic) {
Characteristic.Damage -> {
- val value = mutableStateOf(
- "${alteredCharacterSheet.maxHp - alteredCharacterSheet.damage}".let {
- TextFieldValue(text = it, selection = TextRange(it.length))
- }
+ val valueFlow = createLwaTextFieldFlow(
+ label = null,
)
+ val armor = if (alteredCharacterSheet.armor > 0) {
+ createLwaCheckBoxFlow(
+ checked = true,
+ )
+ } else {
+ null
+ }
CharacterSheetCharacteristicDialogUio(
characterSheetId = characterSheetId,
characteristic = characteristic,
- label = getString(resource = Res.string.character_sheet_edit__sub_characteristics__hit_point),
- value = { value.value },
- onValueChange = { value.value = it },
- maxValue = "${alteredCharacterSheet.maxHp}",
+ label = getString(Res.string.character_sheet_edit__sub_characteristics__hit_point).let {
+ "$it : ${alteredCharacterSheet.maxHp - alteredCharacterSheet.damage}/${alteredCharacterSheet.maxHp}"
+ },
+ add = signFlow,
+ value = valueFlow.createLwaTextField {
+ valueFlow.errorFlow.value = check(it)
+ valueFlow.valueFlow.value = it
+ },
+ enableArmor = armor?.createLwaCheckBox(),
+ enableConfirm = valueFlow.errorFlow.map { it.not() }.stateIn(
+ scope = scope,
+ started = SharingStarted.Lazily,
+ initialValue = valueFlow.errorFlow.value.not(),
+ ),
)
}
Characteristic.Fatigue -> {
- val value = mutableStateOf(
- "${alteredCharacterSheet.maxPp - alteredCharacterSheet.fatigue}".let {
- TextFieldValue(text = it, selection = TextRange(it.length))
- }
+ val valueFlow = createLwaTextFieldFlow(
+ label = null,
)
CharacterSheetCharacteristicDialogUio(
characterSheetId = characterSheetId,
characteristic = characteristic,
- label = getString(resource = Res.string.character_sheet_edit__sub_characteristics__power_point),
- value = { value.value },
- onValueChange = { value.value = it },
- maxValue = "${alteredCharacterSheet.maxPp}",
+ label = getString(Res.string.character_sheet_edit__sub_characteristics__power_point).let {
+ "$it : ${alteredCharacterSheet.maxPp - alteredCharacterSheet.fatigue}/${alteredCharacterSheet.maxPp}"
+ },
+ add = signFlow,
+ value = valueFlow.createLwaTextField {
+ valueFlow.errorFlow.value = check(it)
+ valueFlow.valueFlow.value = it
+ },
+ enableArmor = null,
+ enableConfirm = valueFlow.errorFlow.map { it.not() }.stateIn(
+ scope = scope,
+ started = SharingStarted.Lazily,
+ initialValue = valueFlow.errorFlow.value.not(),
+ ),
)
}
}
}
+
+ private fun check(value: String): Boolean = !decimalChecker.matches(value)
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogViewModel.kt
index 9ea4c93..7e104d4 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterSheetCharacteristicDialogViewModel.kt
@@ -3,12 +3,15 @@ package com.pixelized.desktop.lwa.ui.composable.character.characteristic
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogUio.Characteristic
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
class CharacterSheetCharacteristicDialogViewModel(
private val characterSheetRepository: CharacterSheetRepository,
@@ -18,6 +21,8 @@ class CharacterSheetCharacteristicDialogViewModel(
private val network: NetworkRepository,
) : ViewModel() {
+ private val signFlow = MutableStateFlow(false)
+
private val _statChangeDialog = mutableStateOf(null)
val statChangeDialog: State get() = _statChangeDialog
@@ -25,19 +30,27 @@ class CharacterSheetCharacteristicDialogViewModel(
_statChangeDialog.value = null
}
+ fun swapCharacteristicSign() {
+ signFlow.update { it.not() }
+ }
+
suspend fun showSubCharacteristicDialog(
characterSheetId: String?,
characteristic: Characteristic,
) {
+ signFlow.update { false }
_statChangeDialog.value = factory.convertToDialogUio(
+ scope = viewModelScope,
characterSheetId = characterSheetId,
characteristic = characteristic,
+ signFlow = signFlow,
)
}
suspend fun changeSubCharacteristic(
characterSheetId: String?,
characteristic: Characteristic,
+ useArmor: Boolean,
value: Int,
) {
if (characterSheetId == null) return
@@ -49,29 +62,37 @@ class CharacterSheetCharacteristicDialogViewModel(
if (characterSheet == null) return
- val alterations = alterationRepository.activeFieldAlterations(
- characterSheetId = characterSheetId,
- )
-
- // we need the maximum HP / Power that the character sheet have.
- val alteredSheet = alteredCharacterSheetFactory.sheet(
- characterSheet = characterSheet,
- alterations = alterations,
- )
+ val sign = if (signFlow.value) 1 else -1
val message = when (characteristic) {
- Characteristic.Damage -> CharacterSheetEvent.UpdateDamage(
- timestamp = System.currentTimeMillis(),
- characterSheetId = characterSheetId,
- oldValue = alteredSheet.damage,
- damage = alteredSheet.maxHp - value,
- )
+ Characteristic.Damage -> {
+ val armor = if (useArmor && signFlow.value.not()) {
+ val alterations = alterationRepository.activeFieldAlterations(
+ characterSheetId = characterSheetId,
+ )
+ val alteredSheet = alteredCharacterSheetFactory.sheet(
+ characterSheet = characterSheet,
+ alterations = alterations,
+ )
+ alteredSheet.armor
+ } else {
+ null
+ }
+
+ CharacterSheetEvent.UpdateDamage(
+ timestamp = System.currentTimeMillis(),
+ characterSheetId = characterSheetId,
+ oldValue = characterSheet.damage,
+ damage = characterSheet.damage - value * sign - (armor ?: 0),
+ armor = armor,
+ )
+ }
Characteristic.Fatigue -> CharacterSheetEvent.UpdateFatigue(
timestamp = System.currentTimeMillis(),
characterSheetId = characterSheetId,
- oldValue = alteredSheet.fatigue,
- fatigue = alteredSheet.maxPp - value,
+ oldValue = characterSheet.fatigue,
+ fatigue = characterSheet.fatigue - value * sign,
)
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialog.kt
index 9ca3d7b..b1ed627 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialog.kt
@@ -23,6 +23,7 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor
@@ -45,6 +46,7 @@ data class CharacterSheetDiminishedDialogUio(
val label: String,
val value: () -> TextFieldValue,
val onValueChange: (TextFieldValue) -> Unit,
+ val additional: String?,
)
@Composable
@@ -76,6 +78,14 @@ private fun CharacterSheetDiminishedContent(
val typography = MaterialTheme.typography
val colors = MaterialTheme.colors
+ val valueTypography = remember {
+ typography.h5.copy(
+ color = colors.primary,
+ textAlign = TextAlign.Center,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
@@ -107,24 +117,32 @@ private fun CharacterSheetDiminishedContent(
style = MaterialTheme.typography.caption,
text = dialog.label,
)
- BasicTextField(
- modifier = Modifier
- .focusRequester(focusRequester = focusRequester)
- .width(width = 120.dp),
- textStyle = remember {
- typography.h5.copy(
- color = colors.primary,
- textAlign = TextAlign.Center,
- fontWeight = FontWeight.Bold
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(
+ space = 8.dp,
+ alignment = Alignment.CenterHorizontally
+ )
+ ) {
+ BasicTextField(
+ modifier = Modifier
+ .focusRequester(focusRequester = focusRequester)
+ .width(width = 120.dp),
+ textStyle = valueTypography,
+ cursorBrush = SolidColor(MaterialTheme.colors.primary),
+ singleLine = true,
+ keyboardActions = KeyboardActions { onConfirm(dialog) },
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
+ value = dialog.value(),
+ onValueChange = dialog.onValueChange,
+ )
+ dialog.additional?.let {
+ Text(
+ modifier = Modifier.alpha(alpha = 0.66f),
+ style = valueTypography,
+ text = it
)
- },
- cursorBrush = SolidColor(MaterialTheme.colors.primary),
- singleLine = true,
- keyboardActions = KeyboardActions { onConfirm(dialog) },
- keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
- value = dialog.value(),
- onValueChange = dialog.onValueChange,
- )
+ }
+ }
Row(
modifier = Modifier
.padding(top = 4.dp)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialogFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialogFactory.kt
index 918ca5f..d4baf8c 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialogFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/diminished/CharacterSheetDiminishedDialogFactory.kt
@@ -6,11 +6,12 @@ import androidx.compose.ui.text.input.TextFieldValue
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
-import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
+import com.pixelized.shared.lwa.utils.signLabel
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label
import org.jetbrains.compose.resources.getString
+import kotlin.math.abs
class CharacterSheetDiminishedDialogFactory(
private val characterSheetRepository: CharacterSheetRepository,
@@ -19,7 +20,7 @@ class CharacterSheetDiminishedDialogFactory(
) {
suspend fun convertToDialogUio(
characterSheetId: String?,
- ) : CharacterSheetDiminishedDialogUio? {
+ ): CharacterSheetDiminishedDialogUio? {
if (characterSheetId == null) return null
@@ -29,21 +30,19 @@ class CharacterSheetDiminishedDialogFactory(
if (characterSheet == null) return null
- val alterations: Map> = alterationRepository.activeFieldAlterations(
- characterSheetId = characterSheetId,
- )
-
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
characterSheet = characterSheet,
- alterations = alterations,
+ alterations = alterationRepository.activeFieldAlterations(
+ characterSheetId = characterSheetId
+ ),
)
-
val textFieldValue = mutableStateOf(
TextFieldValue(
- text = "${alteredCharacterSheet.diminished}",
+ text = "${characterSheet.diminished}",
selection = TextRange(index = 0),
)
)
+
return CharacterSheetDiminishedDialogUio(
characterSheetId = characterSheetId,
label = getString(resource = Res.string.character_sheet__diminished__label),
@@ -54,6 +53,9 @@ class CharacterSheetDiminishedDialogFactory(
else -> value
}
},
+ additional = (alteredCharacterSheet.diminishedAlterations)
+ .takeIf { it != 0 }
+ ?.let { "${it.signLabel}${abs(it)}" }
)
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt
index 192e136..37d4286 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogFactory.kt
@@ -65,6 +65,6 @@ class ItemDetailDialogFactory {
inventoryId: String?,
value: String,
): Boolean {
- return floatChecker.matches(value).not() || (inventoryId == null && value.toFloat() < 1f)
+ return value.isBlank() || floatChecker.matches(value).not() || (inventoryId == null && value.toFloat() < 1f)
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt
index ae95364..974468d 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/item/ItemDetailDialogViewModel.kt
@@ -52,7 +52,7 @@ class ItemDetailDialogViewModel(
characterSheetId = ids?.characterSheetId,
items = items,
count = selectedInventoryItem?.count ?: 1f,
- equipped = selectedInventoryItem?.equipped ?: false,
+ equipped = selectedInventoryItem?.equipped == true,
inventoryId = ids?.inventoryId,
itemId = ids?.itemId,
)
@@ -86,9 +86,9 @@ class ItemDetailDialogViewModel(
): Boolean {
try {
if (dialog.countable?.errorFlow?.value == true) return false
- val quantity = dialog.countable?.valueFlow?.value ?: return false
- val count = factory.parse(quantity = quantity)
- ?: quantity.toFloatOrNull()
+ val quantity = dialog.countable?.valueFlow?.value
+ val count = factory.parse(quantity = quantity ?: "")
+ ?: quantity?.toFloatOrNull()
?: 1f
// create the inventory item on the server, get the newly create id from that.
inventoryRepository.createInventoryItem(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/purse/PurseDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/purse/PurseDialog.kt
index 8a6e708..9c4c360 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/purse/PurseDialog.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/purse/PurseDialog.kt
@@ -1,9 +1,6 @@
package com.pixelized.desktop.lwa.ui.composable.character.purse
import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
@@ -19,8 +16,6 @@ import androidx.compose.foundation.layout.fillMaxSize
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
@@ -35,13 +30,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.graphicsLayer
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.DpSize
import androidx.compose.ui.unit.dp
+import com.pixelized.desktop.lwa.ui.composable.button.SignButton
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
@@ -53,8 +48,6 @@ import kotlinx.coroutines.flow.StateFlow
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
-import lwacharactersheet.composeapp.generated.resources.ic_remove_24dp
-import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@Stable
@@ -94,6 +87,10 @@ fun PurseDialog(
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.requestFocus() }
+ PurseDialogKeyHandler(
+ onSwap = { onSwapSign(state) },
+ )
+
Column(
modifier = Modifier.padding(paddingValues = paddings),
verticalArrangement = Arrangement.spacedBy(space = spacings.height),
@@ -162,10 +159,6 @@ fun PurseDialog(
}
}
}
-
- PurseDialogKeyHandler(
- onSwap = { onSwapSign(state) },
- )
}
}
@@ -202,46 +195,6 @@ private fun PurseContent(
}
}
-@Composable
-private fun SignButton(
- modifier: Modifier = Modifier,
- interactionSource: MutableInteractionSource? = null,
- enabled: Boolean = true,
- add: StateFlow,
- onClick: () -> Unit,
-) {
- IconButton(
- onClick = onClick,
- modifier = modifier,
- enabled = enabled,
- interactionSource = interactionSource,
- ) {
- val rotation = animateFloatAsState(
- animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
- targetValue = when (add.collectAsState().value) {
- true -> 90f
- else -> 0f
- }
- )
- Icon(
- modifier = Modifier.graphicsLayer {
- this.rotationZ = rotation.value * 2f
- },
- painter = painterResource(Res.drawable.ic_remove_24dp),
- tint = MaterialTheme.lwa.colorScheme.base.primary,
- contentDescription = null
- )
- Icon(
- modifier = Modifier.graphicsLayer {
- this.rotationZ = rotation.value * 3f
- },
- painter = painterResource(Res.drawable.ic_remove_24dp),
- tint = MaterialTheme.lwa.colorScheme.base.primary,
- contentDescription = null
- )
- }
-}
-
@Composable
private fun PurseDialogKeyHandler(
onSwap: () -> Unit,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/checkbox/LwaCheckBoxHelper.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/checkbox/LwaCheckBoxHelper.kt
new file mode 100644
index 0000000..7caea00
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/checkbox/LwaCheckBoxHelper.kt
@@ -0,0 +1,30 @@
+package com.pixelized.desktop.lwa.ui.composable.checkbox
+
+import androidx.compose.runtime.Stable
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@Stable
+data class LwaCheckBoxFlow(
+ val checkedFlow: MutableStateFlow,
+)
+
+@Stable
+fun createLwaCheckBoxFlow(
+ checked: Boolean,
+): LwaCheckBoxFlow {
+ return LwaCheckBoxFlow(
+ checkedFlow = MutableStateFlow(checked),
+ )
+}
+
+@Stable
+fun LwaCheckBoxFlow.createLwaCheckBox(
+ enable: Boolean = true,
+ onCheckedChange: (Boolean) -> Unit = { checkedFlow.value = it },
+): LwaCheckBoxUio {
+ return LwaCheckBoxUio(
+ enable = enable,
+ checked = checkedFlow,
+ onCheckedChange = onCheckedChange,
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextFieldHelper.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextFieldHelper.kt
index 9bc4fe0..b1e88a8 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextFieldHelper.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextFieldHelper.kt
@@ -6,13 +6,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
@Stable
data class LwaTextFieldFlow(
val errorFlow: MutableStateFlow,
- val valueFlow: MutableStateFlow,
val labelFlow: MutableStateFlow,
+ val valueFlow: MutableStateFlow,
)
fun createLwaTextFieldFlow(
error: Boolean = false,
- label: String,
+ label: String?,
value: String = "",
): LwaTextFieldFlow {
return createLwaTextFieldFlow(
@@ -24,8 +24,8 @@ fun createLwaTextFieldFlow(
fun createLwaTextFieldFlow(
errorFlow: MutableStateFlow = MutableStateFlow(false),
- valueFlow: MutableStateFlow,
labelFlow: MutableStateFlow,
+ valueFlow: MutableStateFlow,
): LwaTextFieldFlow {
return LwaTextFieldFlow(
errorFlow = errorFlow,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt
index 39586d3..4d4f2ce 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt
@@ -6,12 +6,18 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
+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
@@ -21,13 +27,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
+import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
import lwacharactersheet.composeapp.generated.resources.ic_link_24dp
+import lwacharactersheet.composeapp.generated.resources.portrait_overlay__close
+import lwacharactersheet.composeapp.generated.resources.portrait_overlay__external_link
import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
@Stable
@@ -79,30 +91,35 @@ private fun PortraitContent(
) {
when (it) {
null -> Box(
- modifier = Modifier.size(size = MaterialTheme.lwa.size.portrait.maximized)
+ modifier = Modifier.fillMaxSize(),
)
else -> Box(
- modifier = Modifier.size(size = MaterialTheme.lwa.size.portrait.maximized)
+ modifier = Modifier.fillMaxSize(),
) {
AsyncImage(
modifier = Modifier.matchParentSize(),
model = it,
filterQuality = FilterQuality.High,
+ contentScale = ContentScale.Inside,
+ alignment = Alignment.Center,
contentDescription = null
)
Row(
modifier = Modifier
- .align(alignment = Alignment.TopEnd)
+ .align(alignment = Alignment.BottomCenter)
.animateContentSize(),
+ horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
AnimatedVisibility(
visible = options.value.isBrowserAvailable,
enter = fadeIn(),
exit = fadeOut(),
) {
- IconButton(
+ Button(
+ colors = LwaButtonColors(),
+ shape = CircleShape,
onClick = { onDownload(it) },
) {
Icon(
@@ -110,6 +127,10 @@ private fun PortraitContent(
tint = MaterialTheme.lwa.colorScheme.base.primary,
contentDescription = null
)
+ Text(
+ modifier = Modifier.padding(start = 4.dp),
+ text = stringResource(Res.string.portrait_overlay__external_link),
+ )
}
}
AnimatedVisibility(
@@ -117,7 +138,9 @@ private fun PortraitContent(
enter = fadeIn(),
exit = fadeOut(),
) {
- IconButton(
+ Button(
+ colors = LwaButtonColors(),
+ shape = CircleShape,
onClick = onClose,
) {
Icon(
@@ -125,6 +148,10 @@ private fun PortraitContent(
tint = MaterialTheme.lwa.colorScheme.base.primary,
contentDescription = null
)
+ Text(
+ modifier = Modifier.padding(start = 4.dp),
+ text = stringResource(Res.string.portrait_overlay__close),
+ )
}
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlayViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlayViewModel.kt
index e5e75b4..8ced0d1 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlayViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlayViewModel.kt
@@ -25,9 +25,13 @@ class PortraitOverlayViewModel(
@OptIn(ExperimentalCoroutinesApi::class)
val portrait = networkRepository.data
.mapNotNull { it as? GameMasterEvent.DisplayPortrait }
- .flatMapLatest { characterSheetRepository.characterDetailFlow(characterSheetId = it.characterSheetId) }
- .map { it?.portrait }
+ .flatMapLatest { portrait ->
+ characterSheetRepository.characterSheetPreviewFlow().map { previews ->
+ previews.firstOrNull { it.characterSheetId == portrait.characterSheetId }
+ }
+ }
.distinctUntilChanged()
+ .map { it?.portrait }
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
@@ -37,7 +41,7 @@ class PortraitOverlayViewModel(
val options = settingsRepository.settingsFlow()
.map { settings ->
PortraitOptionUio(
- isGameMaster = settings.isGameMaster ?: false,
+ isGameMaster = settings.isGameMaster == true,
isBrowserAvailable = Desktop.isDesktopSupported()
&& Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)
)
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
index 0fafa77..c18e99b 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
@@ -206,12 +206,16 @@ fun CampaignScreen(
characteristicDialogViewModel.changeSubCharacteristic(
characterSheetId = dialog.characterSheetId,
characteristic = dialog.characteristic,
- value = dialog.value().text.toIntOrNull() ?: 0,
+ useArmor= dialog.enableArmor?.checked?.value == true,
+ value = dialog.value.valueFlow.value.toIntOrNull() ?: 0,
)
characteristicDialogViewModel.hideSubCharacteristicDialog()
blurController.hide()
}
},
+ onSwapSign = {
+ characteristicDialogViewModel.swapCharacteristicSign()
+ },
onDismissRequest = {
characteristicDialogViewModel.hideSubCharacteristicDialog()
blurController.hide()
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
index 96d89d3..714593f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt
@@ -1,16 +1,23 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory
+import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
@@ -22,6 +29,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
@@ -32,6 +40,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
@@ -53,11 +62,13 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.item
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
import com.pixelized.desktop.lwa.ui.theme.lwa
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character__inventory__add_to_inventory__action
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
+import lwacharactersheet.composeapp.generated.resources.ic_filter_list_24dp
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
@@ -66,6 +77,7 @@ import org.koin.compose.viewmodel.koinViewModel
data class CharacterDetailInventoryUio(
val characterSheetId: String,
val addItemAction: StateFlow,
+ val showFilter: StateFlow,
val filter: LwaTextFieldUio,
val purse: PurseUio,
val items: List,
@@ -292,35 +304,56 @@ private fun CharacterDetailInventoryContent(
horizontalArrangement = Arrangement.spacedBy(space = spacing),
) {
InventoryPurse(
- modifier = Modifier.weight(weight = 1f),
purse = inventory.purse,
onPurse = { onPurse(inventory.characterSheetId) },
)
- LwaTextField(
- modifier = Modifier.weight(weight = 1f),
- colors = LwaTextFieldColors(
- backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base2dp,
- ),
- field = inventory.filter,
- trailingIcon = {
- val value = inventory.filter.valueFlow.collectAsState()
- AnimatedVisibility(
- visible = value.value.isNotBlank(),
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- IconButton(
- onClick = { inventory.filter.onValueChange.invoke("") },
- ) {
- Icon(
- painter = painterResource(Res.drawable.ic_cancel_24dp),
- tint = MaterialTheme.lwa.colorScheme.base.primary,
- contentDescription = null,
- )
+ Spacer(
+ modifier = Modifier.weight(weight = 1f)
+ )
+ AnimatedContent(
+ targetState = inventory.showFilter.collectAsState().value,
+ ) {
+ when (it) {
+ true -> LwaTextField(
+ colors = LwaTextFieldColors(
+ backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base2dp,
+ ),
+ field = inventory.filter,
+ trailingIcon = {
+ val value = inventory.filter.valueFlow.collectAsState()
+ androidx.compose.animation.AnimatedVisibility(
+ visible = value.value.isNotBlank(),
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ) {
+ IconButton(
+ onClick = { inventory.filter.onValueChange.invoke("") },
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_cancel_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ }
+ }
}
+ )
+
+ else -> Box(
+ modifier = Modifier
+ .clip(shape = MaterialTheme.lwa.shapes.base.small)
+ .clickable { (inventory.showFilter as? MutableStateFlow)?.value = true }
+ .size(size = 32.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_filter_list_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null
+ )
}
}
- )
+ }
}
}
items(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt
index cdf640a..8fe8beb 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventoryFactory.kt
@@ -13,6 +13,7 @@ import com.pixelized.shared.lwa.model.inventory.Inventory
import com.pixelized.shared.lwa.model.item.Item
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -45,6 +46,7 @@ class CharacterDetailInventoryFactory(
addItemAction: StateFlow,
initialValue: () -> CharacterDetailInventoryUio?,
): StateFlow {
+ val showFilterFlow = MutableStateFlow(false)
val filterFlow = createLwaTextFieldFlow(
label = getString(Res.string.character__inventory__filter_inventory__label),
value = "",
@@ -56,6 +58,7 @@ class CharacterDetailInventoryFactory(
) { inventory, items, filter ->
convertToCharacterInventoryUio(
characterSheetId = characterSheetId,
+ showFilter = showFilterFlow,
filter = filterFlow.createLwaTextField(),
addItemAction = addItemAction,
purse = inventory.purse,
@@ -71,6 +74,7 @@ class CharacterDetailInventoryFactory(
private fun convertToCharacterInventoryUio(
characterSheetId: String?,
+ showFilter: StateFlow,
filter: LwaTextFieldUio,
addItemAction: StateFlow,
purse: Inventory.Purse?,
@@ -86,6 +90,7 @@ class CharacterDetailInventoryFactory(
silver = purse?.silver ?: 0,
copper = purse?.copper ?: 0,
),
+ showFilter = showFilter,
filter = filter,
addItemAction = addItemAction,
items = inventory
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryPurse.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryPurse.kt
index da0c79f..3cde458 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryPurse.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryPurse.kt
@@ -10,13 +10,11 @@ import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -29,11 +27,6 @@ import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.currency.Currency
import com.pixelized.desktop.lwa.ui.composable.currency.CurrencyUio
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.painterResource
@Stable
data class PurseUio(
@@ -57,63 +50,58 @@ fun InventoryPurse(
onPurse: () -> Unit,
) {
Row(
- modifier = modifier,
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Start,
+ modifier = Modifier
+ .clip(shape = MaterialTheme.lwa.shapes.item)
+ .clickable { onPurse() }
+ .padding(paddingValues = paddings)
+ .then(other = modifier),
+ horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
- Row(
- modifier = Modifier
- .clip(shape = MaterialTheme.lwa.shapes.item)
- .clickable { onPurse() }
- .padding(paddingValues = paddings),
- horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
- ) {
- Row(verticalAlignment = Alignment.Bottom) {
- Currency(
- currency = CurrencyUio.Gold,
+ Row(verticalAlignment = Alignment.Bottom) {
+ Currency(
+ currency = CurrencyUio.Gold,
+ )
+ AnimatedContent(
+ targetState = purse.gold,
+ transitionSpec = coinTransitionSpec(),
+ ) {
+ Text(
+ style = MaterialTheme.lwa.typography.system.body1,
+ fontWeight = FontWeight.Bold,
+ text = "$it",
)
- AnimatedContent(
- targetState = purse.gold,
- transitionSpec = coinTransitionSpec(),
- ) {
- Text(
- style = MaterialTheme.lwa.typography.system.body1,
- fontWeight = FontWeight.Bold,
- text = "$it",
- )
- }
}
+ }
- Row(verticalAlignment = Alignment.Bottom) {
- Currency(
- currency = CurrencyUio.Silver,
+ Row(verticalAlignment = Alignment.Bottom) {
+ Currency(
+ currency = CurrencyUio.Silver,
+ )
+ AnimatedContent(
+ targetState = purse.silver,
+ transitionSpec = coinTransitionSpec(),
+ ) {
+ Text(
+ style = MaterialTheme.lwa.typography.system.body1,
+ fontWeight = FontWeight.Bold,
+ text = "$it",
)
- AnimatedContent(
- targetState = purse.silver,
- transitionSpec = coinTransitionSpec(),
- ) {
- Text(
- style = MaterialTheme.lwa.typography.system.body1,
- fontWeight = FontWeight.Bold,
- text = "$it",
- )
- }
}
+ }
- Row(verticalAlignment = Alignment.Bottom) {
- Currency(
- currency = CurrencyUio.Copper,
+ Row(verticalAlignment = Alignment.Bottom) {
+ Currency(
+ currency = CurrencyUio.Copper,
+ )
+ AnimatedContent(
+ targetState = purse.copper,
+ transitionSpec = coinTransitionSpec(),
+ ) {
+ Text(
+ style = MaterialTheme.lwa.typography.system.body1,
+ fontWeight = FontWeight.Bold,
+ text = "$it",
)
- AnimatedContent(
- targetState = purse.copper,
- transitionSpec = coinTransitionSpec(),
- ) {
- Text(
- style = MaterialTheme.lwa.typography.system.body1,
- fontWeight = FontWeight.Bold,
- text = "$it",
- )
- }
}
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonRoll.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonRoll.kt
index 0721040..20af6b8 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonRoll.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonRoll.kt
@@ -23,14 +23,12 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
@@ -39,6 +37,7 @@ import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
import org.jetbrains.compose.resources.painterResource
+import androidx.compose.runtime.saveable.Saver as ComposeSaver
@Stable
data class CharacterRibbonRollUio(
@@ -64,6 +63,24 @@ class CharacterRibbonRollAnimation(
animatedRotation = Animatable(rotation),
animatedScale = Animatable(scale),
)
+
+ object Saver : ComposeSaver> {
+ override fun restore(value: List): CharacterRibbonRollAnimation {
+ return CharacterRibbonRollAnimation(
+ animatedAlpha = Animatable(value[0]),
+ animatedRotation = Animatable(value[1]),
+ animatedScale = Animatable(value[2]),
+ )
+ }
+
+ override fun SaverScope.save(value: CharacterRibbonRollAnimation): List {
+ return listOf(
+ value.animatedAlpha.value,
+ value.animatedRotation.value,
+ value.animatedScale.value,
+ )
+ }
+ }
}
@Composable
@@ -85,10 +102,12 @@ fun CharacterRibbonRoll(
}
) {
val animation = diceIconAnimation(
- key = it?.rollId ?: Unit,
+ key = it?.rollId,
rollDelay = it?.hideDelay ?: 1000,
)
- val color = animateColorAsState(targetValue = it?.tint ?: Color.Transparent)
+ val color = animateColorAsState(
+ targetValue = it?.tint ?: Color.Transparent,
+ )
Box(
modifier = Modifier.graphicsLayer {
@@ -124,10 +143,13 @@ fun CharacterRibbonRoll(
@Composable
private fun diceIconAnimation(
- key: Any = Unit,
+ key: String?,
rollDelay: Int,
): CharacterRibbonRollAnimation {
- val animation = remember(key) {
+ val animation = rememberSaveable(
+ key = key,
+ saver = CharacterRibbonRollAnimation.Saver,
+ ) {
CharacterRibbonRollAnimation()
}
LaunchedEffect(key) {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/TextMessageFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/TextMessageFactory.kt
index 0148b95..373c3a4 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/TextMessageFactory.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/TextMessageFactory.kt
@@ -17,6 +17,7 @@ import com.pixelized.shared.lwa.protocol.websocket.RollEvent
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.chat__characteristic_change__hp_down
+import lwacharactersheet.composeapp.generated.resources.chat__characteristic_change__hp_down_armor
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
@@ -38,7 +39,7 @@ class TextMessageFactory(
return when (message) {
is RollEvent -> {
val sheetPreview = characterSheetRepository
- .characterPreview(characterId = message.characterSheetId)
+ .characterPreview(characterSheetId = message.characterSheetId)
?: return null
val isGm = settings.isGameMaster ?: false
@@ -72,7 +73,7 @@ class TextMessageFactory(
if ((isInParty || isInNpcs).not()) return null
// get the character sheet
val sheetPreview = characterSheetRepository
- .characterPreview(characterId = message.characterSheetId)
+ .characterPreview(characterSheetId = message.characterSheetId)
?: return null
DiminishedTextMessageUio(
@@ -94,16 +95,19 @@ class TextMessageFactory(
?: return null
// We are talking about damage / consumption there so old value have to be put first.
val value = message.oldValue - message.damage
+ val armor = message.armor
CharacteristicTextMessageUio(
id = "${message.timestamp}-${message.characterSheetId}-Damage",
timestamp = formatTime.format(message.timestamp),
label = when {
- value < 0 -> Res.string.chat__characteristic_change__hp_down
- else -> Res.string.chat__characteristic_change__hp_up
+ value >= 0 -> Res.string.chat__characteristic_change__hp_up
+ armor != null -> Res.string.chat__characteristic_change__hp_down_armor
+ else -> Res.string.chat__characteristic_change__hp_down
},
character = sheet.name,
value = abs(value),
+ armor = message.armor,
)
}
@@ -127,6 +131,7 @@ class TextMessageFactory(
},
character = sheet.name,
value = abs(value),
+ armor = 0,
)
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/messages/CharacteristicTextMessage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/messages/CharacteristicTextMessage.kt
index 9e31927..40568f8 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/messages/CharacteristicTextMessage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/messages/CharacteristicTextMessage.kt
@@ -21,6 +21,7 @@ data class CharacteristicTextMessageUio(
val label: StringResource,
val character: String,
val value: Int,
+ val armor: Int?,
) : TextMessage
@Composable
@@ -52,7 +53,7 @@ fun CharacteristicTextMessage(
style = MaterialTheme.lwa.typography.chat.text,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
- text = stringResource(message.label, message.character, message.value),
+ text = stringResource(message.label, message.character, message.value, message.armor ?: 0),
)
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt
index cb1bdb9..79899c8 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt
@@ -126,12 +126,16 @@ fun GMCharacterPage(
characteristicDialogViewModel.changeSubCharacteristic(
characterSheetId = dialog.characterSheetId,
characteristic = dialog.characteristic,
- value = dialog.value().text.toIntOrNull() ?: 0,
+ useArmor= dialog.enableArmor?.checked?.value == true,
+ value = dialog.value.valueFlow.value.toIntOrNull() ?: 0,
)
characteristicDialogViewModel.hideSubCharacteristicDialog()
blurController.hide()
}
},
+ onSwapSign = {
+ characteristicDialogViewModel.swapCharacteristicSign()
+ },
onDismissRequest = {
characteristicDialogViewModel.hideSubCharacteristicDialog()
blurController.hide()
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt
index 9650cc7..57dd396 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/size/LwaSize.kt
@@ -14,7 +14,6 @@ data class LwaSize(
) {
@Stable
data class Portrait(
- val maximized: DpSize,
val minimized: DpSize,
)
@@ -30,7 +29,6 @@ data class LwaSize(
fun lwaSize(
portrait: LwaSize.Portrait = LwaSize.Portrait(
minimized = DpSize(width = 96.dp, height = 128.dp),
- maximized = DpSize(width = 512.dp, height = 512.dp),
),
sheet: LwaSize.Sheet = LwaSize.Sheet(
subCategory = 14.dp,
diff --git a/server/src/main/kotlin/Module.kt b/server/src/main/kotlin/Module.kt
index e2ca61a..2b0345d 100644
--- a/server/src/main/kotlin/Module.kt
+++ b/server/src/main/kotlin/Module.kt
@@ -12,6 +12,8 @@ import com.pixelized.server.lwa.model.tag.TagService
import com.pixelized.server.lwa.model.tag.TagStore
import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.utils.PathProvider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import org.koin.core.module.dsl.createdAtStart
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
@@ -29,6 +31,9 @@ val toolsDependencies
single {
PathProvider(appName = "LwaServer")
}
+ factory {
+ CoroutineScope(Job())
+ }
}
val engineDependencies
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt
index e450290..91bb091 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt
@@ -32,7 +32,7 @@ class AlterationService(
}
@Throws
- fun save(
+ suspend fun save(
json: AlterationJson,
create: Boolean,
) {
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt
index 58a1113..2414213 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt
@@ -12,11 +12,11 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.File
import java.text.Collator
@@ -24,15 +24,14 @@ import java.text.Collator
class AlterationStore(
private val pathProvider: PathProvider,
private val factory: AlterationJsonFactory,
- private val json: Json,
+ private val jsonSerializer: Json,
+ scope: CoroutineScope,
) {
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
private val alterationFlow = MutableStateFlow>(emptyList())
init {
- // build a coroutine scope for async calls
- val scope = CoroutineScope(Dispatchers.IO + Job())
// load the initial data
scope.launch {
updateAlterationFlow()
@@ -41,7 +40,7 @@ class AlterationStore(
fun alterationsFlow(): StateFlow> = alterationFlow
- fun updateAlterationFlow() {
+ suspend fun updateAlterationFlow() {
alterationFlow.value = try {
load()
} catch (exception: Exception) {
@@ -52,104 +51,110 @@ class AlterationStore(
@Throws(
FileReadException::class,
+ JsonCodingException::class,
JsonConversionException::class,
)
- private fun load(
+ private suspend fun load(
directory: File = this.directory,
): List {
- return directory
- .listFiles()
- ?.mapNotNull { file ->
- val json = try {
- file.readText(charset = Charsets.UTF_8)
- } catch (exception: Exception) {
- throw FileReadException(root = exception)
+ return withContext(Dispatchers.IO) {
+ directory
+ .listFiles()
+ ?.mapNotNull { file ->
+ val json = try {
+ file.readText(charset = Charsets.UTF_8)
+ } catch (exception: Exception) {
+ throw FileReadException(root = exception)
+ }
+ // Guard, if the json is blank no alteration have been save, ignore this file.
+ if (json.isBlank()) {
+ return@mapNotNull null
+ }
+ // decode the file
+ val data = try {
+ jsonSerializer.decodeFromString(json)
+ } catch (exception: Exception) {
+ throw JsonCodingException(root = exception)
+ }
+ // parse the json string.
+ val alterations = try {
+ factory.convertFromJson(data)
+ } catch (exception: Exception) {
+ throw JsonConversionException(root = exception)
+ }
+ return@mapNotNull alterations
}
- // Guard, if the json is blank no alteration have been save, ignore this file.
- if (json.isBlank()) {
- return@mapNotNull null
- }
- // decode the file
- val data = try {
- this.json.decodeFromString(json)
- } catch (exception: Exception) {
- throw JsonCodingException(root = exception)
- }
- // parse the json string.
- try {
- factory.convertFromJson(data)
- } catch (exception: Exception) {
- throw JsonConversionException(root = exception)
- }
- }
- ?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
- ?: emptyList()
+ ?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
+ ?: emptyList()
+ }
}
@Throws(
BusinessException::class,
- JsonConversionException::class,
- JsonCodingException::class,
FileWriteException::class,
+ JsonCodingException::class,
+ JsonConversionException::class,
)
- fun save(
+ suspend fun save(
json: AlterationJson,
create: Boolean,
) {
- val file = alterationFile(id = json.id)
- // Guard case on update alteration
- if (create && file.exists()) {
- throw BusinessException(
- message = "Alteration already exist, creation is impossible.",
- )
- }
- // Transform the json into the model.
- val alteration = try {
- factory.convertFromJson(json)
- } catch (exception: Exception) {
- throw JsonConversionException(root = exception)
- }
- if (alteration.id.isEmpty()) {
- throw BusinessException(
- message = "Alteration 'id' is a mandatory field.",
- code = APIResponse.ErrorCode.AlterationId,
- )
- }
- if (alteration.metadata.name.isEmpty()) {
- throw BusinessException(
- message = "Alteration 'name' is a mandatory field.",
- code = APIResponse.ErrorCode.AlterationName,
- )
- }
- // Encode the json into a string.
- val data = try {
- this.json.encodeToString(json)
- } catch (exception: Exception) {
- throw JsonCodingException(root = exception)
- }
- // Write the alteration into a file.
- try {
- file.writeText(
- text = data,
- charset = Charsets.UTF_8,
- )
- } catch (exception: Exception) {
- throw FileWriteException(root = exception)
- }
- // Update the dataflow.
- alterationFlow.update { alterations ->
- val index = alterations.indexOfFirst { it.id == json.id }
- alterations.toMutableList()
- .also {
- if (index >= 0) {
- it[index] = alteration
- } else {
- it.add(alteration)
+ withContext(Dispatchers.IO) {
+ val file = alterationFile(id = json.id)
+ // Guard case on update alteration
+ if (create && file.exists()) {
+ throw BusinessException(
+ message = "Alteration already exist, creation is impossible.",
+ )
+ }
+ // Transform the json into the model.
+ val alteration = try {
+ factory.convertFromJson(json)
+ } catch (exception: Exception) {
+ throw JsonConversionException(root = exception)
+ }
+ if (alteration.id.isEmpty()) {
+ throw BusinessException(
+ message = "Alteration 'id' is a mandatory field.",
+ code = APIResponse.ErrorCode.AlterationId,
+ )
+ }
+ if (alteration.metadata.name.isEmpty()) {
+ throw BusinessException(
+ message = "Alteration 'name' is a mandatory field.",
+ code = APIResponse.ErrorCode.AlterationName,
+ )
+ }
+ // Encode the json into a string.
+ val data = try {
+ jsonSerializer.encodeToString(json)
+ } catch (exception: Exception) {
+ throw JsonCodingException(root = exception)
+ }
+ // Write the alteration into a file.
+ try {
+ file.writeText(
+ text = data,
+ charset = Charsets.UTF_8,
+ )
+ } catch (exception: Exception) {
+ throw FileWriteException(root = exception)
+ }
+ // Update the dataflow.
+ alterationFlow.update { alterations ->
+ val index = alterations.indexOfFirst { it.id == json.id }
+ alterations.toMutableList()
+ .also {
+ if (index >= 0) {
+ it[index] = alteration
+ } else {
+ it.add(alteration)
+ }
}
- }
- .sortedWith(compareBy(Collator.getInstance()) {
- it.metadata.name
- })
+ .sortedWith(compareBy(Collator.getInstance()) {
+ it.metadata.name
+ })
+ }
}
}
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt
index e71b8fa..7521cac 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt
@@ -39,7 +39,7 @@ class CampaignService(
}
@Throws
- fun addCharacter(
+ suspend fun addCharacter(
characterSheetId: String,
) {
// Check if the character is already in the campaign.
@@ -57,7 +57,7 @@ class CampaignService(
}
@Throws
- fun addNpc(
+ suspend fun addNpc(
characterSheetId: String,
) {
// Check if the character is already in the campaign.
@@ -75,7 +75,7 @@ class CampaignService(
}
@Throws
- fun removeCharacter(
+ suspend fun removeCharacter(
characterSheetId: String,
) {
// Check if the character is in the campaign.
@@ -93,7 +93,7 @@ class CampaignService(
}
@Throws
- fun removeNpc(
+ suspend fun removeNpc(
characterSheetId: String,
) {
// Check if the character is in the campaign.
@@ -111,7 +111,7 @@ class CampaignService(
}
@Throws
- fun setScene(
+ suspend fun setScene(
scene: Campaign.Scene,
) {
// save the campaign to the disk + update the flow.
@@ -122,7 +122,7 @@ class CampaignService(
// Data manipulation through WebSocket.
- fun updateToggleParty() {
+ suspend fun updateToggleParty() {
store.save(
campaign = campaign.copy(
options = campaign.options.copy(
@@ -132,7 +132,7 @@ class CampaignService(
)
}
- fun updateToggleNpc() {
+ suspend fun updateToggleNpc() {
store.save(
campaign = campaign.copy(
options = campaign.options.copy(
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt
index f612cea..63bc43d 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt
@@ -15,21 +15,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.File
class CampaignStore(
private val pathProvider: PathProvider,
private val factory: CampaignJsonFactory,
- private val json: Json,
+ private val jsonSerializer: Json,
+ scope: CoroutineScope,
) {
private val campaignFlow = MutableStateFlow(value = Campaign.empty())
init {
// create the directory if needed.
File(pathProvider.campaignPath()).also { it.mkdirs() }
- // build a coroutine scope for async calls
- val scope = CoroutineScope(Dispatchers.IO + Job())
// load the initial data
scope.launch {
updateCampaignFlow()
@@ -38,7 +38,7 @@ class CampaignStore(
fun campaignFlow(): StateFlow = campaignFlow
- fun updateCampaignFlow() {
+ suspend fun updateCampaignFlow() {
campaignFlow.value = try {
load()
} catch (exception: Exception) {
@@ -52,29 +52,31 @@ class CampaignStore(
JsonCodingException::class,
JsonConversionException::class,
)
- fun load(): Campaign {
- val file = campaignFile()
- // Read the campaign file.
- val data = try {
- file.readText(charset = Charsets.UTF_8)
- } catch (exception: Exception) {
- throw FileReadException(root = exception)
+ suspend fun load(): Campaign {
+ return withContext(Dispatchers.IO) {
+ val file = campaignFile()
+ // Read the campaign file.
+ val data = try {
+ file.readText(charset = Charsets.UTF_8)
+ } catch (exception: Exception) {
+ throw FileReadException(root = exception)
+ }
+ // Guard, if the file is empty we load a default campaign.
+ if (data.isBlank()) return@withContext Campaign.empty()
+ // Decode the json into a string.
+ val json = try {
+ jsonSerializer.decodeFromString(data)
+ } catch (exception: Exception) {
+ throw JsonCodingException(root = exception)
+ }
+ // Convert from the Json format
+ val campaign = try {
+ factory.convertFromJson(json)
+ } catch (exception: Exception) {
+ throw JsonConversionException(root = exception)
+ }
+ return@withContext campaign
}
- // Guard, if the file is empty we load a default campaign.
- if (data.isBlank()) return Campaign.empty()
- // Decode the json into a string.
- val json = try {
- this.json.decodeFromString(data)
- } catch (exception: Exception) {
- throw JsonCodingException(root = exception)
- }
- // Convert from the Json format
- val campaign = try {
- factory.convertFromJson(json)
- } catch (exception: Exception) {
- throw JsonConversionException(root = exception)
- }
- return campaign
}
@Throws(
@@ -82,31 +84,33 @@ class CampaignStore(
JsonCodingException::class,
FileWriteException::class,
)
- fun save(campaign: Campaign) {
- // Transform the json into the model.
- val json = try {
- factory.convertToJson(campaign = campaign)
- } catch (exception: Exception) {
- throw JsonConversionException(root = exception)
+ suspend fun save(campaign: Campaign) {
+ withContext(Dispatchers.IO) {
+ // Transform the json into the model.
+ val json = try {
+ factory.convertToJson(campaign = campaign)
+ } catch (exception: Exception) {
+ throw JsonConversionException(root = exception)
+ }
+ // Encode the json into a string.
+ val data = try {
+ jsonSerializer.encodeToString(json)
+ } catch (exception: Exception) {
+ throw JsonCodingException(root = exception)
+ }
+ // Write the file
+ try {
+ val file = campaignFile()
+ file.writeText(
+ text = data,
+ charset = Charsets.UTF_8,
+ )
+ } catch (exception: Exception) {
+ throw FileWriteException(root = exception)
+ }
+ // Update the dataflow.
+ campaignFlow.update { campaign }
}
- // Encode the json into a string.
- val data = try {
- this.json.encodeToString(json)
- } catch (exception: Exception) {
- throw JsonCodingException(root = exception)
- }
- // Write the file
- try {
- val file = campaignFile()
- file.writeText(
- text = data,
- charset = Charsets.UTF_8,
- )
- } catch (exception: Exception) {
- throw FileWriteException(root = exception)
- }
- // Update the dataflow.
- campaignFlow.update { campaign }
}
private fun campaignFile(): File {
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt
index 5005af7..a02611e 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt
@@ -55,7 +55,7 @@ class CharacterSheetService(
}
@Throws
- fun deleteCharacterSheet(
+ suspend fun deleteCharacterSheet(
characterSheetId: String,
) {
characterStore.delete(
@@ -65,7 +65,7 @@ class CharacterSheetService(
// Data manipulation through WebSocket.
- fun updateAlteration(
+ suspend fun updateAlteration(
characterSheetId: String,
alterationId: String,
active: Boolean,
@@ -97,7 +97,7 @@ class CharacterSheetService(
}
}
- fun updateDamage(
+ suspend fun updateDamage(
characterSheetId: String,
damage: Int,
) {
@@ -110,7 +110,7 @@ class CharacterSheetService(
}
}
- fun updateDiminished(
+ suspend fun updateDiminished(
characterSheetId: String,
diminished: Int,
) {
@@ -123,7 +123,7 @@ class CharacterSheetService(
}
}
- fun updateFatigue(
+ suspend fun updateFatigue(
characterSheetId: String,
fatigue: Int,
) {
@@ -136,7 +136,7 @@ class CharacterSheetService(
}
}
- fun updateCharacterSkillUsage(
+ suspend fun updateCharacterSkillUsage(
characterSheetId: String,
skillId: String,
used: Boolean,
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt
index f2b3545..3cd0b3d 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt
@@ -12,11 +12,11 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.File
import java.text.Collator
@@ -24,14 +24,13 @@ import java.text.Collator
class CharacterSheetStore(
private val pathProvider: PathProvider,
private val factory: CharacterSheetJsonFactory,
- private val json: Json,
+ private val jsonSerializer: Json,
+ scope: CoroutineScope,
) {
private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() }
private val characterSheetsFlow = MutableStateFlow>(value = emptyList())
init {
- // build a coroutine scope for async calls
- val scope = CoroutineScope(Dispatchers.IO + Job())
// load the initial data
scope.launch {
updateCharacterFlow()
@@ -40,7 +39,7 @@ class CharacterSheetStore(
fun characterSheetsFlow(): StateFlow> = characterSheetsFlow
- fun updateCharacterFlow() {
+ suspend fun updateCharacterFlow() {
characterSheetsFlow.value = try {
load()
} catch (exception: Exception) {
@@ -54,112 +53,118 @@ class CharacterSheetStore(
JsonCodingException::class,
JsonConversionException::class,
)
- fun load(
+ suspend fun load(
directory: File = this.directory,
): List {
- return directory
- .listFiles()
- ?.mapNotNull { file ->
- val json = try {
- file.readText(charset = Charsets.UTF_8)
- } catch (exception: Exception) {
- throw FileReadException(root = exception)
+ return withContext(Dispatchers.IO) {
+ directory
+ .listFiles()
+ ?.mapNotNull { file ->
+ val json = try {
+ file.readText(charset = Charsets.UTF_8)
+ } catch (exception: Exception) {
+ throw FileReadException(root = exception)
+ }
+ // Guard, if the json is blank no character have been save, ignore this file.
+ if (json.isBlank()) {
+ return@mapNotNull null
+ }
+ // decode the file
+ val data = try {
+ jsonSerializer.decodeFromString(json)
+ } catch (exception: Exception) {
+ throw JsonCodingException(root = exception)
+ }
+ // parse the json string.
+ try {
+ factory.convertFromJson(data)
+ } catch (exception: Exception) {
+ throw JsonConversionException(root = exception)
+ }
}
- // Guard, if the json is blank no character have been save, ignore this file.
- if (json.isBlank()) {
- return@mapNotNull null
- }
- // decode the file
- val data = try {
- this.json.decodeFromString(json)
- } catch (exception: Exception) {
- throw JsonCodingException(root = exception)
- }
- // parse the json string.
- try {
- factory.convertFromJson(data)
- } catch (exception: Exception) {
- throw JsonConversionException(root = exception)
- }
- }
- ?.sortedWith(compareBy(Collator.getInstance()) { it.name })
- ?: emptyList()
+ ?.sortedWith(compareBy(Collator.getInstance()) { it.name })
+ ?: emptyList()
+ }
}
@Throws(
BusinessException::class,
- JsonConversionException::class,
- JsonCodingException::class,
FileWriteException::class,
+ JsonCodingException::class,
+ JsonConversionException::class,
)
- fun save(
+ suspend fun save(
sheet: CharacterSheet,
create: Boolean,
) {
- val file = characterSheetFile(id = sheet.id)
- // Guard case on update alteration
- if (create && file.exists()) {
- throw BusinessException(message = "Character already exist, creation is impossible.")
- }
- // Transform the json into the model.
- val json = try {
- factory.convertToJson(sheet = sheet)
- } catch (exception: Exception) {
- throw JsonConversionException(root = exception)
- }
- // Encode the json into a string.
- val data = try {
- this.json.encodeToString(json)
- } catch (exception: Exception) {
- throw JsonCodingException(root = exception)
- }
- // write the character file.
- try {
- file.writeText(
- text = data,
- charset = Charsets.UTF_8,
- )
- } catch (exception: Exception) {
- throw FileWriteException(root = exception)
- }
- // Update the dataflow.
- characterSheetsFlow.update { characters ->
- characters.toMutableList()
- .also { data ->
- val index = data.indexOfFirst { it.id == sheet.id }
- if (index >= 0) {
- data[index] = sheet
- } else {
- data.add(sheet)
+ withContext(Dispatchers.IO) {
+ val file = characterSheetFile(id = sheet.id)
+ // Guard case on update alteration
+ if (create && file.exists()) {
+ throw BusinessException(message = "Character already exist, creation is impossible.")
+ }
+ // Transform the json into the model.
+ val json = try {
+ factory.convertToJson(sheet = sheet)
+ } catch (exception: Exception) {
+ throw JsonConversionException(root = exception)
+ }
+ // Encode the json into a string.
+ val data = try {
+ jsonSerializer.encodeToString(json)
+ } catch (exception: Exception) {
+ throw JsonCodingException(root = exception)
+ }
+ // write the character file.
+ try {
+ file.writeText(
+ text = data,
+ charset = Charsets.UTF_8,
+ )
+ } catch (exception: Exception) {
+ throw FileWriteException(root = exception)
+ }
+ // Update the dataflow.
+ characterSheetsFlow.update { characters ->
+ characters.toMutableList()
+ .also { data ->
+ val index = data.indexOfFirst { it.id == sheet.id }
+ if (index >= 0) {
+ data[index] = sheet
+ } else {
+ data.add(sheet)
+ }
}
- }
- .sortedWith(compareBy(Collator.getInstance()) {
- it.name
- })
+ .sortedWith(compareBy(Collator.getInstance()) {
+ it.name
+ })
+ }
}
}
@Throws(BusinessException::class)
- fun delete(characterSheetId: String) {
- val file = characterSheetFile(id = characterSheetId)
- // Guard case on the file existence.
- if (file.exists().not()) {
- throw BusinessException(
- message = "Character file with id:$characterSheetId doesn't not exist.",
- code = APIResponse.ErrorCode.CharacterSheetId
- )
- }
- // Guard case on the file deletion
- if (file.delete().not()) {
- throw BusinessException(
- message = "Character file have not been deleted for unknown reason.",
- )
- }
- // Update the data model with the deleted character.
- characterSheetsFlow.update { characters ->
- characters.toMutableList()
- .also { data -> data.removeIf { it.id == characterSheetId } }
- .sortedWith(compareBy(Collator.getInstance()) { it.name })
+ suspend fun delete(characterSheetId: String) {
+ withContext(Dispatchers.IO) {
+ val file = characterSheetFile(id = characterSheetId)
+ // Guard case on the file existence.
+ if (file.exists().not()) {
+ throw BusinessException(
+ message = "Character file with id:$characterSheetId doesn't not exist.",
+ code = APIResponse.ErrorCode.CharacterSheetId
+ )
+ }
+ // Guard case on the file deletion
+ if (file.delete().not()) {
+ throw BusinessException(
+ message = "Character file have not been deleted for unknown reason.",
+ )
+ }
+ // Update the data model with the deleted character.
+ characterSheetsFlow.update { characters ->
+ characters.toMutableList()
+ .also { data -> data.removeIf { it.id == characterSheetId } }
+ .sortedWith(compareBy(Collator.getInstance()) { it.name })
+ }
}
}
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryService.kt
index 5aa75d4..9c0ba11 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryService.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryService.kt
@@ -40,7 +40,7 @@ class InventoryService(
}
@Throws
- fun updatePurse(
+ suspend fun updatePurse(
purse: ApiPurseJson,
) {
val inventory = inventory(
@@ -59,7 +59,7 @@ class InventoryService(
}
@Throws
- fun save(
+ suspend fun save(
inventoryJson: InventoryJson,
create: Boolean,
) {
@@ -70,12 +70,12 @@ class InventoryService(
}
@Throws
- fun delete(characterSheetId: String) {
+ suspend fun delete(characterSheetId: String) {
inventoryStore.delete(characterSheetId = characterSheetId)
}
@Throws
- fun createInventoryItem(
+ suspend fun createInventoryItem(
characterSheetId: String,
itemId: String,
count: Float,
@@ -107,7 +107,7 @@ class InventoryService(
}
@Throws
- fun changeInventoryItemCount(
+ suspend fun changeInventoryItemCount(
characterSheetId: String,
inventoryId: String,
count: Float,
@@ -145,7 +145,7 @@ class InventoryService(
}
@Throws
- fun consumeInventoryItem(
+ suspend fun consumeInventoryItem(
characterSheetId: String,
inventoryId: String,
) {
@@ -185,7 +185,7 @@ class InventoryService(
}
@Throws
- fun equipInventoryItem(
+ suspend fun equipInventoryItem(
characterSheetId: String,
inventoryId: String,
equip: Boolean,
@@ -223,7 +223,7 @@ class InventoryService(
}
@Throws
- fun deleteInventoryItem(
+ suspend fun deleteInventoryItem(
characterSheetId: String,
inventoryId: String,
) {
diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryStore.kt
index b9f3fed..4c8f32a 100644
--- a/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryStore.kt
+++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/inventory/InventoryStore.kt
@@ -12,26 +12,24 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.File
-import java.util.UUID
class InventoryStore(
private val pathProvider: PathProvider,
private val factory: InventoryJsonFactory,
- private val json: Json,
+ private val jsonSerializer: Json,
+ scope: CoroutineScope,
) {
private val directory = File(pathProvider.inventoryPath()).also { it.mkdirs() }
private val inventoryFlow = MutableStateFlow