Change the UI of damage & fatigue dialogs.

This commit is contained in:
Thomas Andres Gomez 2025-06-15 14:51:40 +02:00
parent 741bb7cf25
commit 894d8db493
15 changed files with 354 additions and 223 deletions

View file

@ -84,6 +84,7 @@
<string name="character_sheet_edit__sub_characteristics__max_hit_point">Points de vie maximum</string>
<string name="character_sheet_edit__sub_characteristics__hit_point">Points de vie</string>
<string name="character_sheet_edit__sub_characteristics__max_power_point">Points de pouvoir maximum</string>
<string name="character_sheet_edit__use_armor">Utiliser l'armure</string>
<string name="character_sheet_edit__sub_characteristics__power_point">Points de pouvoir</string>
<string name="character_sheet_edit__sub_characteristics__damage_bonus">Bonus aux dégats</string>
<string name="character_sheet_edit__sub_characteristics__armor">Armure</string>
@ -253,6 +254,7 @@
<string name="chat__diminished_change">%1$s passe à %2$d d'état diminuée</string>
<string name="chat__characteristic_change__hp_down">%1$s subit %2$d point(s) de dommage</string>
<string name="chat__characteristic_change__hp_down_armor">%1$s subit %2$d point(s) de dommage (armure : %3$d)</string>
<string name="chat__characteristic_change__hp_up">%1$s récupère %2$d point(s) de vie</string>
<string name="chat__characteristic_change__pp_down">%1$s utilise %2$d point(s) de pouvoir</string>
<string name="chat__characteristic_change__pp_up">%1$s récupère %2$d point(s) de pouvoir</string>

View file

@ -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<Boolean>,
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
)
}
}

View file

@ -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<Boolean>,
val value: LwaTextFieldUio,
val enableArmor: LwaCheckBoxUio?,
val enableConfirm: StateFlow<Boolean>,
) {
@Stable
enum class Characteristic {
@ -55,125 +59,131 @@ data class CharacterSheetCharacteristicDialogUio(
}
}
@Composable
fun CharacterSheetCharacteristicDialog(
dialog: State<CharacterSheetCharacteristicDialogUio?>,
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<CharacterSheetCharacteristicDialogUio?>,
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
}
}
}

View file

@ -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<Boolean>,
): CharacterSheetCharacteristicDialogUio? {
if (characterSheetId == null) return null
@ -32,10 +43,10 @@ class CharacterSheetCharacteristicDialogFactory(
if (characterSheet == null) return null
val alterations: Map<String, List<FieldAlteration>> = alterationRepository.activeFieldAlterations(
characterSheetId = characterSheetId,
)
val alterations: Map<String, List<FieldAlteration>> =
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)
}

View file

@ -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<CharacterSheetCharacteristicDialogUio?>(null)
val statChangeDialog: State<CharacterSheetCharacteristicDialogUio?> 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,
)
}

View file

@ -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<Boolean>,
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,

View file

@ -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<Boolean>,
)
@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,
)
}

View file

@ -6,13 +6,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
@Stable
data class LwaTextFieldFlow(
val errorFlow: MutableStateFlow<Boolean>,
val valueFlow: MutableStateFlow<String>,
val labelFlow: MutableStateFlow<String?>,
val valueFlow: MutableStateFlow<String>,
)
fun createLwaTextFieldFlow(
error: Boolean = false,
label: String,
label: String?,
value: String = "",
): LwaTextFieldFlow {
return createLwaTextFieldFlow(
@ -24,8 +24,8 @@ fun createLwaTextFieldFlow(
fun createLwaTextFieldFlow(
errorFlow: MutableStateFlow<Boolean> = MutableStateFlow(false),
valueFlow: MutableStateFlow<String>,
labelFlow: MutableStateFlow<String?>,
valueFlow: MutableStateFlow<String>,
): LwaTextFieldFlow {
return LwaTextFieldFlow(
errorFlow = errorFlow,

View file

@ -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()

View file

@ -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
@ -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,
)
}

View file

@ -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),
)
}
}

View file

@ -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()

View file

@ -18,6 +18,10 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
name = "damage",
code = APIResponse.ErrorCode.Damage,
)
val armor: Int = call.queryParameters.param(
name = "armor",
code = APIResponse.ErrorCode.Armor,
)
// fetch the character sheet
val characterSheet = characterService.character(characterSheetId)
?: error("CharacterSheet with id:$characterSheetId not found.")
@ -36,6 +40,7 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
characterSheetId = characterSheetId,
oldValue = characterSheet.damage,
damage = damage,
armor = armor,
)
)
} catch (exception: Exception) {

View file

@ -22,6 +22,7 @@ data class APIResponse<T>(
Active,
Equip,
Damage,
Armor,
Fatigue,
Diminished,
Count,

View file

@ -9,6 +9,7 @@ sealed interface CharacterSheetEvent : SocketMessage, CharacterSheetIdMessage {
data class UpdateDamage(
override val characterSheetId: String,
override val timestamp: Long,
val armor: Int?,
val oldValue: Int,
val damage: Int,
) : CharacterSheetEvent