Add PP and HP managmenet

This commit is contained in:
Thomas Andres Gomez 2024-11-11 22:32:01 +01:00
parent f6d026d50f
commit 3705fbd947
5 changed files with 305 additions and 8 deletions

View file

@ -4,66 +4,105 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Node
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.*
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__cha
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__con
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__dex
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__hei
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__int
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__pow
import lwacharactersheet.composeapp.generated.resources.character_sheet__characteristics__str
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point
import org.jetbrains.compose.resources.getString
class CharacterSheetFactory {
companion object {
const val HP = "HP"
const val PP = "PP"
}
suspend fun convertToUio(model: CharacterSheet): CharacterSheetPageUio {
return CharacterSheetPageUio(
id = model.id,
name = model.name,
characteristics = listOf(
Characteristic(
id = "STR",
label = getString(Res.string.character_sheet__characteristics__str),
value = "${model.strength}",
editable = false,
),
Characteristic(
id = "DEX",
label = getString(Res.string.character_sheet__characteristics__dex),
value = "${model.dexterity}",
editable = false,
),
Characteristic(
id = "CON",
label = getString(Res.string.character_sheet__characteristics__con),
value = "${model.constitution}",
editable = false,
),
Characteristic(
id = "HEI",
label = getString(Res.string.character_sheet__characteristics__hei),
value = "${model.height}",
editable = false,
),
Characteristic(
id = "INT",
label = getString(Res.string.character_sheet__characteristics__int),
value = "${model.intelligence}",
editable = false,
),
Characteristic(
id = "POW",
label = getString(Res.string.character_sheet__characteristics__pow),
value = "${model.power}",
editable = false,
),
Characteristic(
id = "CHA",
label = getString(Res.string.character_sheet__characteristics__cha),
value = "${model.charisma}",
editable = false,
),
),
subCharacteristics = listOf(
Characteristic(
id = "MOV",
label = getString(Res.string.character_sheet__sub_characteristics__movement),
value = "${model.movement}",
editable = false,
),
Characteristic(
id = HP,
label = getString(Res.string.character_sheet__sub_characteristics__hit_point),
value = "${model.currentHp}/${model.maxHp}",
editable = true,
),
Characteristic(
id = PP,
label = getString(Res.string.character_sheet__sub_characteristics__power_point),
value = "${model.currentPP}/${model.maxPP}",
editable = true,
),
Characteristic(
id = "DMG",
label = getString(Res.string.character_sheet__sub_characteristics__damage_bonus),
value = model.damageBonus,
editable = false,
),
Characteristic(
id = "ARMOR",
label = getString(Res.string.character_sheet__sub_characteristics__armor),
value = "${model.armor}",
editable = false,
),
),
skills = model.skills.mapNotNull {

View file

@ -45,6 +45,8 @@ import com.pixelized.desktop.lwa.composable.overlay.BlurOverlay
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlayViewModel
import com.pixelized.desktop.lwa.navigation.LocalScreenController
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialog
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.CharacterSheetStatDialog
import com.pixelized.desktop.lwa.screen.roll.RollPage
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
import kotlinx.coroutines.launch
@ -70,8 +72,10 @@ data class CharacterSheetPageUio(
) {
@Stable
data class Characteristic(
val id: String,
val label: String,
val value: String,
val editable: Boolean,
)
@Stable
@ -132,6 +136,9 @@ fun CharacterSheetPage(
)
overlayViewModel.show()
},
onSubCharacteristic = {
viewModel.showSubCharacteristicDialog(id = it.id)
},
onSkill = { node ->
rollViewModel.prepareRoll(sheet = sheet, node = node)
overlayViewModel.show()
@ -161,6 +168,20 @@ fun CharacterSheetPage(
viewModel.hideConfirmCharacterDeletionDialog()
},
)
CharacterSheetStatDialog(
dialog = viewModel.statChangeDialog,
onConfirm = {
viewModel.changeSubCharacteristic(
characteristicId = it.id,
value = it.value().text.toIntOrNull() ?: 0,
)
viewModel.hideSubCharacteristicDialog()
},
onDismissRequest = {
viewModel.hideSubCharacteristicDialog()
}
)
}
@Composable
@ -171,6 +192,7 @@ fun CharacterSheetPageContent(
onEdit: () -> Unit,
onDelete: () -> Unit,
onCharacteristic: (characteristic: CharacterSheetPageUio.Characteristic) -> Unit,
onSubCharacteristic: (characteristic: CharacterSheetPageUio.Characteristic) -> Unit,
onSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
onUseSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
onRoll: (roll: CharacterSheetPageUio.Roll) -> Unit,
@ -252,9 +274,13 @@ fun CharacterSheetPageContent(
text = stringResource(Res.string.character_sheet__sub_characteristics__title),
)
characterSheet.subCharacteristics.forEach {
Characteristics(
SubCharacteristics(
modifier = Modifier.cell(),
characteristic = it,
onCharacteristic = when {
it.editable -> onSubCharacteristic
else -> null
}
)
}
}
@ -372,24 +398,32 @@ private fun Stat(
}
@Composable
private fun Characteristics(
private fun SubCharacteristics(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
characteristic: CharacterSheetPageUio.Characteristic,
onCharacteristic: ((characteristic: CharacterSheetPageUio.Characteristic) -> Unit)?,
) {
Row(
modifier = Modifier.padding(paddingValues = paddingValues).then(other = modifier),
modifier = Modifier
.clickable(enabled = characteristic.editable && onCharacteristic != null) {
onCharacteristic?.invoke(characteristic)
}
.padding(paddingValues = paddingValues).then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
text = characteristic.label
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = when (characteristic.editable && onCharacteristic != null) {
true -> MaterialTheme.colors.primary
else -> MaterialTheme.colors.onSurface
},
text = characteristic.value,
)
}

View file

@ -4,12 +4,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.CharacterSheetDeleteConfirmationDialogUio
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.StatChangeDialogUio
import com.pixelized.desktop.lwa.utils.extention.collectAsState
import kotlinx.coroutines.runBlocking
import kotlin.math.max
import kotlin.math.min
class CharacterSheetViewModel(
savedStateHandle: SavedStateHandle,
@ -24,6 +30,9 @@ class CharacterSheetViewModel(
val displayDeleteConfirmationDialog: State<CharacterSheetDeleteConfirmationDialogUio?>
get() = _displayDeleteConfirmationDialog
private val _statChangeDialog = mutableStateOf<StatChangeDialogUio?>(null)
val statChangeDialog: State<StatChangeDialogUio?> get() = _statChangeDialog
val sheet: State<CharacterSheetPageUio?>
@Composable
@Stable
@ -35,7 +44,7 @@ class CharacterSheetViewModel(
}
}
suspend fun deleteCharacter(id: String) {
fun deleteCharacter(id: String) {
repository.delete(id = id)
}
@ -74,4 +83,61 @@ class CharacterSheetViewModel(
fun hideConfirmCharacterDeletionDialog() {
_displayDeleteConfirmationDialog.value = null
}
fun showSubCharacteristicDialog(id: String) {
repository.characterSheetFlow(id = argument.id).value?.let { sheet ->
_statChangeDialog.value = when (id) {
CharacterSheetFactory.HP -> {
val value = mutableStateOf(
"${sheet.currentHp}".let {
TextFieldValue(text = it, selection = TextRange(it.length))
}
)
StatChangeDialogUio(
id = CharacterSheetFactory.HP,
value = { value.value },
onValueChange = { value.value = it },
placeholder = "${sheet.currentHp}",
maxValue = "${sheet.maxHp}",
)
}
CharacterSheetFactory.PP -> {
val value = mutableStateOf(
"${sheet.currentPP}".let {
TextFieldValue(text = it, selection = TextRange(it.length))
}
)
StatChangeDialogUio(
id = CharacterSheetFactory.PP,
value = { value.value },
onValueChange = { value.value = it },
placeholder = "${sheet.currentPP}",
maxValue = "${sheet.maxPP}",
)
}
else -> null
}
}
}
fun hideSubCharacteristicDialog() {
_statChangeDialog.value = null
}
fun changeSubCharacteristic(
characteristicId: String,
value: Int,
) {
val sheet = repository.characterSheetFlow(id = argument.id).value
val updated = when (characteristicId) {
CharacterSheetFactory.HP -> sheet?.copy(currentHp = max(0, min(sheet.maxHp, value)))
CharacterSheetFactory.PP -> sheet?.copy(currentPP = max(0, min(sheet.maxPP, value)))
else -> null
}
updated?.let {
repository.save(it)
}
}
}

View file

@ -1,4 +1,4 @@
package com.pixelized.desktop.lwa.screen.characterSheet.detail
package com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility

View file

@ -0,0 +1,158 @@
package com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
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.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
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.unit.dp
private val DefaultScrimColor = Color.Black.copy(alpha = 0.6f)
@Stable
data class StatChangeDialogUio(
val id: String,
val value: () -> TextFieldValue,
val placeholder: String,
val onValueChange: (TextFieldValue) -> Unit,
val maxValue: String,
)
@Composable
fun CharacterSheetStatDialog(
dialog: State<StatChangeDialogUio?>,
onConfirm: (StatChangeDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
AnimatedVisibility(
visible = dialog.value != null,
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = DefaultScrimColor),
)
}
AnimatedContent(
modifier = Modifier.fillMaxSize(),
targetState = dialog.value,
transitionSpec = {
val enter = fadeIn() + slideInVertically { 32 }
val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
Box(
modifier = Modifier.fillMaxSize()
) {
when (it) {
null -> Box(
modifier = Modifier,
)
else -> Dialog(
dialog = it,
onConfirm = onConfirm,
onDismissRequest = onDismissRequest,
)
}
}
}
}
@Composable
private fun Dialog(
dialog: StatChangeDialogUio,
onConfirm: (StatChangeDialogUio) -> Unit,
onDismissRequest: () -> Unit,
) {
val typography = MaterialTheme.typography
val colors = MaterialTheme.colors
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Box(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onDismissRequest,
)
.fillMaxSize()
.padding(all = 32.dp),
contentAlignment = Alignment.Center,
) {
Surface(
shape = remember { RoundedCornerShape(size = 16.dp) },
) {
Row(
modifier = Modifier.padding(all = 16.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TextField(
modifier = Modifier
.focusRequester(focusRequester = focusRequester)
.alignByBaseline()
.width(width = 120.dp),
value = dialog.value(),
textStyle = remember {
typography.body1.copy(
color = colors.primary,
textAlign = TextAlign.End,
fontWeight = FontWeight.Bold
)
},
singleLine = true,
keyboardActions = KeyboardActions { onConfirm(dialog) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
placeholder = { dialog.placeholder },
onValueChange = dialog.onValueChange,
)
Text(
modifier = Modifier.alignByBaseline(),
text = "/",
)
Text(
modifier = Modifier.alignByBaseline(),
text = dialog.maxValue,
)
}
}
}
}