diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/CharacterSheet.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/CharacterSheet.kt index b986c98..70e582e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/CharacterSheet.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/CharacterSheet.kt @@ -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, // 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, // languages masteries val others: List, // 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"), } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/alteration/AlterationParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/alteration/AlterationParser.kt index dfe5d2f..2b5ff34 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/alteration/AlterationParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/alteration/AlterationParser.kt @@ -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 diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/characterSheet/CharacterClassParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/characterSheet/CharacterClassParser.kt new file mode 100644 index 0000000..d62a27e --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/characterSheet/CharacterClassParser.kt @@ -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 { + 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/CharacterSheetParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/characterSheet/CharacterSheetParser.kt similarity index 96% rename from app/src/main/java/com/pixelized/rplexicon/data/parser/CharacterSheetParser.kt rename to app/src/main/java/com/pixelized/rplexicon/data/parser/characterSheet/CharacterSheetParser.kt index 0b0afa5..13ab44a 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/CharacterSheetParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/characterSheet/CharacterSheetParser.kt @@ -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 = 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), diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/character/CharacterSheetRepository.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/character/CharacterSheetRepository.kt index b63ddd8..9fec94e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/repository/character/CharacterSheetRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/character/CharacterSheetRepository.kt @@ -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 diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/character/SkillRepository.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/character/SkillRepository.kt index db759ba..63d7247 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/repository/character/SkillRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/character/SkillRepository.kt @@ -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 } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt index 0f146ef..47a46dd 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt @@ -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>, header: State, 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 = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt index ae201de..d496de7 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt @@ -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, 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 = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/ResourcePoint.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/ResourcePoint.kt new file mode 100644 index 0000000..fffc6c5 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/ResourcePoint.kt @@ -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 = { }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt index f6c1f05..2170560 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt @@ -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, ) ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt index 589a86f..b51480e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt @@ -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, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/HeaderViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/HeaderViewModel.kt index 4d455a6..0e4d7e5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/HeaderViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/HeaderViewModel.kt @@ -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, diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6d2afd8..55ead76 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -88,6 +88,9 @@ CA DD Vitesse + Rage + Inspiration + Conduit Divin Initiative Jet de sauvegarde Talents diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 457cf0a..99280f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,6 +96,9 @@ CA DC Speed + Rage + Inspiration + Divine conduit Initiative Saving Throws Proficiencies