Basic implementation of Alteration.
This commit is contained in:
parent
2fb1ef2fd8
commit
6c7ac8e010
51 changed files with 2164 additions and 462 deletions
|
|
@ -1,50 +0,0 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.model.Roll
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier
|
||||
import javax.inject.Inject
|
||||
|
||||
class RollParser @Inject constructor() {
|
||||
private val diceRegex = Regex("(\\d+)d(\\d+)")
|
||||
private val bonusRegex = Regex("(?:[a-zA-Z]|\\?)[a-zA-Z]+")
|
||||
|
||||
fun parseRoll(characterSheet: CharacterSheet, value: String?): Roll {
|
||||
val roll = value?.split(";")
|
||||
|
||||
val label = roll?.getOrNull(0)
|
||||
val (dices, bonus) = roll?.getOrNull(1)?.let { item ->
|
||||
val dices = diceRegex.findAll(item).toList().map { it.parseDice() }
|
||||
val bonus = bonusRegex.findAll(item).map { bonus ->
|
||||
Roll.Bonus(
|
||||
label = bonus.value,
|
||||
value = bonus.value.parseBonus(characterSheet = characterSheet),
|
||||
)
|
||||
}
|
||||
dices.toList() to bonus.toList()
|
||||
} ?: (null to null)
|
||||
|
||||
return Roll(
|
||||
title = label.toString(),
|
||||
highlight = null,
|
||||
dices = dices ?: emptyList(),
|
||||
bonus = bonus ?: emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String?.parseBonus(
|
||||
characterSheet: CharacterSheet,
|
||||
): Int = when (this?.lowercase()) {
|
||||
"bonus" -> characterSheet.proficiency
|
||||
"force" -> characterSheet.strength.modifier
|
||||
else -> 0
|
||||
}
|
||||
|
||||
private fun MatchResult.parseDice(): Roll.Dice {
|
||||
val (count, faces) = destructured
|
||||
return Roll.Dice(
|
||||
count = count.toIntOrNull() ?: 0,
|
||||
faces = faces.toIntOrNull() ?: 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.pixelized.rplexicon.facotry.displayable
|
||||
|
||||
import com.pixelized.rplexicon.model.Action
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio
|
||||
import com.pixelized.rplexicon.utilitary.extentions.icon
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConvertActionIntoDisplayableFactory @Inject constructor() {
|
||||
fun toUio(action: Action): ActionsUio {
|
||||
return ActionsUio(
|
||||
title = action.title,
|
||||
hit = action.hit?.faces?.icon,
|
||||
damage = action.damage?.faces?.icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.displayable
|
||||
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.pixelized.rplexicon.facotry.displayable
|
||||
|
||||
import com.pixelized.rplexicon.model.Counter
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConvertCounterIntoDisplayableFactory @Inject constructor() {
|
||||
fun toUio(counter: Counter): CounterUio = CounterUio(
|
||||
title = counter.title,
|
||||
value = counter.value,
|
||||
max = counter.max,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.displayable
|
||||
|
||||
import com.pixelized.rplexicon.model.Roll
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
|
||||
|
|
@ -44,7 +44,7 @@ class ConvertRollIntoDisplayableFactory @Inject constructor() {
|
|||
allRolledValues.add(bonus.value)
|
||||
|
||||
ThrowsCardUio.Detail(
|
||||
title = bonus.label,
|
||||
title = bonus.title,
|
||||
result = "${bonus.value}",
|
||||
)
|
||||
}
|
||||
|
|
@ -64,62 +64,61 @@ class ConvertRollIntoDisplayableFactory @Inject constructor() {
|
|||
details = rollDetail + diceBonus + flatBonus,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Roll.Dice.toRollCardUio(result: Int) = RollDiceUio(
|
||||
icon = faces.icon,
|
||||
isCriticalSuccess = count == 1 && result == faces,
|
||||
isCriticalFailure = count == 1 && result == 1,
|
||||
result = "$result",
|
||||
)
|
||||
|
||||
private fun Roll.Dice.toThrowsCardUio(roll: String, result: Int) = ThrowsCardUio.Detail(
|
||||
title = title,
|
||||
throws = ThrowsCardUio.Throw(
|
||||
dice = faces.icon,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
roll = label,
|
||||
result = roll,
|
||||
),
|
||||
result = "$result",
|
||||
)
|
||||
private fun Roll.Dice.toRollCardUio(result: Int) = RollDiceUio(
|
||||
icon = faces.icon,
|
||||
isCriticalSuccess = count == 1 && faces == 20 && result == faces,
|
||||
isCriticalFailure = count == 1 && faces == 20 && result == 1,
|
||||
result = "$result",
|
||||
)
|
||||
|
||||
private data class RollResult(
|
||||
val label: String,
|
||||
val sum: Int,
|
||||
)
|
||||
private fun Roll.Dice.toThrowsCardUio(roll: String, result: Int) = ThrowsCardUio.Detail(
|
||||
title = title,
|
||||
throws = ThrowsCardUio.Throw(
|
||||
dice = faces.icon,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
roll = label,
|
||||
result = roll,
|
||||
),
|
||||
result = "$result",
|
||||
)
|
||||
|
||||
private fun Roll.Dice.roll(): RollResult {
|
||||
return when {
|
||||
advantage -> {
|
||||
val roll = List(count) {
|
||||
(Math.random() * faces + 1).toInt() to (Math.random() * faces + 1).toInt()
|
||||
private data class RollResult(
|
||||
val label: String,
|
||||
val sum: Int,
|
||||
)
|
||||
|
||||
private fun Roll.Dice.roll(): RollResult {
|
||||
return when {
|
||||
|
||||
advantage && !disadvantage -> {
|
||||
val roll = List(count) { random() to random() }
|
||||
RollResult(
|
||||
label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
|
||||
sum = roll.sumOf { max(it.first, it.second) },
|
||||
)
|
||||
}
|
||||
RollResult(
|
||||
label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
|
||||
sum = roll.sumOf { max(it.first, it.second) },
|
||||
)
|
||||
}
|
||||
|
||||
disadvantage -> {
|
||||
val roll = List(count) {
|
||||
(Math.random() * faces + 1).toInt() to (Math.random() * faces + 1).toInt()
|
||||
disadvantage && !advantage -> {
|
||||
val roll = List(count) { random() to random() }
|
||||
RollResult(
|
||||
label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
|
||||
sum = roll.sumOf { min(it.first, it.second) },
|
||||
)
|
||||
}
|
||||
RollResult(
|
||||
label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
|
||||
sum = roll.sumOf { min(it.first, it.second) },
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val roll = List(count) {
|
||||
(Math.random() * faces + 1).toInt()
|
||||
else -> {
|
||||
val roll = List(count) { random() }
|
||||
RollResult(
|
||||
label = roll.toLabel(),
|
||||
sum = roll.sum(),
|
||||
)
|
||||
}
|
||||
RollResult(
|
||||
label = roll.toLabel(),
|
||||
sum = roll.sum(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Roll.Dice.random(): Int = if (fail) 1 else (Math.random() * faces + 1).toInt()
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.facotry.model.roll.DiceParser
|
||||
import com.pixelized.rplexicon.facotry.model.roll.ModifierParser
|
||||
import com.pixelized.rplexicon.model.Action
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
class ActionParser @Inject constructor(
|
||||
private val diceParser: DiceParser,
|
||||
private val modifierParser: ModifierParser,
|
||||
) {
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(
|
||||
value: ValueRange,
|
||||
charactersSheets: Map<String, CharacterSheet>,
|
||||
): Map<String, List<Action>> {
|
||||
val sheet = value.values.sheet()
|
||||
lateinit var structure: Map<String, Int>
|
||||
|
||||
val actions = hashMapOf<String, MutableList<Action>>()
|
||||
|
||||
sheet?.forEachIndexed { index, row ->
|
||||
when {
|
||||
index == 0 -> {
|
||||
structure = row.checkSheetStructure(model = COLUMNS)
|
||||
}
|
||||
|
||||
row is List<*> -> {
|
||||
// Assume that the name is the first column.
|
||||
val characterSheet = charactersSheets[row.getOrNull(0) as? String ?: ""]
|
||||
val title = row.getOrNull(structure.getValue(NAME))?.toString()
|
||||
if (characterSheet != null && title != null) {
|
||||
val action = Action(
|
||||
title = title,
|
||||
type = parseType(
|
||||
value = row.getOrNull(structure.getValue(TYPE))?.toString(),
|
||||
),
|
||||
hit = parseThrows(
|
||||
value = row.getOrNull(structure.getValue(HIT))?.toString(),
|
||||
),
|
||||
damage = parseThrows(
|
||||
value = row.getOrNull(structure.getValue(DAMAGE))?.toString(),
|
||||
),
|
||||
)
|
||||
actions
|
||||
.getOrPut(characterSheet.name) { mutableListOf() }
|
||||
.add(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
private fun parseType(value: String?): Action.Type? {
|
||||
return when (value) {
|
||||
Action.Type.ATTACK.key -> Action.Type.ATTACK
|
||||
Action.Type.SPELL.key -> Action.Type.SPELL
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseThrows(value: String?): Action.Throw? {
|
||||
if (value != null) {
|
||||
val dice = diceParser.findAll(value = value).firstOrNull()
|
||||
if (dice != null) {
|
||||
val modifier = modifierParser.findAll(value = value)
|
||||
return Action.Throw(
|
||||
amount = dice.count,
|
||||
faces = dice.faces,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME = "Nom"
|
||||
const val TYPE = "type"
|
||||
const val HIT = "Touché"
|
||||
const val DAMAGE = "Dommage"
|
||||
|
||||
val COLUMNS get() = listOf("", NAME, TYPE, HIT, DAMAGE)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class CharacterSheetParser @Inject constructor() {
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(value: ValueRange): Map<String, CharacterSheet> {
|
||||
|
||||
// fetch the character sheet
|
||||
val sheet = value.values.sheet() // fetch the List<*>
|
||||
?.mapNotNull { it as? List<*> } // transform the List<*> to a List<List<*>>
|
||||
?.let { sheet ->
|
||||
val entryCount = if (sheet.isNotEmpty()) sheet[0].size else 0
|
||||
List(entryCount) { index -> sheet.map { it.getOrNull(index) } }
|
||||
}
|
||||
|
||||
// prepare a hash on the structure & function to get index in it.
|
||||
lateinit var structure: Map<String, Int>
|
||||
fun List<*>.parse(key: String): Int =
|
||||
(getOrNull(structure.getValue(key)) as? String)?.toIntOrNull() ?: 0
|
||||
|
||||
// parse the sheet
|
||||
return sheet?.mapIndexedNotNull { index, item ->
|
||||
when (index) {
|
||||
0 -> {
|
||||
structure = item.checkSheetStructure(model = ROWS)
|
||||
return@mapIndexedNotNull null
|
||||
}
|
||||
|
||||
else -> {
|
||||
val name = item.getOrNull(structure.getValue(NAME)) as String?
|
||||
if (name != null) {
|
||||
CharacterSheet(
|
||||
name = name,
|
||||
hitPoint = item.parse(HIT_POINT),
|
||||
armorClass = item.parse(ARMOR_CLASS),
|
||||
proficiency = item.parse(MASTERY),
|
||||
strength = item.parse(STRENGTH),
|
||||
dexterity = item.parse(DEXTERITY),
|
||||
constitution = item.parse(CONSTITUTION),
|
||||
intelligence = item.parse(INTELLIGENCE),
|
||||
wisdom = item.parse(WISDOM),
|
||||
charisma = item.parse(CHARISMA),
|
||||
strengthSavingThrows = item.parse(STRENGTH_SAVING_THROW),
|
||||
dexteritySavingThrows = item.parse(DEXTERITY_SAVING_THROW),
|
||||
constitutionSavingThrows = item.parse(CONSTITUTION_SAVING_THROW),
|
||||
intelligenceSavingThrows = item.parse(INTELLIGENCE_SAVING_THROW),
|
||||
wisdomSavingThrows = item.parse(WISDOM_SAVING_THROW),
|
||||
charismaSavingThrows = item.parse(CHARISMA_SAVING_THROW),
|
||||
acrobatics = item.parse(ACROBATICS),
|
||||
animalHandling = item.parse(ANIMAL_HANDLING),
|
||||
arcana = item.parse(ARCANA),
|
||||
athletics = item.parse(ATHLETICS),
|
||||
deception = item.parse(DECEPTION),
|
||||
history = item.parse(HISTORY),
|
||||
insight = item.parse(INSIGHT),
|
||||
intimidation = item.parse(INTIMIDATION),
|
||||
investigation = item.parse(INVESTIGATION),
|
||||
medicine = item.parse(MEDICINE),
|
||||
nature = item.parse(NATURE),
|
||||
perception = item.parse(PERCEPTION),
|
||||
performance = item.parse(PERFORMANCE),
|
||||
persuasion = item.parse(PERSUASION),
|
||||
religion = item.parse(RELIGION),
|
||||
sleightOfHand = item.parse(SLEIGHT_OF_HAND),
|
||||
stealth = item.parse(STEALTH),
|
||||
survival = item.parse(SURVIVAL),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}?.associateBy { it.name } ?: emptyMap()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NAME = "Nom"
|
||||
private const val HIT_POINT = "Point de vie"
|
||||
private const val ARMOR_CLASS = "Classe d'armure"
|
||||
private const val MASTERY = "Bonus de maîtrise"
|
||||
private const val STRENGTH = "Force"
|
||||
private const val DEXTERITY = "Dextérité"
|
||||
private const val CONSTITUTION = "Constitution"
|
||||
private const val INTELLIGENCE = "Intelligence"
|
||||
private const val WISDOM = "Sagesse"
|
||||
private const val CHARISMA = "Charisme"
|
||||
private const val STRENGTH_SAVING_THROW = "Jet de sauvegarde: Force"
|
||||
private const val DEXTERITY_SAVING_THROW = "Jet de sauvegarde: Dextérité"
|
||||
private const val CONSTITUTION_SAVING_THROW = "Jet de sauvegarde: Constitution"
|
||||
private const val INTELLIGENCE_SAVING_THROW = "Jet de sauvegarde: Intelligence"
|
||||
private const val WISDOM_SAVING_THROW = "Jet de sauvegarde: Sagesse"
|
||||
private const val CHARISMA_SAVING_THROW = "Jet de sauvegarde: Charisme"
|
||||
private const val ACROBATICS = "Acrobaties"
|
||||
private const val ANIMAL_HANDLING = "Dressage"
|
||||
private const val ARCANA = "Arcanes"
|
||||
private const val ATHLETICS = "Athlétisme"
|
||||
private const val DECEPTION = "Tromperie"
|
||||
private const val HISTORY = "Histoire"
|
||||
private const val INSIGHT = "Intuition"
|
||||
private const val INTIMIDATION = "Intimidation"
|
||||
private const val INVESTIGATION = "Investigation"
|
||||
private const val MEDICINE = "Médecine"
|
||||
private const val NATURE = "Nature"
|
||||
private const val PERCEPTION = "Perception"
|
||||
private const val PERFORMANCE = "Représentation"
|
||||
private const val PERSUASION = "Persuasion"
|
||||
private const val RELIGION = "Religion"
|
||||
private const val SLEIGHT_OF_HAND = "Escamotage"
|
||||
private const val STEALTH = "Discrétion"
|
||||
private const val SURVIVAL = "Survie"
|
||||
|
||||
private val ROWS
|
||||
get() = listOf(
|
||||
NAME,
|
||||
HIT_POINT,
|
||||
ARMOR_CLASS,
|
||||
MASTERY,
|
||||
STRENGTH,
|
||||
DEXTERITY,
|
||||
CONSTITUTION,
|
||||
INTELLIGENCE,
|
||||
WISDOM,
|
||||
CHARISMA,
|
||||
STRENGTH_SAVING_THROW,
|
||||
DEXTERITY_SAVING_THROW,
|
||||
CONSTITUTION_SAVING_THROW,
|
||||
INTELLIGENCE_SAVING_THROW,
|
||||
WISDOM_SAVING_THROW,
|
||||
CHARISMA_SAVING_THROW,
|
||||
ACROBATICS,
|
||||
ANIMAL_HANDLING,
|
||||
ARCANA,
|
||||
ATHLETICS,
|
||||
DECEPTION,
|
||||
HISTORY,
|
||||
INSIGHT,
|
||||
INTIMIDATION,
|
||||
INVESTIGATION,
|
||||
MEDICINE,
|
||||
NATURE,
|
||||
PERCEPTION,
|
||||
PERFORMANCE,
|
||||
PERSUASION,
|
||||
RELIGION,
|
||||
SLEIGHT_OF_HAND,
|
||||
STEALTH,
|
||||
SURVIVAL,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import javax.inject.Inject
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import android.net.Uri
|
||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.facotry
|
||||
package com.pixelized.rplexicon.facotry.model
|
||||
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import javax.inject.Inject
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.pixelized.rplexicon.facotry.model.alteration
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.facotry.model.roll.DiceParser
|
||||
import com.pixelized.rplexicon.facotry.model.roll.FlatValueParser
|
||||
import com.pixelized.rplexicon.model.Alteration
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
class AlterationParser @Inject constructor(
|
||||
private val diceParser: DiceParser,
|
||||
private val flatParser: FlatValueParser,
|
||||
private val propertyParser: PropertyParser,
|
||||
) {
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(value: ValueRange): List<Alteration> {
|
||||
val sheet = value.values.sheet()
|
||||
lateinit var structure: Map<String, Int>
|
||||
|
||||
return sheet?.mapIndexedNotNull { index, row ->
|
||||
when {
|
||||
index == 0 -> {
|
||||
structure = row.checkSheetStructure(model = COLUMNS)
|
||||
null
|
||||
}
|
||||
|
||||
row is List<*> -> {
|
||||
// Assume that the name is the first column.
|
||||
val name = row.getOrNull(0) as? String
|
||||
|
||||
if (name != null) {
|
||||
Alteration(
|
||||
name = name,
|
||||
status = COLUMNS
|
||||
.mapNotNull { column ->
|
||||
val property = propertyParser.parseProperty(column)
|
||||
val flat = row.getOrNull(structure.getValue(column)) as? String
|
||||
if (property != null && !flat.isNullOrEmpty()) {
|
||||
property to parseAlterationStatus(name, flat)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.toMap(),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
private fun parseAlterationStatus(name: String, value: String): Alteration.Status =
|
||||
when (value) {
|
||||
"adv" -> Alteration.Status(
|
||||
name = name,
|
||||
advantage = true,
|
||||
disadvantage = false,
|
||||
fail = false,
|
||||
)
|
||||
|
||||
"dis" -> Alteration.Status(
|
||||
name = name,
|
||||
advantage = false,
|
||||
disadvantage = true,
|
||||
fail = false,
|
||||
)
|
||||
|
||||
"fail" -> Alteration.Status(
|
||||
name = name,
|
||||
advantage = false,
|
||||
disadvantage = false,
|
||||
fail = true,
|
||||
)
|
||||
|
||||
else -> {
|
||||
Alteration.Status(
|
||||
name = name,
|
||||
advantage = false,
|
||||
disadvantage = false,
|
||||
fail = false,
|
||||
dices = diceParser.findAll(title = name, value = value),
|
||||
bonus = flatParser.findAll(title = name, value = value),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val COLUMNS
|
||||
get() = listOf(
|
||||
Property.HIT_POINT.key,
|
||||
Property.ARMOR_CLASS.key,
|
||||
Property.STRENGTH.key,
|
||||
Property.DEXTERITY.key,
|
||||
Property.CONSTITUTION.key,
|
||||
Property.INTELLIGENCE.key,
|
||||
Property.WISDOM.key,
|
||||
Property.CHARISMA.key,
|
||||
Property.STRENGTH_SAVING_THROW.key,
|
||||
Property.DEXTERITY_SAVING_THROW.key,
|
||||
Property.CONSTITUTION_SAVING_THROW.key,
|
||||
Property.INTELLIGENCE_SAVING_THROW.key,
|
||||
Property.WISDOM_SAVING_THROW.key,
|
||||
Property.CHARISMA_SAVING_THROW.key,
|
||||
Property.ACROBATICS.key,
|
||||
Property.ANIMAL_HANDLING.key,
|
||||
Property.ARCANA.key,
|
||||
Property.ATHLETICS.key,
|
||||
Property.DECEPTION.key,
|
||||
Property.HISTORY.key,
|
||||
Property.INSIGHT.key,
|
||||
Property.INTIMIDATION.key,
|
||||
Property.INVESTIGATION.key,
|
||||
Property.MEDICINE.key,
|
||||
Property.NATURE.key,
|
||||
Property.PERCEPTION.key,
|
||||
Property.PERFORMANCE.key,
|
||||
Property.PERSUASION.key,
|
||||
Property.RELIGION.key,
|
||||
Property.SLEIGHT_OF_HAND.key,
|
||||
Property.STEALTH.key,
|
||||
Property.SURVIVAL.key,
|
||||
Property.ATTACK_ROLL.key,
|
||||
Property.ATTACK_DAMAGE_ROLL.key,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.pixelized.rplexicon.facotry.model.alteration
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.Counter
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
class CounterParser @Inject constructor() {
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(values: ValueRange): Map<String, List<Counter>> {
|
||||
val sheet = values.values.sheet()
|
||||
lateinit var characters: List<String>
|
||||
val counters = hashMapOf<String, MutableList<Counter>>()
|
||||
|
||||
sheet?.mapNotNull { it as? List<*> }?.forEachIndexed { columnIndex, row ->
|
||||
when (columnIndex) {
|
||||
0 -> characters = row
|
||||
.subList(fromIndex = 1, toIndex = row.size)
|
||||
.map { it.toString() }
|
||||
|
||||
else -> {
|
||||
row.getOrNull(0)?.toString()?.let { title ->
|
||||
row.subList(fromIndex = 1, toIndex = row.size)
|
||||
.forEachIndexed { rowIndex, value ->
|
||||
val counter = parseCounter(title = title, value = value?.toString())
|
||||
if (counter != null) {
|
||||
counters
|
||||
.getOrPut(characters[rowIndex]) { mutableListOf() }
|
||||
.add(counter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return counters
|
||||
}
|
||||
|
||||
private fun parseCounter(title: String, value: String?): Counter? {
|
||||
return if (value != null) {
|
||||
COUNTER_REGEX.find(value)?.let {
|
||||
val (actual, max) = it.destructured
|
||||
Counter(
|
||||
title = title,
|
||||
value = actual.toIntOrNull() ?: 0,
|
||||
max = max.toIntOrNull(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COUNTER_REGEX = Regex("(\\d+)\\/(\\d+)")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.pixelized.rplexicon.facotry.model.alteration
|
||||
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import javax.inject.Inject
|
||||
|
||||
class PropertyParser @Inject constructor() {
|
||||
fun parseProperty(property: String): Property? = when (property) {
|
||||
Property.PROFICIENCY.key -> Property.PROFICIENCY
|
||||
Property.HIT_POINT.key -> Property.HIT_POINT
|
||||
Property.ARMOR_CLASS.key -> Property.ARMOR_CLASS
|
||||
Property.STRENGTH.key -> Property.STRENGTH
|
||||
Property.DEXTERITY.key -> Property.DEXTERITY
|
||||
Property.CONSTITUTION.key -> Property.CONSTITUTION
|
||||
Property.INTELLIGENCE.key -> Property.INTELLIGENCE
|
||||
Property.WISDOM.key -> Property.WISDOM
|
||||
Property.CHARISMA.key -> Property.CHARISMA
|
||||
Property.STRENGTH_SAVING_THROW.key -> Property.STRENGTH_SAVING_THROW
|
||||
Property.DEXTERITY_SAVING_THROW.key -> Property.DEXTERITY_SAVING_THROW
|
||||
Property.CONSTITUTION_SAVING_THROW.key -> Property.CONSTITUTION_SAVING_THROW
|
||||
Property.INTELLIGENCE_SAVING_THROW.key -> Property.INTELLIGENCE_SAVING_THROW
|
||||
Property.WISDOM_SAVING_THROW.key -> Property.WISDOM_SAVING_THROW
|
||||
Property.CHARISMA_SAVING_THROW.key -> Property.CHARISMA_SAVING_THROW
|
||||
Property.ACROBATICS.key -> Property.ACROBATICS
|
||||
Property.ANIMAL_HANDLING.key -> Property.ANIMAL_HANDLING
|
||||
Property.ARCANA.key -> Property.ARCANA
|
||||
Property.ATHLETICS.key -> Property.ATHLETICS
|
||||
Property.DECEPTION.key -> Property.DECEPTION
|
||||
Property.HISTORY.key -> Property.HISTORY
|
||||
Property.INSIGHT.key -> Property.INSIGHT
|
||||
Property.INTIMIDATION.key -> Property.INTIMIDATION
|
||||
Property.INVESTIGATION.key -> Property.INVESTIGATION
|
||||
Property.MEDICINE.key -> Property.MEDICINE
|
||||
Property.NATURE.key -> Property.NATURE
|
||||
Property.PERCEPTION.key -> Property.PERCEPTION
|
||||
Property.PERFORMANCE.key -> Property.PERFORMANCE
|
||||
Property.PERSUASION.key -> Property.PERSUASION
|
||||
Property.RELIGION.key -> Property.RELIGION
|
||||
Property.SLEIGHT_OF_HAND.key -> Property.SLEIGHT_OF_HAND
|
||||
Property.STEALTH.key -> Property.STEALTH
|
||||
Property.SURVIVAL.key -> Property.SURVIVAL
|
||||
Property.ATTACK_ROLL.key -> Property.ATTACK_ROLL
|
||||
Property.ATTACK_DAMAGE_ROLL.key -> Property.ATTACK_DAMAGE_ROLL
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.pixelized.rplexicon.facotry.model.alteration
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
class StatusParser @Inject constructor() {
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(value: ValueRange): Map<String, List<String>> {
|
||||
val sheet = value.values.sheet()
|
||||
lateinit var characters: List<String>
|
||||
val status = hashMapOf<String, MutableList<String>>()
|
||||
|
||||
sheet?.mapNotNull { it as? List<*> }?.forEachIndexed { columnIndex, row ->
|
||||
when (columnIndex) {
|
||||
0 -> characters = row
|
||||
.subList(fromIndex = 1, toIndex = row.size)
|
||||
.map { it.toString() }
|
||||
|
||||
else -> {
|
||||
row.getOrNull(0)?.toString()?.let { alteration ->
|
||||
row.subList(fromIndex = 1, toIndex = row.size)
|
||||
.forEachIndexed { rowIndex, value ->
|
||||
if (value == TRUE) {
|
||||
status
|
||||
.getOrPut(characters[rowIndex]) { mutableListOf() }
|
||||
.add(alteration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TRUE = "TRUE"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.pixelized.rplexicon.facotry.model.roll
|
||||
|
||||
import com.pixelized.rplexicon.model.Roll
|
||||
import javax.inject.Inject
|
||||
|
||||
class DiceParser @Inject constructor() {
|
||||
|
||||
companion object {
|
||||
private val DICE_REGEX = Regex("(a|adv|d|dis)*(\\d+)d(\\d+)")
|
||||
}
|
||||
|
||||
fun findAll(title: String? = null, value: String): List<Roll.Dice> =
|
||||
DICE_REGEX.findAll(value).map { it.parse(title = title) }.toList()
|
||||
|
||||
private fun MatchResult.parse(
|
||||
title: String? = null,
|
||||
): Roll.Dice {
|
||||
val (status, count, faces) = destructured
|
||||
return Roll.Dice(
|
||||
title = title,
|
||||
advantage = status == "a" || status == "adv",
|
||||
disadvantage = status == "d" || status == "dis",
|
||||
count = count.toIntOrNull() ?: 0,
|
||||
faces = faces.toIntOrNull() ?: 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.pixelized.rplexicon.facotry.model.roll
|
||||
|
||||
import com.pixelized.rplexicon.model.Roll
|
||||
import javax.inject.Inject
|
||||
|
||||
class FlatValueParser @Inject constructor() {
|
||||
|
||||
companion object {
|
||||
private val FLAT_REGEX = Regex("(?<!d|\\d)(\\d+)(?!d)")
|
||||
}
|
||||
|
||||
fun findAll(title: String, value: String): List<Roll.Bonus> {
|
||||
return FLAT_REGEX.findAll(value).map { it.parse(title = title) }.toList()
|
||||
}
|
||||
|
||||
private fun MatchResult.parse(
|
||||
title: String,
|
||||
): Roll.Bonus {
|
||||
return Roll.Bonus(
|
||||
title = title,
|
||||
value = value.toIntOrNull() ?: 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.pixelized.rplexicon.facotry.model.roll
|
||||
|
||||
import com.pixelized.rplexicon.facotry.model.alteration.PropertyParser
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import javax.inject.Inject
|
||||
|
||||
class ModifierParser @Inject constructor(
|
||||
private val propertyParser: PropertyParser
|
||||
) {
|
||||
companion object {
|
||||
private val MODIFIER_REGEX = Regex(
|
||||
pattern = Property.PROFICIENCY.key +
|
||||
"|${Property.STRENGTH.key}" +
|
||||
"|${Property.DEXTERITY.key}" +
|
||||
"|${Property.CONSTITUTION.key}" +
|
||||
"|${Property.INTELLIGENCE.key}" +
|
||||
"|${Property.WISDOM.key}" +
|
||||
"|${Property.CHARISMA.key}",
|
||||
option = RegexOption.IGNORE_CASE
|
||||
)
|
||||
}
|
||||
|
||||
fun findAll(value: String): List<Property> {
|
||||
return MODIFIER_REGEX.findAll(value)
|
||||
.mapNotNull { propertyParser.parseProperty(it.value) }
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/com/pixelized/rplexicon/model/Action.kt
Normal file
19
app/src/main/java/com/pixelized/rplexicon/model/Action.kt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
data class Action(
|
||||
val title: String,
|
||||
val type: Type?,
|
||||
val hit: Throw?,
|
||||
val damage: Throw?,
|
||||
) {
|
||||
enum class Type(val key: String) {
|
||||
ATTACK("Attaque"),
|
||||
SPELL("Sortilège"),
|
||||
}
|
||||
|
||||
class Throw(
|
||||
val amount: Int,
|
||||
val faces: Int,
|
||||
val modifier: List<Property>,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
data class Alteration(
|
||||
val name: String,
|
||||
val status: Map<Property, Status>,
|
||||
) {
|
||||
data class Status(
|
||||
val name: String,
|
||||
val advantage: Boolean,
|
||||
val disadvantage: Boolean,
|
||||
val fail: Boolean,
|
||||
val dices: List<Roll.Dice> = emptyList(),
|
||||
val bonus: List<Roll.Bonus> = emptyList(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
data class CharacterSheet(
|
||||
val name: String,
|
||||
val hitPoint: Int, // Point de vie
|
||||
val armorClass: Int, // Classe d'armure
|
||||
val proficiency: Int, // Bonus de maîtrise
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
data class Counter(
|
||||
val title: String,
|
||||
val value: Int,
|
||||
val max: Int?,
|
||||
)
|
||||
39
app/src/main/java/com/pixelized/rplexicon/model/Property.kt
Normal file
39
app/src/main/java/com/pixelized/rplexicon/model/Property.kt
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
enum class Property(val key: String) {
|
||||
PROFICIENCY("Maîtrise"),
|
||||
HIT_POINT("Point de vie"),
|
||||
ARMOR_CLASS("Classe d'armure"),
|
||||
STRENGTH("Force"),
|
||||
DEXTERITY("Dextérité"),
|
||||
CONSTITUTION("Constitution"),
|
||||
INTELLIGENCE("Intelligence"),
|
||||
WISDOM("Sagesse"),
|
||||
CHARISMA("Charisme"),
|
||||
STRENGTH_SAVING_THROW("Jet de sauvegarde: Force"),
|
||||
DEXTERITY_SAVING_THROW("Jet de sauvegarde: Dextérité"),
|
||||
CONSTITUTION_SAVING_THROW("Jet de sauvegarde: Constitution"),
|
||||
INTELLIGENCE_SAVING_THROW("Jet de sauvegarde: Intelligence"),
|
||||
WISDOM_SAVING_THROW("Jet de sauvegarde: Sagesse"),
|
||||
CHARISMA_SAVING_THROW("Jet de sauvegarde: Charisme"),
|
||||
ACROBATICS("Acrobaties"),
|
||||
ANIMAL_HANDLING("Dressage"),
|
||||
ARCANA("Arcanes"),
|
||||
ATHLETICS("Athlétisme"),
|
||||
DECEPTION("Tromperie"),
|
||||
HISTORY("Histoire"),
|
||||
INSIGHT("Intuition"),
|
||||
INTIMIDATION("Intimidation"),
|
||||
INVESTIGATION("Investigation"),
|
||||
MEDICINE("Médecine"),
|
||||
NATURE("Nature"),
|
||||
PERCEPTION("Perception"),
|
||||
PERFORMANCE("Représentation"),
|
||||
PERSUASION("Persuasion"),
|
||||
RELIGION("Religion"),
|
||||
SLEIGHT_OF_HAND("Escamotage"),
|
||||
STEALTH("Discrétion"),
|
||||
SURVIVAL("Survie"),
|
||||
ATTACK_ROLL("Attaque"),
|
||||
ATTACK_DAMAGE_ROLL("Dommage"),
|
||||
}
|
||||
|
|
@ -1,108 +1,104 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
data class Roll(
|
||||
val title: String,
|
||||
val highlight: String? = null,
|
||||
val dices: List<Dice>,
|
||||
val bonus: List<Bonus>,
|
||||
) {
|
||||
@Stable
|
||||
data class Dice(
|
||||
val title: String? = null,
|
||||
val advantage: Boolean = false,
|
||||
val disadvantage: Boolean = false,
|
||||
val fail: Boolean = false,
|
||||
val count: Int,
|
||||
val faces: Int,
|
||||
) {
|
||||
val label: String = "${count}d${faces}"
|
||||
|
||||
companion object {
|
||||
fun d20(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 20,
|
||||
)
|
||||
|
||||
fun d12(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 12,
|
||||
)
|
||||
|
||||
fun d10(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 10,
|
||||
)
|
||||
|
||||
fun d8(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 8,
|
||||
)
|
||||
|
||||
fun d6(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 6,
|
||||
)
|
||||
|
||||
fun d4(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 4,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
data class Bonus(
|
||||
val label: String,
|
||||
val title: String,
|
||||
val value: Int,
|
||||
)
|
||||
}
|
||||
|
||||
fun d20(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
fail: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Roll.Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
fail = fail,
|
||||
faces = 20,
|
||||
)
|
||||
|
||||
fun d12(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Roll.Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 12,
|
||||
)
|
||||
|
||||
fun d10(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Roll.Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 10,
|
||||
)
|
||||
|
||||
fun d8(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Roll.Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 8,
|
||||
)
|
||||
|
||||
fun d6(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Roll.Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 6,
|
||||
)
|
||||
|
||||
fun d4(
|
||||
title: String? = null,
|
||||
advantage: Boolean = false,
|
||||
disadvantage: Boolean = false,
|
||||
amount: Int = 1,
|
||||
) = Roll.Dice(
|
||||
title = title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
count = amount,
|
||||
faces = 4,
|
||||
)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.pixelized.rplexicon.facotry.model.ActionParser
|
||||
import com.pixelized.rplexicon.model.Action
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@Singleton
|
||||
class ActionRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val actionParser: ActionParser,
|
||||
) {
|
||||
private val _data = MutableStateFlow<Map<String, List<Action>>>(emptyMap())
|
||||
val data: StateFlow<Map<String, List<Action>>> get() = _data
|
||||
|
||||
fun find(name: String?): List<Action>? {
|
||||
return name?.let { _data.value[it] }
|
||||
}
|
||||
|
||||
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchActions() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.ID, Sheet.ACTIONS)
|
||||
val data = actionParser.parse(
|
||||
value = request.execute(),
|
||||
charactersSheets = characterSheetRepository.data.value
|
||||
)
|
||||
_data.emit(data)
|
||||
}
|
||||
}
|
||||
|
||||
private object Sheet {
|
||||
const val ID = "1fHfzeb8y5u9lEQB1iI-jBEhqu7YSip5sAajXcXK7VJ8"
|
||||
const val ACTIONS = "Actions"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.pixelized.rplexicon.facotry.model.alteration.AlterationParser
|
||||
import com.pixelized.rplexicon.facotry.model.alteration.CounterParser
|
||||
import com.pixelized.rplexicon.facotry.model.alteration.StatusParser
|
||||
import com.pixelized.rplexicon.model.Alteration
|
||||
import com.pixelized.rplexicon.model.Counter
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AlterationRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val alterationParser: AlterationParser,
|
||||
private val statusParser: StatusParser,
|
||||
private val counterParser: CounterParser,
|
||||
) {
|
||||
private val _alterations = MutableStateFlow<List<Alteration>>(emptyList())
|
||||
val alterations: StateFlow<List<Alteration>> get() = _alterations
|
||||
|
||||
private val _status = MutableStateFlow<Map<String, List<String>>>(emptyMap())
|
||||
val status: StateFlow<Map<String, List<String>>> get() = _status
|
||||
|
||||
private val _counter = MutableStateFlow<Map<String, List<Counter>>>(emptyMap())
|
||||
val counter: StateFlow<Map<String, List<Counter>>> get() = _counter
|
||||
|
||||
fun getAlterations(character: String): List<Alteration> {
|
||||
return status.value[character]?.mapNotNull { alterationName ->
|
||||
alterations.value.firstOrNull { it.name == alterationName }
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun getStatus(character: String, property: Property): List<Alteration.Status> {
|
||||
return getAlterations(character).mapNotNull { it.status[property] }
|
||||
}
|
||||
|
||||
fun getCounter(name: String?): List<Counter>? {
|
||||
return name?.let { counter.value[it] }
|
||||
}
|
||||
|
||||
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchAlterationSheet() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.ID, Sheet.ALTERATION_SHEET)
|
||||
val data = alterationParser.parse(value = request.execute())
|
||||
_alterations.emit(data)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchStatusSheet() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.ID, Sheet.STATUS_SHEET)
|
||||
val status = statusParser.parse(value = request.execute())
|
||||
_status.emit(status)
|
||||
val counter = counterParser.parse(values = request.execute())
|
||||
_counter.emit(counter)
|
||||
}
|
||||
}
|
||||
|
||||
private object Sheet {
|
||||
const val ID = "1fHfzeb8y5u9lEQB1iI-jBEhqu7YSip5sAajXcXK7VJ8"
|
||||
const val ALTERATION_SHEET = "Altérations"
|
||||
const val STATUS_SHEET = "État des personnages"
|
||||
const val STATUS_GID = "1246442302"
|
||||
|
||||
const val SHEET_URL = "https://docs.google.com/spreadsheets/d/${ID}/edit#gid=$STATUS_GID"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.facotry.RollParser
|
||||
import com.pixelized.rplexicon.facotry.model.CharacterSheetParser
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
|
@ -15,7 +13,7 @@ import javax.inject.Singleton
|
|||
@Singleton
|
||||
class CharacterSheetRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val rollParser: RollParser,
|
||||
private val characterSheetParser: CharacterSheetParser,
|
||||
) {
|
||||
private val _data = MutableStateFlow<Map<String, CharacterSheet>>(emptyMap())
|
||||
val data: StateFlow<Map<String, CharacterSheet>> get() = _data
|
||||
|
|
@ -28,68 +26,11 @@ class CharacterSheetRepository @Inject constructor(
|
|||
suspend fun fetchCharacterSheet() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.ID, Sheet.CHARACTER_SHEET)
|
||||
val data = request.execute()
|
||||
updateData(data = data)
|
||||
val data = characterSheetParser.parse(value = request.execute())
|
||||
_data.emit(data)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
private fun updateData(data: ValueRange?) {
|
||||
val sheet = data?.values?.sheet()
|
||||
|
||||
var id = 0
|
||||
val bru = sheet?.map { (it as? List<*>)?.get(1) }
|
||||
|
||||
val characterSheet = CharacterSheet(
|
||||
hitPoint = (bru?.get(Sheet.HIT_POINT) as? String)?.toIntOrNull() ?: 0,
|
||||
armorClass = (bru?.get(Sheet.ARMOR_CLASS) as? String)?.toIntOrNull() ?: 0,
|
||||
proficiency = (bru?.get(Sheet.PROFICIENCY) as? String)?.toIntOrNull() ?: 0,
|
||||
strength = (bru?.get(Sheet.STRENGTH) as? String)?.toIntOrNull() ?: 0,
|
||||
dexterity = (bru?.get(Sheet.DEXTERITY) as? String)?.toIntOrNull() ?: 0,
|
||||
constitution = (bru?.get(Sheet.CONSTITUTION) as? String)?.toIntOrNull() ?: 0,
|
||||
intelligence = (bru?.get(Sheet.INTELLIGENCE) as? String)?.toIntOrNull() ?: 0,
|
||||
wisdom = (bru?.get(Sheet.WISDOM) as? String)?.toIntOrNull() ?: 0,
|
||||
charisma = (bru?.get(Sheet.CHARISMA) as? String)?.toIntOrNull() ?: 0,
|
||||
strengthSavingThrows = (bru?.get(Sheet.STRENGTH_SAVING_THROWS) as? String)?.toIntOrNull()
|
||||
?: 0,
|
||||
dexteritySavingThrows = (bru?.get(Sheet.DEXTERITY_SAVING_THROWS) as? String)?.toIntOrNull()
|
||||
?: 0,
|
||||
constitutionSavingThrows = (bru?.get(Sheet.CONSTITUTION_SAVING_THROWS) as? String)?.toIntOrNull()
|
||||
?: 0,
|
||||
intelligenceSavingThrows = (bru?.get(Sheet.INTELLIGENCE_SAVING_THROWS) as? String)?.toIntOrNull()
|
||||
?: 0,
|
||||
wisdomSavingThrows = (bru?.get(Sheet.WISDOM_SAVING_THROWS) as? String)?.toIntOrNull()
|
||||
?: 0,
|
||||
charismaSavingThrows = (bru?.get(Sheet.CHARISMA_SAVING_THROWS) as? String)?.toIntOrNull()
|
||||
?: 0,
|
||||
acrobatics = (bru?.get(Sheet.ACROBATICS) as? String)?.toIntOrNull() ?: 0,
|
||||
animalHandling = (bru?.get(Sheet.ANIMAL_HANDLING) as? String)?.toIntOrNull() ?: 0,
|
||||
arcana = (bru?.get(Sheet.ARCANA) as? String)?.toIntOrNull() ?: 0,
|
||||
athletics = (bru?.get(Sheet.ATHLETICS) as? String)?.toIntOrNull() ?: 0,
|
||||
deception = (bru?.get(Sheet.DECEPTION) as? String)?.toIntOrNull() ?: 0,
|
||||
history = (bru?.get(Sheet.HISTORY) as? String)?.toIntOrNull() ?: 0,
|
||||
insight = (bru?.get(Sheet.INSIGHT) as? String)?.toIntOrNull() ?: 0,
|
||||
intimidation = (bru?.get(Sheet.INTIMIDATION) as? String)?.toIntOrNull() ?: 0,
|
||||
investigation = (bru?.get(Sheet.INVESTIGATION) as? String)?.toIntOrNull() ?: 0,
|
||||
medicine = (bru?.get(Sheet.MEDICINE) as? String)?.toIntOrNull() ?: 0,
|
||||
nature = (bru?.get(Sheet.NATURE) as? String)?.toIntOrNull() ?: 0,
|
||||
perception = (bru?.get(Sheet.PERCEPTION) as? String)?.toIntOrNull() ?: 0,
|
||||
performance = (bru?.get(Sheet.PERFORMANCE) as? String)?.toIntOrNull() ?: 0,
|
||||
persuasion = (bru?.get(Sheet.PERSUASION) as? String)?.toIntOrNull() ?: 0,
|
||||
religion = (bru?.get(Sheet.RELIGION) as? String)?.toIntOrNull() ?: 0,
|
||||
sleightOfHand = (bru?.get(Sheet.SLEIGHT_OF_HAND) as? String)?.toIntOrNull() ?: 0,
|
||||
stealth = (bru?.get(Sheet.STEALTH) as? String)?.toIntOrNull() ?: 0,
|
||||
survival = (bru?.get(Sheet.SURVIVAL) as? String)?.toIntOrNull() ?: 0,
|
||||
)
|
||||
|
||||
val rolls = bru?.subList(fromIndex = 34, bru.size)?.mapNotNull {
|
||||
rollParser.parseRoll(characterSheet = characterSheet, value = it?.toString())
|
||||
}
|
||||
|
||||
_data.tryEmit(
|
||||
hashMapOf("bru" to characterSheet)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "CharacterSheetRepository"
|
||||
|
|
@ -98,39 +39,5 @@ class CharacterSheetRepository @Inject constructor(
|
|||
private object Sheet {
|
||||
const val ID = "1fHfzeb8y5u9lEQB1iI-jBEhqu7YSip5sAajXcXK7VJ8"
|
||||
const val CHARACTER_SHEET = "Feuille de personnage"
|
||||
|
||||
const val HIT_POINT = 1
|
||||
const val ARMOR_CLASS = 2
|
||||
const val PROFICIENCY = 3
|
||||
const val STRENGTH = 4
|
||||
const val DEXTERITY = 5
|
||||
const val CONSTITUTION = 6
|
||||
const val INTELLIGENCE = 7
|
||||
const val WISDOM = 8
|
||||
const val CHARISMA = 9
|
||||
const val STRENGTH_SAVING_THROWS = 10
|
||||
const val DEXTERITY_SAVING_THROWS = 11
|
||||
const val CONSTITUTION_SAVING_THROWS = 12
|
||||
const val INTELLIGENCE_SAVING_THROWS = 13
|
||||
const val WISDOM_SAVING_THROWS = 14
|
||||
const val CHARISMA_SAVING_THROWS = 15
|
||||
const val ACROBATICS = 16 // Acrobaties
|
||||
const val ARCANA = 17 // Arcanes
|
||||
const val ATHLETICS = 18 // Athlétisme
|
||||
const val STEALTH = 19 // Discrétion
|
||||
const val ANIMAL_HANDLING = 20 // Dressage
|
||||
const val SLEIGHT_OF_HAND = 21 // Escamotage
|
||||
const val HISTORY = 22 // Histoire
|
||||
const val INTIMIDATION = 23 // Intimidation
|
||||
const val INSIGHT = 24 // Intuition
|
||||
const val INVESTIGATION = 25 // Investigation
|
||||
const val MEDICINE = 26 // Médecine
|
||||
const val NATURE = 27 // Nature
|
||||
const val PERCEPTION = 28 // Perception
|
||||
const val PERSUASION = 29 // Persuasion
|
||||
const val RELIGION = 30 // Religion
|
||||
const val PERFORMANCE = 31 // Représentation
|
||||
const val SURVIVAL = 32 // Survie
|
||||
const val DECEPTION = 33 // Tromperie
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.facotry.LexiconParser
|
||||
import com.pixelized.rplexicon.facotry.model.LexiconParser
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
|
|
@ -27,16 +27,11 @@ class LexiconRepository @Inject constructor(
|
|||
suspend fun fetchLexicon() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.ID, Sheet.LEXICON)
|
||||
val data = request.execute()
|
||||
updateData(data = data)
|
||||
val data = lexiconParser.parse(data = request.execute())
|
||||
_data.tryEmit(data)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
private fun updateData(data: ValueRange) {
|
||||
val lexicon = lexiconParser.parse(data)
|
||||
_data.tryEmit(lexicon)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "LexiconRepository"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package com.pixelized.rplexicon.repository.data
|
|||
|
||||
import com.google.api.services.sheets.v4.Sheets
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.facotry.LocationParser
|
||||
import com.pixelized.rplexicon.facotry.MarqueeParser
|
||||
import com.pixelized.rplexicon.facotry.model.LocationParser
|
||||
import com.pixelized.rplexicon.facotry.model.MarqueeParser
|
||||
import com.pixelized.rplexicon.model.Location
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
|
|
@ -40,7 +40,7 @@ class LocationRepository @Inject constructor(
|
|||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
private fun updateData(map: ValueRange, marquee: ValueRange) {
|
||||
private suspend fun updateData(map: ValueRange, marquee: ValueRange) {
|
||||
val marquees = marqueeParser
|
||||
.parse(data = marquee)
|
||||
.groupBy { it.map }
|
||||
|
|
@ -56,7 +56,7 @@ class LocationRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
_data.tryEmit(maps)
|
||||
_data.emit(maps)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.facotry.QuestParser
|
||||
import com.pixelized.rplexicon.facotry.model.QuestParser
|
||||
import com.pixelized.rplexicon.model.Quest
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
|
|
@ -29,7 +29,7 @@ class QuestRepository @Inject constructor(
|
|||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||
private fun updateData(data: ValueRange) {
|
||||
private suspend fun updateData(data: ValueRange) {
|
||||
val questEntries = questParser.parse(value = data)
|
||||
val questMap = questEntries.groupBy { it.title }
|
||||
|
||||
|
|
@ -40,6 +40,6 @@ class QuestRepository @Inject constructor(
|
|||
entries = questMap[item] ?: emptyList(),
|
||||
)
|
||||
}
|
||||
_data.tryEmit(quests)
|
||||
_data.emit(quests)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package com.pixelized.rplexicon.ui.composable
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
|
|
@ -18,7 +21,12 @@ fun Loader(
|
|||
refreshState: PullRefreshState,
|
||||
refreshing: State<Boolean>,
|
||||
) {
|
||||
if (refreshing.value) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
visible = refreshing.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -1,27 +1,51 @@
|
|||
package com.pixelized.rplexicon.ui.navigation.screens
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavOptionsBuilder
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||
import com.pixelized.rplexicon.ui.screens.character.CharacterSheetScreen
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ARG
|
||||
|
||||
private const val ROUTE = "characterSheet"
|
||||
const val CHARACTER_SHEET_ROUTE = ROUTE
|
||||
private const val CHARACTER_SHEET_NAME = "id"
|
||||
val CHARACTER_SHEET_ROUTE = "$ROUTE?${CHARACTER_SHEET_NAME.ARG}"
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetArgument(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
val SavedStateHandle.characterSheetArgument
|
||||
get() = CharacterSheetArgument(
|
||||
name = get(CHARACTER_SHEET_NAME)
|
||||
?: error("CharacterDetailArgument argument: $CHARACTER_SHEET_NAME"),
|
||||
)
|
||||
|
||||
fun NavGraphBuilder.composableCharacterSheet() {
|
||||
animatedComposable(
|
||||
route = CHARACTER_SHEET_ROUTE,
|
||||
animation = NavigationAnimation.Push,
|
||||
arguments = listOf(
|
||||
navArgument(name = CHARACTER_SHEET_NAME) {
|
||||
type = NavType.StringType
|
||||
nullable = false
|
||||
}
|
||||
),
|
||||
) {
|
||||
CharacterSheetScreen()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToCharacterSheet(
|
||||
name: String,
|
||||
option: NavOptionsBuilder.() -> Unit = {},
|
||||
) {
|
||||
val route = ROUTE
|
||||
val route = "$ROUTE?$CHARACTER_SHEET_NAME=$name"
|
||||
navigate(route = route, builder = option)
|
||||
}
|
||||
|
|
@ -52,6 +52,7 @@ import com.pixelized.rplexicon.LocalActivity
|
|||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.rootOption
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToHome
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
|
||||
|
|
|
|||
|
|
@ -2,37 +2,62 @@ package com.pixelized.rplexicon.ui.screens.character
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.pullrefresh.PullRefreshState
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.LocalRollOverlay
|
||||
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.Loader
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.Action
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.Counter
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.Proficiency
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.SavingsThrows
|
||||
|
|
@ -40,6 +65,8 @@ import com.pixelized.rplexicon.ui.screens.character.composable.SavingsThrowsUio
|
|||
import com.pixelized.rplexicon.ui.screens.character.composable.Stat
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.StatUio
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetUio(
|
||||
|
|
@ -75,14 +102,21 @@ data class CharacterSheetUio(
|
|||
val survival: ProficiencyUio,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun CharacterSheetScreen(
|
||||
viewModel: CharacterSheetViewModel = hiltViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenNavHost.current
|
||||
val overlay = LocalRollOverlay.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Surface(
|
||||
val refresh = rememberPullRefreshState(
|
||||
refreshing = false,
|
||||
onRefresh = { scope.launch { viewModel.update() } },
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
viewModel.sheet.value?.let {
|
||||
|
|
@ -91,7 +125,13 @@ fun CharacterSheetScreen(
|
|||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
state = rememberScrollState(),
|
||||
refreshState = refresh,
|
||||
refreshing = viewModel.isLoading,
|
||||
onRefresh = { scope.launch { viewModel.update() } },
|
||||
sheet = it,
|
||||
actions = viewModel.actions,
|
||||
counter = viewModel.counter,
|
||||
alterations = viewModel.alterations,
|
||||
onBack = {
|
||||
screen.popBackStack()
|
||||
},
|
||||
|
|
@ -245,6 +285,16 @@ fun CharacterSheetScreen(
|
|||
overlay.prepareRoll(roll = roll)
|
||||
overlay.showOverlay()
|
||||
},
|
||||
onHit = { id ->
|
||||
val roll = viewModel.onHitRoll(id)
|
||||
overlay.prepareRoll(roll = roll)
|
||||
overlay.showOverlay()
|
||||
},
|
||||
onDamage = { id ->
|
||||
val roll = viewModel.onDamageRoll(id)
|
||||
overlay.prepareRoll(roll = roll)
|
||||
overlay.showOverlay()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -254,12 +304,18 @@ fun CharacterSheetScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun CharacterSheetContent(
|
||||
modifier: Modifier = Modifier,
|
||||
state: ScrollState,
|
||||
refreshState: PullRefreshState,
|
||||
refreshing: State<Boolean>,
|
||||
onRefresh: () -> Unit,
|
||||
sheet: CharacterSheetUio,
|
||||
actions: State<List<ActionsUio>>,
|
||||
counter: State<List<CounterUio>>,
|
||||
alterations: State<List<String>>,
|
||||
onBack: () -> Unit,
|
||||
onStrength: () -> Unit,
|
||||
onDexterity: () -> Unit,
|
||||
|
|
@ -291,6 +347,8 @@ private fun CharacterSheetContent(
|
|||
onSleightOfHand: () -> Unit,
|
||||
onStealth: () -> Unit,
|
||||
onSurvival: () -> Unit,
|
||||
onHit: (id: String) -> Unit,
|
||||
onDamage: (id: String) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
|
|
@ -306,6 +364,14 @@ private fun CharacterSheetContent(
|
|||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onRefresh) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Refresh,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.character_sheet_title),
|
||||
|
|
@ -316,6 +382,7 @@ private fun CharacterSheetContent(
|
|||
) { paddingValues ->
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.pullRefresh(refreshState)
|
||||
.verticalScroll(state = state)
|
||||
.padding(paddingValues = paddingValues),
|
||||
) {
|
||||
|
|
@ -367,6 +434,41 @@ private fun CharacterSheetContent(
|
|||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f, fill = false)
|
||||
.padding(horizontal = 8.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(R.string.character_sheet_title_saving_throws),
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
|
@ -407,88 +509,250 @@ private fun CharacterSheetContent(
|
|||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onAcrobatics),
|
||||
proficiency = sheet.acrobatics,
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onAnimalHandling),
|
||||
proficiency = sheet.animalHandling,
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f, fill = false)
|
||||
.padding(horizontal = 8.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(R.string.character_sheet_title_proficiencies),
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onArcana),
|
||||
proficiency = sheet.arcana,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onAthletics),
|
||||
proficiency = sheet.athletics,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onDeception),
|
||||
proficiency = sheet.deception,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onHistory),
|
||||
proficiency = sheet.history,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onInsight),
|
||||
proficiency = sheet.insight,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onIntimidation),
|
||||
proficiency = sheet.intimidation,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onInvestigation),
|
||||
proficiency = sheet.investigation,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onMedicine),
|
||||
proficiency = sheet.medicine,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onNature),
|
||||
proficiency = sheet.nature,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onPerception),
|
||||
proficiency = sheet.perception,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onPerformance),
|
||||
proficiency = sheet.performance,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onPersuasion),
|
||||
proficiency = sheet.persuasion,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onReligion),
|
||||
proficiency = sheet.religion,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onSleightOfHand),
|
||||
proficiency = sheet.sleightOfHand,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onStealth),
|
||||
proficiency = sheet.stealth,
|
||||
)
|
||||
Proficiency(
|
||||
modifier = Modifier.clickable(onClick = onSurvival),
|
||||
proficiency = sheet.survival,
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.ddBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
),
|
||||
) {
|
||||
Proficiency(
|
||||
proficiency = sheet.acrobatics,
|
||||
onClick = onAcrobatics,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.animalHandling,
|
||||
onClick = onAnimalHandling,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.arcana,
|
||||
onClick = onArcana,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.athletics,
|
||||
onClick = onAthletics,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.deception,
|
||||
onClick = onDeception,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.history,
|
||||
onClick = onHistory,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.insight,
|
||||
onClick = onInsight,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.intimidation,
|
||||
onClick = onIntimidation,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.investigation,
|
||||
onClick = onInvestigation,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.medicine,
|
||||
onClick = onMedicine,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.nature,
|
||||
onClick = onNature,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.perception,
|
||||
onClick = onPerception,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.performance,
|
||||
onClick = onPerformance,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.persuasion,
|
||||
onClick = onPersuasion,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.religion,
|
||||
onClick = onReligion,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.sleightOfHand,
|
||||
onClick = onSleightOfHand,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.stealth,
|
||||
onClick = onStealth,
|
||||
)
|
||||
Proficiency(
|
||||
proficiency = sheet.survival,
|
||||
onClick = onSurvival,
|
||||
)
|
||||
}
|
||||
|
||||
if (actions.value.isNotEmpty() || counter.value.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f, fill = false)
|
||||
.padding(horizontal = 8.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(R.string.character_sheet_title_actions),
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
counter.value.forEach {
|
||||
Counter(
|
||||
counter = it,
|
||||
)
|
||||
}
|
||||
actions.value.forEach {
|
||||
Action(
|
||||
action = it,
|
||||
onHit = onHit,
|
||||
onDamage = onDamage,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alterations.value.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f, fill = false)
|
||||
.padding(horizontal = 8.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(R.string.character_sheet_title_alteration),
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
.ddBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
.padding(all = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
alterations.value.forEach {
|
||||
Text(
|
||||
text = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues = paddingValues)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Loader(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
refreshState = refreshState,
|
||||
refreshing = refreshing,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, heightDp = 2000)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, heightDp = 2000)
|
||||
private fun CharacterScreenPreview() {
|
||||
LexiconTheme {
|
||||
val bru = remember {
|
||||
|
|
@ -668,7 +932,40 @@ private fun CharacterScreenPreview() {
|
|||
CharacterSheetContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = rememberScrollState(),
|
||||
refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }),
|
||||
refreshing = remember { mutableStateOf(false) },
|
||||
onRefresh = { },
|
||||
sheet = bru,
|
||||
actions = remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
ActionsUio(
|
||||
title = "Battle Axe",
|
||||
hit = R.drawable.ic_d20_24,
|
||||
damage = R.drawable.ic_d8_24,
|
||||
),
|
||||
ActionsUio(
|
||||
title = "Greataxe",
|
||||
hit = R.drawable.ic_d20_24,
|
||||
damage = R.drawable.ic_d12_24,
|
||||
),
|
||||
)
|
||||
)
|
||||
},
|
||||
counter = remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
CounterUio(
|
||||
title = "Rage",
|
||||
value = 1,
|
||||
max = 2,
|
||||
),
|
||||
)
|
||||
)
|
||||
},
|
||||
alterations = remember {
|
||||
mutableStateOf(listOf("Rage", "Attaque téméraire"))
|
||||
},
|
||||
onBack = { },
|
||||
onStrength = { },
|
||||
onDexterity = { },
|
||||
|
|
@ -700,6 +997,8 @@ private fun CharacterScreenPreview() {
|
|||
onSleightOfHand = { },
|
||||
onStealth = { },
|
||||
onSurvival = { },
|
||||
onHit = { },
|
||||
onDamage = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,107 +1,231 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.facotry.ConvertCharacterSheetIntoDisplayableFactory
|
||||
import com.pixelized.rplexicon.facotry.displayable.ConvertActionIntoDisplayableFactory
|
||||
import com.pixelized.rplexicon.facotry.displayable.ConvertCharacterSheetIntoDisplayableFactory
|
||||
import com.pixelized.rplexicon.facotry.displayable.ConvertCounterIntoDisplayableFactory
|
||||
import com.pixelized.rplexicon.model.Action
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.model.Roll
|
||||
import com.pixelized.rplexicon.model.d20
|
||||
import com.pixelized.rplexicon.repository.data.ActionRepository
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio
|
||||
import com.pixelized.rplexicon.utilitary.extentions.context
|
||||
import com.pixelized.rplexicon.utilitary.extentions.modifier
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class CharacterSheetViewModel @Inject constructor(
|
||||
application: Application,
|
||||
repository: CharacterSheetRepository,
|
||||
factory: ConvertCharacterSheetIntoDisplayableFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val characterSheetFactory: ConvertCharacterSheetIntoDisplayableFactory,
|
||||
private val counterFactory: ConvertCounterIntoDisplayableFactory,
|
||||
private val actionFactory: ConvertActionIntoDisplayableFactory,
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val actionRepository: ActionRepository,
|
||||
) : AndroidViewModel(application = application) {
|
||||
private val argument = savedStateHandle.characterSheetArgument
|
||||
|
||||
private lateinit var model: CharacterSheet
|
||||
private val _sheet = mutableStateOf<CharacterSheetUio?>(null)
|
||||
val sheet: State<CharacterSheetUio?> get() = _sheet
|
||||
val sheet: State<CharacterSheetUio?>
|
||||
val alterations: State<List<String>>
|
||||
val actions: State<List<ActionsUio>>
|
||||
val counter: State<List<CounterUio>>
|
||||
|
||||
private val _isLoading = mutableStateOf(false)
|
||||
val isLoading: State<Boolean> get() = _isLoading
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
repository.fetchCharacterSheet()
|
||||
_sheet.value = repository.find("bru")?.let {
|
||||
sheet = mutableStateOf(
|
||||
characterRepository.find(name = argument.name)?.let {
|
||||
model = it
|
||||
factory.toUio(sheet = it)
|
||||
characterSheetFactory.toUio(sheet = model)
|
||||
}
|
||||
)
|
||||
alterations = mutableStateOf(
|
||||
alterationRepository.getAlterations(argument.name).map {
|
||||
it.name
|
||||
}
|
||||
)
|
||||
counter = mutableStateOf(
|
||||
alterationRepository.getCounter(argument.name)?.map {
|
||||
counterFactory.toUio(counter = it)
|
||||
} ?: emptyList()
|
||||
)
|
||||
actions = mutableStateOf(
|
||||
actionRepository.find(name = argument.name)?.map {
|
||||
actionFactory.toUio(action = it)
|
||||
} ?: emptyList()
|
||||
)
|
||||
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
characterRepository.data.collect {
|
||||
model = it.getValue(key = argument.name)
|
||||
sheet.value = characterSheetFactory.toUio(sheet = model)
|
||||
}
|
||||
}
|
||||
launch {
|
||||
actionRepository.data.collect {
|
||||
actions.value = it[argument.name]?.map { action ->
|
||||
actionFactory.toUio(action = action)
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
alterationRepository.counter.collect {
|
||||
counter.value = it[argument.name]?.map { counter ->
|
||||
counterFactory.toUio(counter = counter)
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
alterationRepository.status.collect {
|
||||
alterations.value = it[argument.name]?.map { name -> name } ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update() = coroutineScope {
|
||||
_isLoading.value = true
|
||||
val characterRequest = async {
|
||||
try {
|
||||
characterRepository.fetchCharacterSheet()
|
||||
actionRepository.fetchActions()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val statusRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchStatusSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val alterationRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchAlterationSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
awaitAll(characterRequest, statusRequest, alterationRequest)
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
fun onHitRoll(id: String): Roll {
|
||||
val action = actionRepository.find(argument.name)?.firstOrNull { it.title == id }
|
||||
return actionRoll(
|
||||
action = action,
|
||||
throws = action?.hit,
|
||||
)
|
||||
}
|
||||
|
||||
fun onDamageRoll(id: String): Roll {
|
||||
val action = actionRepository.find(argument.name)?.firstOrNull { it.title == id }
|
||||
return actionRoll(
|
||||
action = action,
|
||||
throws = action?.damage,
|
||||
)
|
||||
}
|
||||
|
||||
fun strengthRoll(): Roll = statRoll(
|
||||
abilityRes = R.string.character_sheet_stat_strength,
|
||||
abilityValue = model.strength,
|
||||
property = Property.STRENGTH,
|
||||
)
|
||||
|
||||
fun dexterityRoll(): Roll = statRoll(
|
||||
abilityRes = R.string.character_sheet_stat_dexterity,
|
||||
abilityValue = model.dexterity,
|
||||
property = Property.DEXTERITY,
|
||||
)
|
||||
|
||||
fun constitutionRoll(): Roll = statRoll(
|
||||
abilityRes = R.string.character_sheet_stat_constitution,
|
||||
abilityValue = model.constitution,
|
||||
property = Property.CONSTITUTION,
|
||||
)
|
||||
|
||||
fun intelligenceRoll(): Roll = statRoll(
|
||||
abilityRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
property = Property.INTELLIGENCE,
|
||||
)
|
||||
|
||||
fun wisdomRoll(): Roll = statRoll(
|
||||
abilityRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
property = Property.WISDOM,
|
||||
)
|
||||
|
||||
fun charismaRoll(): Roll = statRoll(
|
||||
abilityRes = R.string.character_sheet_stat_charisma,
|
||||
abilityValue = model.charisma,
|
||||
property = Property.CHARISMA,
|
||||
)
|
||||
|
||||
fun strengthSavingThrowsRoll(): Roll = savingThrowRoll(
|
||||
abilityRes = R.string.character_sheet_stat_strength,
|
||||
abilityValue = model.strength,
|
||||
masteryLevel = model.strengthSavingThrows,
|
||||
property = Property.STRENGTH_SAVING_THROW,
|
||||
)
|
||||
|
||||
fun dexteritySavingThrowsRoll(): Roll = savingThrowRoll(
|
||||
abilityRes = R.string.character_sheet_stat_dexterity,
|
||||
abilityValue = model.dexterity,
|
||||
masteryLevel = model.dexteritySavingThrows,
|
||||
property = Property.DEXTERITY_SAVING_THROW,
|
||||
)
|
||||
|
||||
fun constitutionSavingThrowsRoll(): Roll = savingThrowRoll(
|
||||
abilityRes = R.string.character_sheet_stat_constitution,
|
||||
abilityValue = model.constitution,
|
||||
masteryLevel = model.constitutionSavingThrows,
|
||||
property = Property.CONSTITUTION_SAVING_THROW,
|
||||
)
|
||||
|
||||
fun intelligenceSavingThrowsRoll(): Roll = savingThrowRoll(
|
||||
abilityRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
masteryLevel = model.intelligenceSavingThrows,
|
||||
property = Property.INTELLIGENCE_SAVING_THROW,
|
||||
)
|
||||
|
||||
fun wisdomSavingThrowsRoll(): Roll = savingThrowRoll(
|
||||
abilityRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
masteryLevel = model.wisdomSavingThrows,
|
||||
property = Property.WISDOM_SAVING_THROW,
|
||||
)
|
||||
|
||||
fun charismaSavingThrowsRoll(): Roll = savingThrowRoll(
|
||||
abilityRes = R.string.character_sheet_stat_charisma,
|
||||
abilityValue = model.charisma,
|
||||
masteryLevel = model.charismaSavingThrows,
|
||||
property = Property.CHARISMA_SAVING_THROW,
|
||||
)
|
||||
|
||||
fun acrobaticsRoll(): Roll = abilityRoll(
|
||||
|
|
@ -109,6 +233,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_dexterity,
|
||||
abilityValue = model.dexterity,
|
||||
masteryLevel = model.acrobatics,
|
||||
property = Property.ACROBATICS,
|
||||
)
|
||||
|
||||
fun animalHandlingRoll(): Roll = abilityRoll(
|
||||
|
|
@ -116,6 +241,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
masteryLevel = model.animalHandling,
|
||||
property = Property.ANIMAL_HANDLING,
|
||||
)
|
||||
|
||||
fun arcanaRoll(): Roll = abilityRoll(
|
||||
|
|
@ -123,6 +249,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
masteryLevel = model.arcana,
|
||||
property = Property.ARCANA,
|
||||
)
|
||||
|
||||
fun athleticsRoll(): Roll = abilityRoll(
|
||||
|
|
@ -130,6 +257,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_strength,
|
||||
abilityValue = model.strength,
|
||||
masteryLevel = model.athletics,
|
||||
property = Property.ATHLETICS,
|
||||
)
|
||||
|
||||
fun deceptionRoll(): Roll = abilityRoll(
|
||||
|
|
@ -137,6 +265,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_charisma,
|
||||
abilityValue = model.charisma,
|
||||
masteryLevel = model.deception,
|
||||
property = Property.DECEPTION,
|
||||
)
|
||||
|
||||
fun historyRoll(): Roll = abilityRoll(
|
||||
|
|
@ -144,6 +273,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
masteryLevel = model.history,
|
||||
property = Property.HISTORY,
|
||||
)
|
||||
|
||||
fun insightRoll(): Roll = abilityRoll(
|
||||
|
|
@ -151,6 +281,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
masteryLevel = model.insight,
|
||||
property = Property.INSIGHT,
|
||||
)
|
||||
|
||||
fun intimidationRoll(): Roll = abilityRoll(
|
||||
|
|
@ -158,6 +289,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_charisma,
|
||||
abilityValue = model.charisma,
|
||||
masteryLevel = model.intimidation,
|
||||
property = Property.INTIMIDATION,
|
||||
)
|
||||
|
||||
fun investigationRoll(): Roll = abilityRoll(
|
||||
|
|
@ -165,6 +297,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
masteryLevel = model.investigation,
|
||||
property = Property.INVESTIGATION,
|
||||
)
|
||||
|
||||
fun medicineRoll(): Roll = abilityRoll(
|
||||
|
|
@ -172,6 +305,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
masteryLevel = model.medicine,
|
||||
property = Property.MEDICINE,
|
||||
)
|
||||
|
||||
fun natureRoll(): Roll = abilityRoll(
|
||||
|
|
@ -179,6 +313,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
masteryLevel = model.nature,
|
||||
property = Property.NATURE,
|
||||
)
|
||||
|
||||
fun perceptionRoll(): Roll = abilityRoll(
|
||||
|
|
@ -186,6 +321,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
masteryLevel = model.perception,
|
||||
property = Property.PERCEPTION,
|
||||
)
|
||||
|
||||
fun performanceRoll(): Roll = abilityRoll(
|
||||
|
|
@ -193,6 +329,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_charisma,
|
||||
abilityValue = model.charisma,
|
||||
masteryLevel = model.performance,
|
||||
property = Property.PERFORMANCE,
|
||||
)
|
||||
|
||||
fun persuasionRoll(): Roll = abilityRoll(
|
||||
|
|
@ -200,6 +337,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_charisma,
|
||||
abilityValue = model.charisma,
|
||||
masteryLevel = model.persuasion,
|
||||
property = Property.PERSUASION,
|
||||
)
|
||||
|
||||
fun religionRoll(): Roll = abilityRoll(
|
||||
|
|
@ -207,6 +345,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_intelligence,
|
||||
abilityValue = model.intelligence,
|
||||
masteryLevel = model.religion,
|
||||
property = Property.RELIGION,
|
||||
)
|
||||
|
||||
fun sleightOfHandRoll(): Roll = abilityRoll(
|
||||
|
|
@ -214,6 +353,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_dexterity,
|
||||
abilityValue = model.dexterity,
|
||||
masteryLevel = model.sleightOfHand,
|
||||
property = Property.SLEIGHT_OF_HAND,
|
||||
)
|
||||
|
||||
fun stealthRoll(): Roll = abilityRoll(
|
||||
|
|
@ -221,6 +361,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_dexterity,
|
||||
abilityValue = model.dexterity,
|
||||
masteryLevel = model.stealth,
|
||||
property = Property.STEALTH,
|
||||
)
|
||||
|
||||
fun survivalRoll(): Roll = abilityRoll(
|
||||
|
|
@ -228,30 +369,45 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
relatedRes = R.string.character_sheet_stat_wisdom,
|
||||
abilityValue = model.wisdom,
|
||||
masteryLevel = model.survival,
|
||||
property = Property.SURVIVAL,
|
||||
)
|
||||
|
||||
// //////////////////////////////////////
|
||||
// region: Helpers
|
||||
////////////////////////////////////////
|
||||
// region: Helpers
|
||||
|
||||
private fun statRoll(
|
||||
abilityRes: Int,
|
||||
abilityValue: Int,
|
||||
property: Property,
|
||||
): Roll {
|
||||
val ability = context.getString(abilityRes)
|
||||
// get the alteration for a given player
|
||||
val alterations = alterationRepository.getStatus(
|
||||
character = model.name,
|
||||
property = property,
|
||||
)
|
||||
// check if any alteration give us advantage or disadvantage
|
||||
val advantage = alterations.any { it.advantage }
|
||||
val disadvantage = alterations.any { it.disadvantage }
|
||||
val fail = alterations.any { it.fail }
|
||||
|
||||
return Roll(
|
||||
title = context.getString(R.string.dice_roll_check_title, ability.uppercase()),
|
||||
highlight = ability,
|
||||
dices = listOf(
|
||||
Roll.Dice.d20(
|
||||
title = context.getString(R.string.dice_roll_check_detail, ability)
|
||||
d20(
|
||||
title = context.getString(R.string.dice_roll_check_detail, ability),
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
fail = fail,
|
||||
),
|
||||
),
|
||||
) + alterations.map { it.dices }.flatten(),
|
||||
bonus = listOf(
|
||||
Roll.Bonus(
|
||||
label = context.getString(R.string.dice_roll_bonus_detail, ability),
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, ability),
|
||||
value = abilityValue.modifier
|
||||
)
|
||||
),
|
||||
) + alterations.map { it.bonus }.flatten(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -260,29 +416,44 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
abilityValue: Int,
|
||||
masteryLevel: Int,
|
||||
masteryValue: Int = model.proficiency,
|
||||
property: Property,
|
||||
): Roll {
|
||||
val ability = context.getString(abilityRes)
|
||||
|
||||
// get the alteration for a given player
|
||||
val alterations = alterationRepository.getStatus(
|
||||
character = model.name,
|
||||
property = property,
|
||||
)
|
||||
// check if any alteration give us advantage or disadvantage
|
||||
val advantage = alterations.any { it.advantage }
|
||||
val disadvantage = alterations.any { it.disadvantage }
|
||||
val fail = alterations.any { it.fail }
|
||||
|
||||
return Roll(
|
||||
title = context.getString(R.string.dice_roll_saving_throw_title, ability.uppercase()),
|
||||
highlight = ability,
|
||||
dices = listOf(
|
||||
Roll.Dice.d20(
|
||||
title = context.getString(R.string.dice_roll_saving_throw_detail, ability)
|
||||
d20(
|
||||
title = context.getString(R.string.dice_roll_saving_throw_detail, ability),
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
fail = fail,
|
||||
),
|
||||
),
|
||||
) + alterations.map { it.dices }.flatten(),
|
||||
bonus = listOf(
|
||||
Roll.Bonus(
|
||||
label = context.getString(
|
||||
title = context.getString(
|
||||
if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency,
|
||||
context.getString(R.string.dice_roll_mastery_saving_throw)
|
||||
),
|
||||
value = masteryValue * masteryLevel,
|
||||
),
|
||||
Roll.Bonus(
|
||||
label = context.getString(R.string.dice_roll_bonus_detail, ability),
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, ability),
|
||||
value = abilityValue.modifier
|
||||
)
|
||||
),
|
||||
),
|
||||
) + alterations.map { it.bonus }.flatten(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -292,30 +463,166 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
abilityValue: Int,
|
||||
masteryLevel: Int,
|
||||
masteryValue: Int = model.proficiency,
|
||||
property: Property,
|
||||
): Roll {
|
||||
val ability = context.getString(abilityRes)
|
||||
val related = context.getString(relatedRes)
|
||||
|
||||
// get the alteration for a given player
|
||||
val alterations = alterationRepository.getStatus(
|
||||
character = model.name,
|
||||
property = property,
|
||||
)
|
||||
// check if any alteration give us advantage or disadvantage
|
||||
val advantage = alterations.any { it.advantage }
|
||||
val disadvantage = alterations.any { it.disadvantage }
|
||||
val fail = alterations.any { it.fail }
|
||||
|
||||
return Roll(
|
||||
title = context.getString(R.string.dice_roll_check_title, ability.uppercase()),
|
||||
highlight = ability,
|
||||
dices = listOf(
|
||||
Roll.Dice.d20(
|
||||
title = context.getString(R.string.dice_roll_check_detail, ability)
|
||||
d20(
|
||||
title = context.getString(R.string.dice_roll_check_detail, ability),
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
fail = fail,
|
||||
),
|
||||
),
|
||||
) + alterations.map { it.dices }.flatten(),
|
||||
bonus = listOf(
|
||||
Roll.Bonus(
|
||||
label = context.getString(
|
||||
title = context.getString(
|
||||
if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency,
|
||||
ability
|
||||
),
|
||||
value = masteryValue * masteryLevel,
|
||||
),
|
||||
Roll.Bonus(
|
||||
label = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = abilityValue.modifier
|
||||
)
|
||||
),
|
||||
) + alterations.map { it.bonus }.flatten(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun actionRoll(
|
||||
action: Action?,
|
||||
throws: Action.Throw?,
|
||||
): Roll {
|
||||
// build the title
|
||||
val title = context.getString(
|
||||
when (action?.type) {
|
||||
Action.Type.SPELL -> when (throws === action.hit) {
|
||||
true -> R.string.dice_roll_spell_hit_title
|
||||
else -> R.string.dice_roll_spell_damage_title
|
||||
}
|
||||
|
||||
else -> when (throws === action?.hit) {
|
||||
true -> R.string.dice_roll_attack_hit_title
|
||||
else -> R.string.dice_roll_attack_damage_title
|
||||
}
|
||||
},
|
||||
action?.title,
|
||||
)
|
||||
// get the alteration for roll and a given player
|
||||
val alterations = if (action?.type == Action.Type.ATTACK) {
|
||||
alterationRepository.getStatus(
|
||||
character = model.name,
|
||||
property = when (throws === action.hit) {
|
||||
true -> Property.ATTACK_ROLL
|
||||
else -> Property.ATTACK_DAMAGE_ROLL
|
||||
},
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
// check if any alteration give us advantage or disadvantage
|
||||
val advantage = alterations.any { it.advantage }
|
||||
val disadvantage = alterations.any { it.disadvantage }
|
||||
val fail = alterations.any { it.fail }
|
||||
// build the roll
|
||||
return Roll(
|
||||
title = title,
|
||||
highlight = action?.title,
|
||||
dices = listOf(
|
||||
Roll.Dice(
|
||||
title = action?.title,
|
||||
advantage = advantage,
|
||||
disadvantage = disadvantage,
|
||||
fail = fail,
|
||||
count = throws?.amount ?: 1,
|
||||
faces = throws?.faces ?: 20,
|
||||
)
|
||||
) + alterations.map { it.dices }.flatten(),
|
||||
bonus = (throws?.modifier
|
||||
?.mapNotNull {
|
||||
when (it) {
|
||||
Property.PROFICIENCY -> {
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_proficiency_bonus),
|
||||
value = model.proficiency,
|
||||
)
|
||||
}
|
||||
|
||||
Property.STRENGTH -> {
|
||||
val related = context.getString(R.string.character_sheet_stat_strength)
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = model.strength.modifier,
|
||||
)
|
||||
}
|
||||
|
||||
Property.DEXTERITY -> {
|
||||
val related = context.getString(R.string.character_sheet_stat_dexterity)
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = model.dexterity.modifier,
|
||||
)
|
||||
}
|
||||
|
||||
Property.CONSTITUTION -> {
|
||||
val related =
|
||||
context.getString(R.string.character_sheet_stat_constitution)
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = model.constitution.modifier,
|
||||
)
|
||||
}
|
||||
|
||||
Property.INTELLIGENCE -> {
|
||||
val related =
|
||||
context.getString(R.string.character_sheet_stat_intelligence)
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = model.intelligence.modifier,
|
||||
)
|
||||
}
|
||||
|
||||
Property.WISDOM -> {
|
||||
val related = context.getString(R.string.character_sheet_stat_wisdom)
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = model.wisdom.modifier,
|
||||
)
|
||||
}
|
||||
|
||||
Property.CHARISMA -> {
|
||||
val related = context.getString(R.string.character_sheet_stat_charisma)
|
||||
Roll.Bonus(
|
||||
title = context.getString(R.string.dice_roll_bonus_detail, related),
|
||||
value = model.charisma.modifier,
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
} ?: emptyList()) + alterations.map { it.bonus }.flatten(),
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
const val TAG = "CharacterSheetViewModel"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
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.graphics.RectangleShape
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
||||
|
||||
@Stable
|
||||
data class ActionsUio(
|
||||
val title: String,
|
||||
@DrawableRes val hit: Int?,
|
||||
@DrawableRes val damage: Int?,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Action(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(start = 16.dp, end = 8.dp, top = 4.dp, bottom = 4.dp),
|
||||
action: ActionsUio,
|
||||
onHit: (id: String) -> Unit,
|
||||
onDamage: (id: String) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.ddBorder(
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
inner = RectangleShape,
|
||||
)
|
||||
.padding(paddingValues = padding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = action.title,
|
||||
)
|
||||
|
||||
action.hit?.let {
|
||||
Button(
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
|
||||
onClick = { onHit(action.title) },
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
painter = painterResource(id = it),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
action.damage?.let {
|
||||
Button(
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
|
||||
onClick = { onDamage(action.title) },
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
painter = painterResource(id = it),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun ActionPreview(
|
||||
@PreviewParameter(ActionPreviewProvider::class) preview: ActionsUio
|
||||
) {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
Action(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
action = preview,
|
||||
onHit = { },
|
||||
onDamage = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionPreviewProvider : PreviewParameterProvider<ActionsUio> {
|
||||
override val values: Sequence<ActionsUio> = sequenceOf(
|
||||
ActionsUio(
|
||||
title = "Hache d'arme",
|
||||
hit = R.drawable.ic_d20_24,
|
||||
damage = R.drawable.ic_d8_24,
|
||||
),
|
||||
ActionsUio(
|
||||
title = "Explosion occulte",
|
||||
hit = R.drawable.ic_d20_24,
|
||||
damage = R.drawable.ic_d10_24,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class CounterUio(
|
||||
val title: String,
|
||||
val value: Int,
|
||||
val max: Int?,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun Counter(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(start = 16.dp, end = 8.dp, top = 4.dp, bottom = 4.dp),
|
||||
counter: CounterUio,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.ddBorder(
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
inner = RectangleShape,
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 40.dp)
|
||||
.padding(paddingValues = padding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = counter.title,
|
||||
)
|
||||
if (counter.max != null && counter.max <= 8) {
|
||||
repeat(counter.max) { index ->
|
||||
Marker(
|
||||
filled = (counter.max - index) > counter.value,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "${counter.value}",
|
||||
)
|
||||
counter.max?.let { max ->
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
text = "/",
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "$max",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Marker(
|
||||
modifier: Modifier = Modifier,
|
||||
filled: Boolean,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
tonalElevation = 8.dp,
|
||||
border = BorderStroke(width = 1.dp, color = MaterialTheme.lexicon.colorScheme.handle),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(all = 4.dp)
|
||||
.size(16.dp)
|
||||
.background(
|
||||
color = when (filled) {
|
||||
true -> MaterialTheme.colorScheme.primary
|
||||
else -> Color.Transparent
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun CounterPreview(
|
||||
@PreviewParameter(CounterPreviewProvider::class) preview: CounterUio
|
||||
) {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
Counter(counter = preview)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CounterPreviewProvider : PreviewParameterProvider<CounterUio> {
|
||||
override val values: Sequence<CounterUio> = sequenceOf(
|
||||
CounterUio(
|
||||
title = "Rage",
|
||||
value = 2,
|
||||
max = 2,
|
||||
),
|
||||
CounterUio(
|
||||
title = "Dé de vie",
|
||||
value = 0,
|
||||
max = 1,
|
||||
),
|
||||
CounterUio(
|
||||
title = "Sort de niveau 1",
|
||||
value = 3,
|
||||
max = 8,
|
||||
),
|
||||
CounterUio(
|
||||
title = "Point de vie",
|
||||
value = 15,
|
||||
max = null,
|
||||
),
|
||||
CounterUio(
|
||||
title = "Point de vie",
|
||||
value = 15,
|
||||
max = 25,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.character.composable
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
|
@ -36,11 +37,14 @@ data class ProficiencyUio(
|
|||
@Composable
|
||||
fun Proficiency(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(start = 16.dp, end = 27.dp),
|
||||
padding: PaddingValues = PaddingValues(horizontal = 8.dp),
|
||||
proficiency: ProficiencyUio,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.heightIn(48.dp),
|
||||
modifier = modifier
|
||||
.heightIn(48.dp)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
Row(
|
||||
|
|
@ -49,7 +53,7 @@ fun Proficiency(
|
|||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(size = 32.dp),
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
MasteryCircle(
|
||||
|
|
@ -91,6 +95,7 @@ private fun ProficiencyPreview(
|
|||
Surface {
|
||||
Proficiency(
|
||||
proficiency = proficiency,
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import android.content.res.Configuration
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -54,9 +56,14 @@ fun SavingsThrows(
|
|||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
MasteryCircle(
|
||||
multiplier = savingsThrows.multiplier,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
MasteryCircle(
|
||||
multiplier = savingsThrows.multiplier,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier.weight(weight = 1f, fill = true),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.lexicon.detail
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -53,6 +54,7 @@ import com.pixelized.rplexicon.model.Lexicon
|
|||
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.composable.BackgroundImage
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
|
||||
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
|
||||
|
|
@ -148,7 +150,9 @@ fun LexiconDetailScreen(
|
|||
LexiconDetailContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
item = viewModel.character,
|
||||
haveCharacterSheet = viewModel.haveCharacterSheet,
|
||||
onBack = { screen.popBackStack() },
|
||||
onCharacterSheet = { screen.navigateToCharacterSheet(name = it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +163,9 @@ private fun LexiconDetailContent(
|
|||
modifier: Modifier = Modifier,
|
||||
state: ScrollState = rememberScrollState(),
|
||||
item: State<LexiconDetailUio>,
|
||||
haveCharacterSheet: State<Boolean>,
|
||||
onBack: () -> Unit,
|
||||
onCharacterSheet: (String) -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val typography = MaterialTheme.typography
|
||||
|
|
@ -178,6 +184,16 @@ private fun LexiconDetailContent(
|
|||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
AnimatedVisibility(visible = haveCharacterSheet.value) {
|
||||
IconButton(onClick = { onCharacterSheet(item.value.name) }) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_d20_24),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.detail_title))
|
||||
},
|
||||
|
|
@ -358,7 +374,9 @@ private fun LexiconDetailPreview() {
|
|||
LexiconDetailContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
item = character,
|
||||
haveCharacterSheet = remember { mutableStateOf(true) },
|
||||
onBack = { },
|
||||
onCharacterSheet = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.repository.data.LexiconRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.lexiconDetailArgument
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -12,13 +13,15 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class LexiconDetailViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
repository: LexiconRepository,
|
||||
lexiconRepository: LexiconRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
) : ViewModel() {
|
||||
val character: State<LexiconDetailUio>
|
||||
val haveCharacterSheet: State<Boolean>
|
||||
|
||||
init {
|
||||
val argument = savedStateHandle.lexiconDetailArgument
|
||||
val source = repository.data.value[argument.id]
|
||||
val source = lexiconRepository.data.value[argument.id]
|
||||
|
||||
character = mutableStateOf(
|
||||
LexiconDetailUio(
|
||||
|
|
@ -35,5 +38,9 @@ class LexiconDetailViewModel @Inject constructor(
|
|||
highlightRace = argument.highlightRace && argument.race == source.race,
|
||||
)
|
||||
)
|
||||
|
||||
haveCharacterSheet = mutableStateOf(
|
||||
characterSheetRepository.find(source.name) != null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -63,7 +62,7 @@ fun LexiconScreen(
|
|||
refreshing = false,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
viewModel.fetchLexicon()
|
||||
viewModel.updateLexicon()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -92,16 +91,13 @@ fun LexiconScreen(
|
|||
HandleFetchError(
|
||||
errors = viewModel.error,
|
||||
onPermissionGranted = {
|
||||
viewModel.fetchLexicon()
|
||||
viewModel.updateLexicon()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterialApi::class,
|
||||
ExperimentalAnimationApi::class,
|
||||
)
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun LexiconScreenContent(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,15 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.repository.data.ActionRepository
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.repository.data.LexiconRepository
|
||||
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -19,7 +24,10 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class LexiconViewModel @Inject constructor(
|
||||
private val repository: LexiconRepository,
|
||||
private val lexiconRepository: LexiconRepository,
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val actionRepository: ActionRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _isLoading = mutableStateOf(false)
|
||||
|
|
@ -33,7 +41,7 @@ class LexiconViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
repository.data.collect { items ->
|
||||
lexiconRepository.data.collect { items ->
|
||||
_items.value = items.map { item ->
|
||||
LexiconItemUio(
|
||||
id = item.id,
|
||||
|
|
@ -66,14 +74,51 @@ class LexiconViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
fetchLexicon()
|
||||
_isLoading.value = true
|
||||
val characterRequest = async {
|
||||
try {
|
||||
characterSheetRepository.fetchCharacterSheet()
|
||||
actionRepository.fetchActions()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val alterationRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchAlterationSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val statusRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchStatusSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val lexiconRequest = async {
|
||||
fetchLexicon()
|
||||
}
|
||||
awaitAll(
|
||||
characterRequest,
|
||||
alterationRequest,
|
||||
statusRequest,
|
||||
lexiconRequest,
|
||||
)
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchLexicon() {
|
||||
suspend fun updateLexicon() {
|
||||
_isLoading.value = true
|
||||
fetchLexicon()
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
private suspend fun fetchLexicon() {
|
||||
try {
|
||||
_isLoading.value = true
|
||||
repository.fetchLexicon()
|
||||
lexiconRepository.fetchLexicon()
|
||||
}
|
||||
// user need to accept OAuth2 permission.
|
||||
catch (exception: UserRecoverableAuthIOException) {
|
||||
|
|
@ -88,10 +133,6 @@ class LexiconViewModel @Inject constructor(
|
|||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Default)
|
||||
}
|
||||
// clean the laoding state
|
||||
finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.facotry.ConvertRollIntoDisplayableFactory
|
||||
import com.pixelized.rplexicon.facotry.displayable.ConvertRollIntoDisplayableFactory
|
||||
import com.pixelized.rplexicon.model.Roll
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
|
||||
|
|
|
|||
|
|
@ -28,16 +28,18 @@ class LexiconTypography(
|
|||
val stamp: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = stampFontFamily,
|
||||
),
|
||||
val bodyDropCapSpan: SpanStyle = base.displaySmall.copy(
|
||||
val bodyDropCap: TextStyle = base.displaySmall.copy(
|
||||
fontFamily = regalFontFamily,
|
||||
baselineShift = BaselineShift(-0.3f),
|
||||
letterSpacing = (-6).sp
|
||||
).toSpanStyle(),
|
||||
val titleDropCapSpan: SpanStyle = base.displayLarge.copy(
|
||||
),
|
||||
val titleDropCap: TextStyle = base.displayLarge.copy(
|
||||
fontFamily = regalFontFamily,
|
||||
baselineShift = BaselineShift(-0.3f),
|
||||
letterSpacing = (-8).sp
|
||||
).toSpanStyle()
|
||||
),
|
||||
val bodyDropCapSpan: SpanStyle = bodyDropCap.toSpanStyle(),
|
||||
val titleDropCapSpan: SpanStyle = titleDropCap.toSpanStyle(),
|
||||
)
|
||||
|
||||
fun lexiconTypography() = LexiconTypography()
|
||||
|
|
@ -18,6 +18,7 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
|
|
@ -82,9 +83,10 @@ fun Modifier.ddBorder(
|
|||
innerWidth: Dp = 1.dp,
|
||||
): Modifier = composed {
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
val elevation = remember { derivedStateOf { if (isDarkTheme) 2.dp else 0.dp } }
|
||||
val colorScheme = MaterialTheme.lexicon.colorScheme
|
||||
|
||||
return@composed this
|
||||
return@composed this then Modifier
|
||||
.border(
|
||||
width = outlineWidth,
|
||||
color = colorScheme.characterSheet.outlineBorder,
|
||||
|
|
@ -99,23 +101,11 @@ fun Modifier.ddBorder(
|
|||
color = colorScheme.characterSheet.innerBorder,
|
||||
shape = inner,
|
||||
)
|
||||
.background(
|
||||
shape = inner,
|
||||
color = colorScheme.base.surfaceColorAtElevation(elevation.value)
|
||||
)
|
||||
.clip(
|
||||
shape = inner,
|
||||
)
|
||||
.thenIf(
|
||||
predicate = { isDarkTheme },
|
||||
thenModifier = Modifier.background(
|
||||
shape = inner,
|
||||
color = colorScheme.base.surfaceColorAtElevation(2.dp)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
inline fun Modifier.thenIf(
|
||||
crossinline predicate: () -> Boolean,
|
||||
thenModifier: Modifier? = null,
|
||||
elseModifier: Modifier? = null,
|
||||
): Modifier = when (predicate()) {
|
||||
true -> thenModifier?.let { this.then(it) } ?: this
|
||||
else -> elseModifier?.let { this.then(it) } ?: this
|
||||
}
|
||||
|
|
@ -57,6 +57,10 @@
|
|||
<string name="map_label">Coordonnées</string>
|
||||
|
||||
<string name="character_sheet_title">Feuille de personnage</string>
|
||||
<string name="character_sheet_title_saving_throws">Jet de sauvegarde</string>
|
||||
<string name="character_sheet_title_proficiencies">Maîtrises</string>
|
||||
<string name="character_sheet_title_actions">Actions</string>
|
||||
<string name="character_sheet_title_alteration">Altérations</string>
|
||||
<string name="character_sheet_stat_strength">Force</string>
|
||||
<string name="character_sheet_stat_strength_short">FOR</string>
|
||||
<string name="character_sheet_stat_dexterity">Dextérité</string>
|
||||
|
|
@ -69,7 +73,6 @@
|
|||
<string name="character_sheet_stat_wisdom_short">SAG</string>
|
||||
<string name="character_sheet_stat_charisma">Charisme</string>
|
||||
<string name="character_sheet_stat_charisma_short">CHA</string>
|
||||
<string name="character_sheet_saving_throws">Jet de sauvegarde</string>
|
||||
<string name="character_sheet_proficiency">Talent</string>
|
||||
<string name="character_sheet_proficiency_acrobatics">Acrobaties</string>
|
||||
<string name="character_sheet_proficiency_animal_handling">Dressage</string>
|
||||
|
|
@ -94,9 +97,15 @@
|
|||
<string name="dice_roll_mastery_expertise">Expertise \"%1$s\" </string>
|
||||
<string name="dice_roll_mastery_saving_throw">Jet de sauvegarde</string>
|
||||
|
||||
<string name="dice_roll_proficiency_bonus">Bonus de Maîtrise</string>
|
||||
<string name="dice_roll_expertise_bonus">Bonus d\'Expertise</string>
|
||||
<string name="dice_roll_check_title">TEST \"%1$s\"</string>
|
||||
<string name="dice_roll_check_detail">Test \"%1$s\"</string>
|
||||
<string name="dice_roll_bonus_detail">Bonus \"%1$s\"</string>
|
||||
<string name="dice_roll_attack_hit_title">Jet d\'attaque : \"%1$s\"</string>
|
||||
<string name="dice_roll_attack_damage_title">Jet de dommage : \"%1$s\"</string>
|
||||
<string name="dice_roll_spell_hit_title">Jet de sort : \"%1$s"</string>
|
||||
<string name="dice_roll_spell_damage_title">Jet de dommage : \"%1$s\"</string>
|
||||
|
||||
<string name="dice_roll_saving_throw_title">JET DE SAUVEGARDE : %1$s</string>
|
||||
<string name="dice_roll_saving_throw_detail">Sauvegarde de \"%1$s\"</string>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@
|
|||
<string name="map_label">Coordinates</string>
|
||||
|
||||
<string name="character_sheet_title">Character sheet</string>
|
||||
<string name="character_sheet_title_saving_throws">Saving Throws</string>
|
||||
<string name="character_sheet_title_proficiencies">Proficiencies</string>
|
||||
<string name="character_sheet_title_actions">Actions</string>
|
||||
<string name="character_sheet_title_alteration">Alterations</string>
|
||||
<string name="character_sheet_stat_strength">Strength</string>
|
||||
<string name="character_sheet_stat_strength_short">STR</string>
|
||||
<string name="character_sheet_stat_dexterity">Dexterity</string>
|
||||
|
|
@ -69,7 +73,6 @@
|
|||
<string name="character_sheet_stat_wisdom_short">WIS</string>
|
||||
<string name="character_sheet_stat_charisma">Charisma</string>
|
||||
<string name="character_sheet_stat_charisma_short">CHA</string>
|
||||
<string name="character_sheet_saving_throws">Saving Throws</string>
|
||||
<string name="character_sheet_proficiency">Proficiency</string>
|
||||
<string name="character_sheet_proficiency_acrobatics">Acrobatics</string>
|
||||
<string name="character_sheet_proficiency_animal_handling">Animal Handling</string>
|
||||
|
|
@ -94,9 +97,15 @@
|
|||
<string name="dice_roll_mastery_expertise">%1$s expertise</string>
|
||||
<string name="dice_roll_mastery_saving_throw">Saving throw</string>
|
||||
|
||||
<string name="dice_roll_proficiency_bonus">Proficiency bonus</string>
|
||||
<string name="dice_roll_expertise_bonus">Expertise bonus</string>
|
||||
<string name="dice_roll_check_title">%1$s CHECK</string>
|
||||
<string name="dice_roll_check_detail">%1$s check</string>
|
||||
<string name="dice_roll_bonus_detail">%1$s bonus</string>
|
||||
<string name="dice_roll_attack_hit_title">%1$s HIT</string>
|
||||
<string name="dice_roll_attack_damage_title">%1$s DAMAGE</string>
|
||||
<string name="dice_roll_spell_hit_title">%1$s HIT</string>
|
||||
<string name="dice_roll_spell_damage_title">%1$s DAMAGE</string>
|
||||
|
||||
<string name="dice_roll_saving_throw_title">%1$s SAVING THROW</string>
|
||||
<string name="dice_roll_saving_throw_detail">%1$s save</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue