diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 164a914..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 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/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/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/text/TextMessageFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/TextMessageFactory.kt index cbb6ece..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 @@ -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/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt index 5c8094d..aa60fd6 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt @@ -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) { diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt index b058d0d..b58c0e9 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt @@ -22,6 +22,7 @@ data class APIResponse( Active, Equip, Damage, + Armor, Fatigue, Diminished, Count, diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/CharacterSheetEvent.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/CharacterSheetEvent.kt index 408d131..37c24d2 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/CharacterSheetEvent.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/CharacterSheetEvent.kt @@ -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