Add spacial resource into CharacterSheet header.

This commit is contained in:
Thomas Andres Gomez 2023-12-12 12:25:13 +01:00
parent 8c1d83402c
commit 75644aad91
14 changed files with 246 additions and 33 deletions

View file

@ -1,11 +1,13 @@
package com.pixelized.rplexicon.data.model
import com.pixelized.rplexicon.R
data class CharacterSheet(
val name: String,
val race: String?,
val proficiency: Int, // Bonus de maîtrise
val level: Int, // Niveau
val characterClass: String, // Classe
val characterClass: List<Class>, // Classe
val hitPoint: Int, // Point de vie MAX
val spell1: Int?, // level 1 spell slot
val spell2: Int?, // level 2 spell slot
@ -53,9 +55,36 @@ data class CharacterSheet(
val languages: List<String>, // languages masteries
val others: List<String>, // others masteries
) {
val isWarlock: Boolean get() = characterClass.contains(CLASS_WARLOCK)
val isWarlock: Boolean get() = characterClass.contains(Class.WARLOCK)
companion object {
const val CLASS_WARLOCK = "Occultiste"
enum class Class(
val value: String,
val resource: String? = null,
val label: Int? = null,
) {
BARBARIAN(
value = "Barbare",
resource = "Rage",
label = R.string.character_sheet_title_rage
),
BARD(
value = "Barde",
resource = "Inspiration bardique",
label = R.string.character_sheet_title_inspiration
),
CLERIC(
value = "Clerc",
resource = "Conduit divin",
label = R.string.character_sheet_title_conduit
),
DRUID(value = "Druide"),
FIGHTER(value = "Guerrier"),
MONK(value = "Moine"),
PALADIN(value = "Paladin"),
RANGER(value = "Rodeur"),
ROGUE(value = "Roublard"),
SORCERER(value = "Ensorceleur"),
WARLOCK(value = "Occultiste"),
WIZARD(value = "Magicien"),
}
}

View file

@ -25,7 +25,9 @@ class AlterationParser @Inject constructor(
sheet.forEachRowIndexed { index, row ->
when (index) {
0 -> updateStructure(row = row, columns = COLUMNS + characterSheets.map { column(it.name) })
0 -> updateStructure(
row = row,
columns = COLUMNS + characterSheets.map { column(it.name) })
else -> {
// Assume that the name is the first column.
@ -56,7 +58,7 @@ class AlterationParser @Inject constructor(
characterSheets
.filter { // check if the alteration is applicable to the character
alteration.target.let { target ->
target == ALL || target == it.characterClass || target == it.race || target == it.name
target == ALL || it.characterClass.any { it.value == target } || target == it.race || target == it.name
}
}
.forEach { // check the default alteration state for that character

View file

@ -0,0 +1,32 @@
package com.pixelized.rplexicon.data.parser.characterSheet
import com.pixelized.rplexicon.data.model.CharacterSheet
import javax.inject.Inject
class CharacterClassParser @Inject constructor() {
fun parse(value: String?): List<CharacterSheet.Class> {
return value
?.split(",")
?.map { it.trim() }
?.mapNotNull {
when (it) {
CharacterSheet.Class.BARBARIAN.value -> CharacterSheet.Class.BARBARIAN
CharacterSheet.Class.BARD.value -> CharacterSheet.Class.BARD
CharacterSheet.Class.CLERIC.value -> CharacterSheet.Class.CLERIC
CharacterSheet.Class.DRUID.value -> CharacterSheet.Class.DRUID
CharacterSheet.Class.FIGHTER.value -> CharacterSheet.Class.FIGHTER
CharacterSheet.Class.MONK.value -> CharacterSheet.Class.MONK
CharacterSheet.Class.PALADIN.value -> CharacterSheet.Class.PALADIN
CharacterSheet.Class.RANGER.value -> CharacterSheet.Class.RANGER
CharacterSheet.Class.ROGUE.value -> CharacterSheet.Class.ROGUE
CharacterSheet.Class.SORCERER.value -> CharacterSheet.Class.SORCERER
CharacterSheet.Class.WARLOCK.value -> CharacterSheet.Class.WARLOCK
CharacterSheet.Class.WIZARD.value -> CharacterSheet.Class.WIZARD
else -> null
}
}
?: emptyList()
}
}

View file

@ -1,14 +1,17 @@
package com.pixelized.rplexicon.data.parser
package com.pixelized.rplexicon.data.parser.characterSheet
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.parser.column
import com.pixelized.rplexicon.data.parser.parserScope
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.extentions.sheet
import javax.inject.Inject
class CharacterSheetParser @Inject constructor() {
class CharacterSheetParser @Inject constructor(
private val classParser: CharacterClassParser,
) {
@Throws(IncompatibleSheetStructure::class)
fun parse(value: ValueRange): Map<String, CharacterSheet> = parserScope {
// fetch the character sheet
@ -34,7 +37,7 @@ class CharacterSheetParser @Inject constructor() {
race = item.parse(column = RACE),
proficiency = item.parseInt(column = MASTERY) ?: 2,
level = item.parseInt(column = LEVEL) ?: 2,
characterClass = item.parse(column = CLASS) ?: "",
characterClass = classParser.parse(value = item.parse(column = CLASS)),
hitPoint = item.parseInt(column = MAX_HIT_POINT) ?: 1,
spell1 = item.parseInt(column = SPELL_LEVEL_1),
spell2 = item.parseInt(column = SPELL_LEVEL_2),

View file

@ -1,7 +1,7 @@
package com.pixelized.rplexicon.data.repository.character
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.parser.CharacterSheetParser
import com.pixelized.rplexicon.data.parser.characterSheet.CharacterSheetParser
import com.pixelized.rplexicon.data.repository.CharacterBinder
import com.pixelized.rplexicon.data.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.Update

View file

@ -22,7 +22,7 @@ class SkillRepository @Inject constructor(
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun find(character: String, skill: String): Skill? {
fun find(character: String, skill: String?): Skill? {
return skills.value[character]?.firstOrNull { it.name == skill }
}

View file

@ -74,8 +74,6 @@ import com.pixelized.rplexicon.ui.screens.character.CharacterTabUio.Inventory
import com.pixelized.rplexicon.ui.screens.character.CharacterTabUio.Proficiency
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.*
import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio.ID.*
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPagePreview
@ -157,6 +155,9 @@ fun CharacterSheetScreen(
scope.launch { pagerState.animateScrollToPage(it) }
},
onHitPoint = headerViewModel::toggleHitPointDialog,
onResource = {
// TODO()
},
onDeathRoll = {
overlay.prepareRoll(diceThrow = headerViewModel.onDeathThrow())
overlay.showOverlay()
@ -260,6 +261,7 @@ private fun CharacterSheetContent(
tabs: State<List<CharacterTabUio>>,
header: State<CharacterSheetHeaderUio?>,
onHitPoint: () -> Unit,
onResource: () -> Unit,
onDeathRoll: () -> Unit,
onDeathSuccess: () -> Unit,
onDeathFailure: () -> Unit,
@ -360,6 +362,7 @@ private fun CharacterSheetContent(
modifier = Modifier.fillMaxWidth(),
header = header,
onHitPoint = onHitPoint,
onResource = onResource,
onDeathRoll = onDeathRoll,
onDeathSuccess = onDeathSuccess,
onDeathFailure = onDeathFailure,
@ -501,6 +504,7 @@ private fun CharacterScreenPreview(
onFullRefresh = { },
loader = { },
onHitPoint = { },
onResource = { },
onDeathRoll = { },
onDeathSuccess = { },
onDeathFailure = { },

View file

@ -27,6 +27,7 @@ data class CharacterSheetHeaderUio(
val hitPoint: LabelPointUio,
val speed: LabelPointUio,
val dC: LabelPointUio?,
val resource: ResourcePointUio?,
val death: DeathThrowUio? = null
)
@ -36,6 +37,7 @@ fun CharacterSheetHeader(
padding: PaddingValues = PaddingValues(start = 16.dp, end = 16.dp, bottom = 4.dp),
header: State<CharacterSheetHeaderUio?>,
onHitPoint: () -> Unit,
onResource: () -> Unit,
onDeathRoll: () -> Unit,
onDeathSuccess: () -> Unit,
onDeathFailure: () -> Unit,
@ -55,6 +57,9 @@ fun CharacterSheetHeader(
alignment = Alignment.CenterHorizontally
),
) {
header.value?.resource?.let {
ResourcePoint(label = it, onClick = onResource)
}
header.value?.armorClass?.let {
LabelPoint(label = it)
}
@ -95,6 +100,7 @@ private fun CharacterSheetHeaderPreview() {
death = rememberDeathThrowUio(),
),
onHitPoint = { },
onResource = { },
onDeathRoll = { },
onDeathSuccess = { },
onDeathFailure = { },

View file

@ -0,0 +1,100 @@
package com.pixelized.rplexicon.ui.screens.character.composable.character
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
data class ResourcePointUio(
val label: Int?,
val value: String?,
val max: String? = null,
)
@Composable
fun ResourcePoint(
modifier: Modifier = Modifier,
labelStyle: TextStyle = MaterialTheme.typography.labelSmall,
valueStyle: TextStyle = MaterialTheme.typography.headlineSmall,
maxStyle: TextStyle = MaterialTheme.typography.titleSmall,
label: ResourcePointUio,
onClick: (() -> Unit)? = null,
) {
Column(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
enabled = onClick != null, onClick = { onClick?.invoke() }
)
.then(other = modifier),
horizontalAlignment = Alignment.CenterHorizontally
) {
label.label?.let {
Text(
style = labelStyle,
fontWeight = FontWeight.Light,
text = stringResource(id = it),
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = valueStyle,
color = when (onClick) {
null -> MaterialTheme.colorScheme.onSurface
else -> MaterialTheme.colorScheme.primary
},
fontWeight = FontWeight.Bold,
text = label.value ?: "0"
)
label.max?.let {
Text(
modifier = Modifier.alignByBaseline(),
style = maxStyle,
text = it,
)
}
}
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun LabelPointPreview() {
LexiconTheme {
Surface {
ResourcePoint(
label = ResourcePointUio(
label = R.string.character_sheet_title_inspiration,
value = "2",
max = "/ 2",
),
onClick = { },
)
}
}
}

View file

@ -8,6 +8,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.DeathThrowUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.ResourcePointUio
@Composable
@Stable
@ -34,6 +35,11 @@ fun rememberCharacterHeaderStatePreview(
label = R.string.character_sheet_title_dc,
value = "13",
),
resource = ResourcePointUio(
label = R.string.character_sheet_title_rage,
value = "2",
max = "/ 2",
),
death = death,
)
)

View file

@ -4,6 +4,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.DeathThrowUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio
import com.pixelized.rplexicon.ui.screens.character.composable.character.ResourcePointUio
import com.pixelized.rplexicon.ui.screens.character.pages.actions.HeaderViewModel
import javax.inject.Inject
@ -35,6 +36,15 @@ class CharacterSheetHeaderUioFactory @Inject constructor() {
value = "$it",
)
},
resource = if (sheetHeaderData?.resourceMax != null) {
ResourcePointUio(
label = sheetHeaderData.resourceLabel,
value = "${fireHeaderData?.resource ?: 0}",
max = "/ ${sheetHeaderData.resourceMax}"
)
} else {
null
},
death = if (fireHeaderData?.hp == 0) {
DeathThrowUio(
success = fireHeaderData.deathSuccess,

View file

@ -12,6 +12,7 @@ import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.ui.composable.edit.HpPointDialogUio
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
@ -30,6 +31,7 @@ class HeaderViewModel @Inject constructor(
private val firebaseRepository: FirebaseRepository,
characterRepository: CharacterSheetRepository,
alterationRepository: AlterationRepository,
skillRepository: SkillRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
val character = savedStateHandle.characterSheetArgument.name
@ -51,18 +53,24 @@ class HeaderViewModel @Inject constructor(
viewModelScope.launch {
launch(context = Dispatchers.IO) {
characterRepository.data
.combine(alterationRepository.assignedAlterations) { sheets, _ -> sheets }
.collect { sheets ->
val character = sheets[character]
if (character != null) {
.combine(skillRepository.skills) { sheets, _ -> sheets[character] }
.combine(alterationRepository.assignedAlterations) { sheet, _ -> sheet }
.collect { sheet ->
if (sheet != null) {
val alterations = alterationRepository.getActiveAlterationsStatus(
character = character.name,
character = sheet.name,
)
val resource = sheet.characterClass.firstOrNull()
val data = SheetHeaderData(
hpMax = character.hitPoint + alterations[Property.HIT_POINT].sum,
speed = character.speed,
ca = character.armorClass + alterations[Property.ARMOR_CLASS].sum,
dc = character.dC,
hpMax = sheet.hitPoint + alterations[Property.HIT_POINT].sum,
resourceLabel = resource?.label,
resourceMax = skillRepository.find(
character = character,
skill = resource?.resource,
)?.amount,
speed = sheet.speed,
ca = sheet.armorClass + alterations[Property.ARMOR_CLASS].sum,
dc = sheet.dC,
)
withContext(Dispatchers.Main) {
sheetData.value = data
@ -73,17 +81,21 @@ class HeaderViewModel @Inject constructor(
}
}
launch(context = Dispatchers.IO) {
firebaseRepository.getCharacter(character = character).collect { sheets ->
val data = FireHeaderData(
hp = sheets.hitPoint?.value ?: 1,
extra = sheets.hitPoint?.additional ?: 0,
deathSuccess = sheets.death?.success ?: 0,
deathFailure = sheets.death?.failure ?: 0,
)
withContext(Dispatchers.Main) {
fireData.value = data
characterRepository.data
.combine(firebaseRepository.getCharacter(character = character)) { sheets, fire -> sheets[character] to fire }
.collect {
val (sheet, fire) = it
val data = FireHeaderData(
hp = fire.hitPoint?.value ?: 1,
resource = fire.skills[sheet?.characterClass?.firstOrNull()?.resource],
extra = fire.hitPoint?.additional ?: 0,
deathSuccess = fire.death?.success ?: 0,
deathFailure = fire.death?.failure ?: 0,
)
withContext(Dispatchers.Main) {
fireData.value = data
}
}
}
}
}
}
@ -141,6 +153,8 @@ class HeaderViewModel @Inject constructor(
@Stable
data class SheetHeaderData(
val hpMax: Int,
val resourceLabel: Int?,
val resourceMax: Int?,
val speed: Int,
val ca: Int,
val dc: Int?,
@ -149,6 +163,7 @@ class HeaderViewModel @Inject constructor(
@Stable
data class FireHeaderData(
val hp: Int,
val resource: Int?,
val extra: Int,
val deathSuccess: Int,
val deathFailure: Int,

View file

@ -88,6 +88,9 @@
<string name="character_sheet_title_ca">CA</string>
<string name="character_sheet_title_dc">DD</string>
<string name="character_sheet_title_speed">Vitesse</string>
<string name="character_sheet_title_rage">Rage</string>
<string name="character_sheet_title_inspiration">Inspiration</string>
<string name="character_sheet_title_conduit">Conduit Divin</string>
<string name="character_sheet_title_initiative">Initiative</string>
<string name="character_sheet_title_saving_throws">Jet de sauvegarde</string>
<string name="character_sheet_title_proficiencies">Talents</string>

View file

@ -96,6 +96,9 @@
<string name="character_sheet_title_ca">CA</string>
<string name="character_sheet_title_dc">DC</string>
<string name="character_sheet_title_speed">Speed</string>
<string name="character_sheet_title_rage">Rage</string>
<string name="character_sheet_title_inspiration">Inspiration</string>
<string name="character_sheet_title_conduit">Divine conduit</string>
<string name="character_sheet_title_initiative">Initiative</string>
<string name="character_sheet_title_saving_throws">Saving Throws</string>
<string name="character_sheet_title_proficiencies">Proficiencies</string>