Add Critical damage support.
This commit is contained in:
		
							parent
							
								
									880d19eb20
								
							
						
					
					
						commit
						b6c7009697
					
				
					 13 changed files with 341 additions and 113 deletions
				
			
		| 
						 | 
					@ -17,6 +17,7 @@ data class CharacterSheet(
 | 
				
			||||||
    val spell7: Counter?,
 | 
					    val spell7: Counter?,
 | 
				
			||||||
    val spell8: Counter?,
 | 
					    val spell8: Counter?,
 | 
				
			||||||
    val spell9: Counter?,
 | 
					    val spell9: Counter?,
 | 
				
			||||||
 | 
					    val criticalModifier: Int, // Critical Dice Multiplier
 | 
				
			||||||
    val armorClass: String, // Classe d'armure
 | 
					    val armorClass: String, // Classe d'armure
 | 
				
			||||||
    val speed: Int, // Vitesse
 | 
					    val speed: Int, // Vitesse
 | 
				
			||||||
    val strength: Int, // Force
 | 
					    val strength: Int, // Force
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
package com.pixelized.rplexicon.model
 | 
					package com.pixelized.rplexicon.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class Roll(
 | 
					data class Roll(
 | 
				
			||||||
 | 
					    val character: String,
 | 
				
			||||||
 | 
					    val canUseCriticalDice: Boolean,
 | 
				
			||||||
    val title: String,
 | 
					    val title: String,
 | 
				
			||||||
    val highlight: String? = null,
 | 
					    val highlight: String? = null,
 | 
				
			||||||
    val dices: List<Dice>,
 | 
					    val dices: List<Dice>,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
package com.pixelized.rplexicon.repository.parser
 | 
					package com.pixelized.rplexicon.repository.parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
					import com.google.api.services.sheets.v4.model.ValueRange
 | 
				
			||||||
import com.pixelized.rplexicon.repository.parser.alteration.CounterParser
 | 
					 | 
				
			||||||
import com.pixelized.rplexicon.model.CharacterSheet
 | 
					import com.pixelized.rplexicon.model.CharacterSheet
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.repository.parser.alteration.CounterParser
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
					import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
 | 
					import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
					import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
				
			||||||
| 
						 | 
					@ -54,50 +54,33 @@ class CharacterSheetParser @Inject constructor(
 | 
				
			||||||
                            maxHitPoint = item.parseString(MAX_HIT_POINT) ?: "1",
 | 
					                            maxHitPoint = item.parseString(MAX_HIT_POINT) ?: "1",
 | 
				
			||||||
                            lifeDice = item.parseInt(LIFE_DICE) ?: 0,
 | 
					                            lifeDice = item.parseInt(LIFE_DICE) ?: 0,
 | 
				
			||||||
                            spell1 = counterParser.parseCounter(
 | 
					                            spell1 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_1)
 | 
				
			||||||
                                    SPELL_LEVEL_1
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell2 = counterParser.parseCounter(
 | 
					                            spell2 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_2)
 | 
				
			||||||
                                    SPELL_LEVEL_2
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell3 = counterParser.parseCounter(
 | 
					                            spell3 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_3)
 | 
				
			||||||
                                    SPELL_LEVEL_3
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell4 = counterParser.parseCounter(
 | 
					                            spell4 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_4)
 | 
				
			||||||
                                    SPELL_LEVEL_4
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell5 = counterParser.parseCounter(
 | 
					                            spell5 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_5)
 | 
				
			||||||
                                    SPELL_LEVEL_5
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell6 = counterParser.parseCounter(
 | 
					                            spell6 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_6)
 | 
				
			||||||
                                    SPELL_LEVEL_6
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell7 = counterParser.parseCounter(
 | 
					                            spell7 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_7)
 | 
				
			||||||
                                    SPELL_LEVEL_7
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell8 = counterParser.parseCounter(
 | 
					                            spell8 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_8)
 | 
				
			||||||
                                    SPELL_LEVEL_8
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            spell9 = counterParser.parseCounter(
 | 
					                            spell9 = counterParser.parseCounter(
 | 
				
			||||||
                                value = item.parseString(
 | 
					                                value = item.parseString(SPELL_LEVEL_9)
 | 
				
			||||||
                                    SPELL_LEVEL_9
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
 | 
					                            criticalModifier = item.parseInt(CRITICAL_MODIFIER) ?: 2,
 | 
				
			||||||
                            armorClass = item.parseString(ARMOR_CLASS) ?: "10",
 | 
					                            armorClass = item.parseString(ARMOR_CLASS) ?: "10",
 | 
				
			||||||
                            speed = item.parseInt(SPEED) ?: 10,
 | 
					                            speed = item.parseInt(SPEED) ?: 10,
 | 
				
			||||||
                            strength = item.parseInt(STRENGTH) ?: 10,
 | 
					                            strength = item.parseInt(STRENGTH) ?: 10,
 | 
				
			||||||
| 
						 | 
					@ -157,6 +140,7 @@ class CharacterSheetParser @Inject constructor(
 | 
				
			||||||
        private const val SPELL_LEVEL_7 = "Sort de niveau 7"
 | 
					        private const val SPELL_LEVEL_7 = "Sort de niveau 7"
 | 
				
			||||||
        private const val SPELL_LEVEL_8 = "Sort de niveau 8"
 | 
					        private const val SPELL_LEVEL_8 = "Sort de niveau 8"
 | 
				
			||||||
        private const val SPELL_LEVEL_9 = "Sort de niveau 9"
 | 
					        private const val SPELL_LEVEL_9 = "Sort de niveau 9"
 | 
				
			||||||
 | 
					        private const val CRITICAL_MODIFIER = "Dé de critique"
 | 
				
			||||||
        private const val ARMOR_CLASS = "Classe d'armure"
 | 
					        private const val ARMOR_CLASS = "Classe d'armure"
 | 
				
			||||||
        private const val SPEED = "Vitesse"
 | 
					        private const val SPEED = "Vitesse"
 | 
				
			||||||
        private const val MASTERY = "Bonus de maîtrise"
 | 
					        private const val MASTERY = "Bonus de maîtrise"
 | 
				
			||||||
| 
						 | 
					@ -208,6 +192,7 @@ class CharacterSheetParser @Inject constructor(
 | 
				
			||||||
                SPELL_LEVEL_7,
 | 
					                SPELL_LEVEL_7,
 | 
				
			||||||
                SPELL_LEVEL_8,
 | 
					                SPELL_LEVEL_8,
 | 
				
			||||||
                SPELL_LEVEL_9,
 | 
					                SPELL_LEVEL_9,
 | 
				
			||||||
 | 
					                CRITICAL_MODIFIER,
 | 
				
			||||||
                ARMOR_CLASS,
 | 
					                ARMOR_CLASS,
 | 
				
			||||||
                SPEED,
 | 
					                SPEED,
 | 
				
			||||||
                MASTERY,
 | 
					                MASTERY,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -212,7 +212,6 @@ private fun CharacterSheetContent(
 | 
				
			||||||
            Surface(
 | 
					            Surface(
 | 
				
			||||||
                modifier = Modifier.padding(paddingValues = paddingValues),
 | 
					                modifier = Modifier.padding(paddingValues = paddingValues),
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                ModalBottomSheetLayout(
 | 
					                ModalBottomSheetLayout(
 | 
				
			||||||
                    sheetState = sheetState,
 | 
					                    sheetState = sheetState,
 | 
				
			||||||
                    sheetContent = {
 | 
					                    sheetContent = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,6 +66,7 @@ class AttackActionViewModel @Inject constructor(
 | 
				
			||||||
        return actionRoll(
 | 
					        return actionRoll(
 | 
				
			||||||
            attack = action,
 | 
					            attack = action,
 | 
				
			||||||
            throws = action?.hit,
 | 
					            throws = action?.hit,
 | 
				
			||||||
 | 
					            canUseCriticalDice = false,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,12 +75,14 @@ class AttackActionViewModel @Inject constructor(
 | 
				
			||||||
        return actionRoll(
 | 
					        return actionRoll(
 | 
				
			||||||
            attack = action,
 | 
					            attack = action,
 | 
				
			||||||
            throws = action?.damage,
 | 
					            throws = action?.damage,
 | 
				
			||||||
 | 
					            canUseCriticalDice = true,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun actionRoll(
 | 
					    private fun actionRoll(
 | 
				
			||||||
        attack: Attack?,
 | 
					        attack: Attack?,
 | 
				
			||||||
        throws: Throw?,
 | 
					        throws: Throw?,
 | 
				
			||||||
 | 
					        canUseCriticalDice: Boolean,
 | 
				
			||||||
    ): Roll {
 | 
					    ): Roll {
 | 
				
			||||||
        // build the title
 | 
					        // build the title
 | 
				
			||||||
        val title = context.getString(
 | 
					        val title = context.getString(
 | 
				
			||||||
| 
						 | 
					@ -123,6 +126,8 @@ class AttackActionViewModel @Inject constructor(
 | 
				
			||||||
        val fail = alterations.any { it.fail }
 | 
					        val fail = alterations.any { it.fail }
 | 
				
			||||||
        // build the roll
 | 
					        // build the roll
 | 
				
			||||||
        return Roll(
 | 
					        return Roll(
 | 
				
			||||||
 | 
					            character = character,
 | 
				
			||||||
 | 
					            canUseCriticalDice = canUseCriticalDice,
 | 
				
			||||||
            title = title,
 | 
					            title = title,
 | 
				
			||||||
            highlight = attack?.title,
 | 
					            highlight = attack?.title,
 | 
				
			||||||
            dices = listOf(
 | 
					            dices = listOf(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -209,6 +209,7 @@ class SpellsActionViewModel @Inject constructor(
 | 
				
			||||||
            spell = spell?.spell,
 | 
					            spell = spell?.spell,
 | 
				
			||||||
            titleRes = R.string.dice_roll_attack_damage_title,
 | 
					            titleRes = R.string.dice_roll_attack_damage_title,
 | 
				
			||||||
            throws = spell?.effect,
 | 
					            throws = spell?.effect,
 | 
				
			||||||
 | 
					            canUseCriticalDice = true,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -221,11 +222,14 @@ class SpellsActionViewModel @Inject constructor(
 | 
				
			||||||
        throws: Throw?,
 | 
					        throws: Throw?,
 | 
				
			||||||
        level: Int = 0,
 | 
					        level: Int = 0,
 | 
				
			||||||
        levelThrow: Throw? = null,
 | 
					        levelThrow: Throw? = null,
 | 
				
			||||||
 | 
					        canUseCriticalDice: Boolean = false,
 | 
				
			||||||
    ): Roll {
 | 
					    ): Roll {
 | 
				
			||||||
        // build the title
 | 
					        // build the title
 | 
				
			||||||
        val title = context.getString(titleRes, spell?.name)
 | 
					        val title = context.getString(titleRes, spell?.name)
 | 
				
			||||||
        // build the roll
 | 
					        // build the roll
 | 
				
			||||||
        return Roll(
 | 
					        return Roll(
 | 
				
			||||||
 | 
					            character = character,
 | 
				
			||||||
 | 
					            canUseCriticalDice = canUseCriticalDice,
 | 
				
			||||||
            title = title,
 | 
					            title = title,
 | 
				
			||||||
            highlight = spell?.name,
 | 
					            highlight = spell?.name,
 | 
				
			||||||
            dices = listOf(
 | 
					            dices = listOf(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,6 +298,8 @@ class ProficiencyViewModel @Inject constructor(
 | 
				
			||||||
        val fail = alterations.any { it.fail }
 | 
					        val fail = alterations.any { it.fail }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Roll(
 | 
					        return Roll(
 | 
				
			||||||
 | 
					            character = character,
 | 
				
			||||||
 | 
					            canUseCriticalDice = false,
 | 
				
			||||||
            title = context.getString(R.string.dice_roll_check_title, ability.uppercase()),
 | 
					            title = context.getString(R.string.dice_roll_check_title, ability.uppercase()),
 | 
				
			||||||
            highlight = ability,
 | 
					            highlight = ability,
 | 
				
			||||||
            dices = listOf(
 | 
					            dices = listOf(
 | 
				
			||||||
| 
						 | 
					@ -337,6 +339,8 @@ class ProficiencyViewModel @Inject constructor(
 | 
				
			||||||
        val fail = alterations.any { it.fail }
 | 
					        val fail = alterations.any { it.fail }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Roll(
 | 
					        return Roll(
 | 
				
			||||||
 | 
					            character = character,
 | 
				
			||||||
 | 
					            canUseCriticalDice = false,
 | 
				
			||||||
            title = context.getString(
 | 
					            title = context.getString(
 | 
				
			||||||
                R.string.dice_roll_saving_throw_title,
 | 
					                R.string.dice_roll_saving_throw_title,
 | 
				
			||||||
                ability.uppercase()
 | 
					                ability.uppercase()
 | 
				
			||||||
| 
						 | 
					@ -388,6 +392,8 @@ class ProficiencyViewModel @Inject constructor(
 | 
				
			||||||
        val fail = alterations.any { it.fail }
 | 
					        val fail = alterations.any { it.fail }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Roll(
 | 
					        return Roll(
 | 
				
			||||||
 | 
					            character = character,
 | 
				
			||||||
 | 
					            canUseCriticalDice = false,
 | 
				
			||||||
            title = context.getString(R.string.dice_roll_check_title, ability.uppercase()),
 | 
					            title = context.getString(R.string.dice_roll_check_title, ability.uppercase()),
 | 
				
			||||||
            highlight = ability,
 | 
					            highlight = ability,
 | 
				
			||||||
            dices = listOf(
 | 
					            dices = listOf(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,16 +9,23 @@ import javax.inject.Inject
 | 
				
			||||||
import kotlin.math.max
 | 
					import kotlin.math.max
 | 
				
			||||||
import kotlin.math.min
 | 
					import kotlin.math.min
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class RollResult(
 | 
				
			||||||
 | 
					    val dice: RollDiceUio?,
 | 
				
			||||||
 | 
					    val card: ThrowsCardUio?,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ConvertRollIntoDisplayableFactory @Inject constructor() {
 | 
					class ConvertRollIntoDisplayableFactory @Inject constructor() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun roll(roll: Roll): Pair<RollDiceUio?, ThrowsCardUio?> {
 | 
					    fun roll(
 | 
				
			||||||
 | 
					        roll: Roll,
 | 
				
			||||||
 | 
					        diceMultiplier: Int = 1,
 | 
				
			||||||
 | 
					    ): RollResult {
 | 
				
			||||||
        val mainDices = roll.dices.firstOrNull()
 | 
					        val mainDices = roll.dices.firstOrNull()
 | 
				
			||||||
        val bonusDicesList = roll.dices.subList(fromIndex = 1, toIndex = roll.dices.size)
 | 
					        val bonusDicesList = roll.dices.subList(fromIndex = 1, toIndex = roll.dices.size)
 | 
				
			||||||
        val allRolledValues = mutableListOf<Int>()
 | 
					        val allRolledValues = mutableListOf<Int>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val (rollDice, rollDetail) = mainDices?.let { dice ->
 | 
					        val (rollDice, rollDetail) = mainDices?.let { dice ->
 | 
				
			||||||
            val (label, sum) = dice.roll()
 | 
					            val (label, sum) = dice.roll(multiplier = diceMultiplier)
 | 
				
			||||||
            allRolledValues.add(sum)
 | 
					            allRolledValues.add(sum)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val diceIcon = dice.toRollCardUio(result = sum)
 | 
					            val diceIcon = dice.toRollCardUio(result = sum)
 | 
				
			||||||
| 
						 | 
					@ -31,7 +38,7 @@ class ConvertRollIntoDisplayableFactory @Inject constructor() {
 | 
				
			||||||
        val isCriticalFailure = rollDice?.isCriticalFailure ?: false
 | 
					        val isCriticalFailure = rollDice?.isCriticalFailure ?: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val diceBonus = bonusDicesList.map { dice ->
 | 
					        val diceBonus = bonusDicesList.map { dice ->
 | 
				
			||||||
            val (label, sum) = dice.roll()
 | 
					            val (label, sum) = dice.roll(multiplier = diceMultiplier)
 | 
				
			||||||
            allRolledValues.add(sum)
 | 
					            allRolledValues.add(sum)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            dice.toThrowsCardUio(
 | 
					            dice.toThrowsCardUio(
 | 
				
			||||||
| 
						 | 
					@ -49,23 +56,25 @@ class ConvertRollIntoDisplayableFactory @Inject constructor() {
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return rollDice to ThrowsCardUio(
 | 
					        return RollResult(
 | 
				
			||||||
            title = roll.title,
 | 
					            dice = rollDice,
 | 
				
			||||||
            highlight = roll.highlight,
 | 
					            card = ThrowsCardUio(
 | 
				
			||||||
            dice = mainDices?.faces?.icon,
 | 
					                title = roll.title,
 | 
				
			||||||
            roll = allRolledValues.toLabel(),
 | 
					                highlight = roll.highlight,
 | 
				
			||||||
            result = when {
 | 
					                dice = mainDices?.faces?.icon,
 | 
				
			||||||
                isCriticalSuccess -> (mainDices?.faces ?: 20).toString()
 | 
					                roll = allRolledValues.toLabel(),
 | 
				
			||||||
                isCriticalFailure -> "1"
 | 
					                result = when {
 | 
				
			||||||
                else -> "${allRolledValues.sum()}"
 | 
					                    isCriticalSuccess -> (mainDices?.faces ?: 20).toString()
 | 
				
			||||||
            },
 | 
					                    isCriticalFailure -> "1"
 | 
				
			||||||
            isCriticalSuccess = isCriticalSuccess,
 | 
					                    else -> "${allRolledValues.sum()}"
 | 
				
			||||||
            isCriticalFailure = isCriticalFailure,
 | 
					                },
 | 
				
			||||||
            details = rollDetail + diceBonus + flatBonus,
 | 
					                isCriticalSuccess = isCriticalSuccess,
 | 
				
			||||||
 | 
					                isCriticalFailure = isCriticalFailure,
 | 
				
			||||||
 | 
					                details = rollDetail + diceBonus + flatBonus,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun Roll.Dice.toRollCardUio(result: Int) = RollDiceUio(
 | 
					    private fun Roll.Dice.toRollCardUio(result: Int) = RollDiceUio(
 | 
				
			||||||
        icon = faces.icon,
 | 
					        icon = faces.icon,
 | 
				
			||||||
        isCriticalSuccess = count == 1 && faces == 20 && result == faces,
 | 
					        isCriticalSuccess = count == 1 && faces == 20 && result == faces,
 | 
				
			||||||
| 
						 | 
					@ -85,33 +94,33 @@ class ConvertRollIntoDisplayableFactory @Inject constructor() {
 | 
				
			||||||
        result = "$result",
 | 
					        result = "$result",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private data class RollResult(
 | 
					    private data class DiceRollResult(
 | 
				
			||||||
        val label: String,
 | 
					        val label: String,
 | 
				
			||||||
        val sum: Int,
 | 
					        val sum: Int,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun Roll.Dice.roll(): RollResult {
 | 
					    private fun Roll.Dice.roll(multiplier: Int): DiceRollResult {
 | 
				
			||||||
        return when {
 | 
					        return when {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            advantage && !disadvantage -> {
 | 
					            advantage && !disadvantage -> {
 | 
				
			||||||
                val roll = List(count) { random() to random() }
 | 
					                val roll = List(count * multiplier) { random() to random() }
 | 
				
			||||||
                RollResult(
 | 
					                DiceRollResult(
 | 
				
			||||||
                    label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
 | 
					                    label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
 | 
				
			||||||
                    sum = roll.sumOf { max(it.first, it.second) },
 | 
					                    sum = roll.sumOf { max(it.first, it.second) },
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            disadvantage && !advantage -> {
 | 
					            disadvantage && !advantage -> {
 | 
				
			||||||
                val roll = List(count) { random() to random() }
 | 
					                val roll = List(count * multiplier) { random() to random() }
 | 
				
			||||||
                RollResult(
 | 
					                DiceRollResult(
 | 
				
			||||||
                    label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
 | 
					                    label = roll.joinToString(" + ") { "${it.first}~${it.second}" },
 | 
				
			||||||
                    sum = roll.sumOf { min(it.first, it.second) },
 | 
					                    sum = roll.sumOf { min(it.first, it.second) },
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            else -> {
 | 
					            else -> {
 | 
				
			||||||
                val roll = List(count) { random() }
 | 
					                val roll = List(count * multiplier) { random() }
 | 
				
			||||||
                RollResult(
 | 
					                DiceRollResult(
 | 
				
			||||||
                    label = roll.toLabel(),
 | 
					                    label = roll.toLabel(),
 | 
				
			||||||
                    sum = roll.sum(),
 | 
					                    sum = roll.sum(),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.rolls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
				
			||||||
 | 
					import androidx.activity.compose.BackHandler
 | 
				
			||||||
import androidx.compose.animation.AnimatedContent
 | 
					import androidx.compose.animation.AnimatedContent
 | 
				
			||||||
import androidx.compose.animation.SizeTransform
 | 
					import androidx.compose.animation.SizeTransform
 | 
				
			||||||
import androidx.compose.animation.fadeIn
 | 
					import androidx.compose.animation.fadeIn
 | 
				
			||||||
| 
						 | 
					@ -11,14 +12,26 @@ import androidx.compose.animation.slideOutVertically
 | 
				
			||||||
import androidx.compose.animation.togetherWith
 | 
					import androidx.compose.animation.togetherWith
 | 
				
			||||||
import androidx.compose.foundation.clickable
 | 
					import androidx.compose.foundation.clickable
 | 
				
			||||||
import androidx.compose.foundation.layout.Box
 | 
					import androidx.compose.foundation.layout.Box
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.fillMaxHeight
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
					import androidx.compose.foundation.layout.fillMaxSize
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
					import androidx.compose.foundation.layout.fillMaxWidth
 | 
				
			||||||
import androidx.compose.foundation.layout.padding
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
 | 
					import androidx.compose.foundation.lazy.LazyColumn
 | 
				
			||||||
 | 
					import androidx.compose.foundation.lazy.items
 | 
				
			||||||
import androidx.compose.material.icons.Icons
 | 
					import androidx.compose.material.icons.Icons
 | 
				
			||||||
import androidx.compose.material.icons.outlined.Close
 | 
					import androidx.compose.material.icons.outlined.Close
 | 
				
			||||||
 | 
					import androidx.compose.material.icons.outlined.Menu
 | 
				
			||||||
 | 
					import androidx.compose.material3.DrawerState
 | 
				
			||||||
 | 
					import androidx.compose.material3.DrawerValue
 | 
				
			||||||
 | 
					import androidx.compose.material3.ExperimentalMaterial3Api
 | 
				
			||||||
import androidx.compose.material3.Icon
 | 
					import androidx.compose.material3.Icon
 | 
				
			||||||
import androidx.compose.material3.IconButton
 | 
					import androidx.compose.material3.IconButton
 | 
				
			||||||
 | 
					import androidx.compose.material3.ModalNavigationDrawer
 | 
				
			||||||
 | 
					import androidx.compose.material3.Scaffold
 | 
				
			||||||
import androidx.compose.material3.Surface
 | 
					import androidx.compose.material3.Surface
 | 
				
			||||||
 | 
					import androidx.compose.material3.TopAppBar
 | 
				
			||||||
 | 
					import androidx.compose.material3.TopAppBarDefaults
 | 
				
			||||||
 | 
					import androidx.compose.material3.rememberDrawerState
 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
import androidx.compose.runtime.MutableState
 | 
					import androidx.compose.runtime.MutableState
 | 
				
			||||||
import androidx.compose.runtime.Stable
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
| 
						 | 
					@ -26,124 +39,194 @@ import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.getValue
 | 
					import androidx.compose.runtime.getValue
 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
import androidx.compose.runtime.remember
 | 
					import androidx.compose.runtime.remember
 | 
				
			||||||
 | 
					import androidx.compose.runtime.rememberCoroutineScope
 | 
				
			||||||
import androidx.compose.runtime.saveable.rememberSaveable
 | 
					import androidx.compose.runtime.saveable.rememberSaveable
 | 
				
			||||||
import androidx.compose.runtime.setValue
 | 
					import androidx.compose.runtime.setValue
 | 
				
			||||||
import androidx.compose.ui.Alignment
 | 
					import androidx.compose.ui.Alignment
 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
 | 
					import androidx.compose.ui.graphics.Color
 | 
				
			||||||
import androidx.compose.ui.platform.LocalDensity
 | 
					import androidx.compose.ui.platform.LocalDensity
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					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 androidx.compose.ui.unit.dp
 | 
				
			||||||
import androidx.hilt.navigation.compose.hiltViewModel
 | 
					import androidx.hilt.navigation.compose.hiltViewModel
 | 
				
			||||||
import com.pixelized.rplexicon.LocalRollOverlay
 | 
					import com.pixelized.rplexicon.LocalRollOverlay
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.NO_WINDOW_INSETS
 | 
				
			||||||
import com.pixelized.rplexicon.R
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.model.Roll
 | 
					import com.pixelized.rplexicon.model.Roll
 | 
				
			||||||
import com.pixelized.rplexicon.ui.composable.BlurredOverlayHostState
 | 
					import com.pixelized.rplexicon.ui.composable.BlurredOverlayHostState
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlteration
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDice
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDice
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCard
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCard
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
 | 
				
			||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
					import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun RollOverlay(
 | 
					fun RollOverlay(
 | 
				
			||||||
    viewModel: RollOverlayViewModel = hiltViewModel(),
 | 
					    viewModel: RollOverlayViewModel = hiltViewModel(),
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val overlay = LocalRollOverlay.current
 | 
					    val overlay = LocalRollOverlay.current
 | 
				
			||||||
 | 
					    val scope = rememberCoroutineScope()
 | 
				
			||||||
 | 
					    val drawer = rememberDrawerState(initialValue = DrawerValue.Closed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    RollOverlayContent(
 | 
					    RollOverlayContent(
 | 
				
			||||||
        modifier = Modifier.fillMaxSize(),
 | 
					        modifier = Modifier.fillMaxSize(),
 | 
				
			||||||
 | 
					        drawer = drawer,
 | 
				
			||||||
        dice = viewModel.dice,
 | 
					        dice = viewModel.dice,
 | 
				
			||||||
        card = viewModel.card,
 | 
					        card = viewModel.card,
 | 
				
			||||||
 | 
					        alterations = viewModel.alterations,
 | 
				
			||||||
        showDetail = viewModel.showDetail,
 | 
					        showDetail = viewModel.showDetail,
 | 
				
			||||||
 | 
					        onMenu = {
 | 
				
			||||||
 | 
					            scope.launch { drawer.open() }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        onClose = {
 | 
				
			||||||
 | 
					            overlay.hideOverlay()
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        onDice = {
 | 
					        onDice = {
 | 
				
			||||||
            viewModel.roll()
 | 
					            viewModel.roll()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        onCard = {
 | 
					        onCard = {
 | 
				
			||||||
            viewModel.toggleDetail()
 | 
					            viewModel.toggleDetail()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        onClose = {
 | 
					        onAlteration = {
 | 
				
			||||||
            overlay.hideOverlay()
 | 
					            viewModel.onAlteration(it)
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    BackHandler(enabled = drawer.isOpen) {
 | 
				
			||||||
 | 
					        scope.launch { drawer.close() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@OptIn(ExperimentalMaterial3Api::class)
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
private fun RollOverlayContent(
 | 
					private fun RollOverlayContent(
 | 
				
			||||||
    modifier: Modifier = Modifier,
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
 | 
					    drawer: DrawerState,
 | 
				
			||||||
    dice: State<RollDiceUio>,
 | 
					    dice: State<RollDiceUio>,
 | 
				
			||||||
    card: State<ThrowsCardUio?>,
 | 
					    card: State<ThrowsCardUio?>,
 | 
				
			||||||
 | 
					    alterations: State<List<RollAlterationUio>>,
 | 
				
			||||||
    showDetail: State<Boolean>,
 | 
					    showDetail: State<Boolean>,
 | 
				
			||||||
 | 
					    onMenu: () -> Unit,
 | 
				
			||||||
 | 
					    onClose: () -> Unit,
 | 
				
			||||||
    onDice: () -> Unit,
 | 
					    onDice: () -> Unit,
 | 
				
			||||||
    onCard: () -> Unit,
 | 
					    onCard: () -> Unit,
 | 
				
			||||||
    onClose: () -> Unit,
 | 
					    onAlteration: (id: String) -> Unit,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val density = LocalDensity.current
 | 
					    val density = LocalDensity.current
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Box(
 | 
					    ModalNavigationDrawer(
 | 
				
			||||||
        modifier = modifier,
 | 
					        modifier = modifier,
 | 
				
			||||||
    ) {
 | 
					        drawerState = drawer,
 | 
				
			||||||
        RollDice(
 | 
					        drawerContent = {
 | 
				
			||||||
            modifier = Modifier
 | 
					            Surface {
 | 
				
			||||||
                .align(alignment = Alignment.Center)
 | 
					                LazyColumn(
 | 
				
			||||||
                .padding(horizontal = 16.dp),
 | 
					 | 
				
			||||||
            dice = dice,
 | 
					 | 
				
			||||||
            onDice = onDice,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        AnimatedContent(
 | 
					 | 
				
			||||||
            modifier = Modifier
 | 
					 | 
				
			||||||
                .fillMaxWidth()
 | 
					 | 
				
			||||||
                .align(Alignment.BottomCenter)
 | 
					 | 
				
			||||||
                .padding(all = 16.dp),
 | 
					 | 
				
			||||||
            targetState = card.value,
 | 
					 | 
				
			||||||
            transitionSpec = {
 | 
					 | 
				
			||||||
                val enter = fadeIn() + slideInVertically { with(density) { 64.dp.roundToPx() } }
 | 
					 | 
				
			||||||
                val exit = fadeOut() + slideOutVertically { with(density) { -64.dp.roundToPx() } }
 | 
					 | 
				
			||||||
                val transform = SizeTransform(clip = false)
 | 
					 | 
				
			||||||
                enter togetherWith exit using transform
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            label = "RollOverlayDisplay",
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            when (it) {
 | 
					 | 
				
			||||||
                null -> Box(
 | 
					 | 
				
			||||||
                    modifier = Modifier.fillMaxWidth(),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                else -> ThrowsCard(
 | 
					 | 
				
			||||||
                    modifier = Modifier
 | 
					                    modifier = Modifier
 | 
				
			||||||
                        .fillMaxWidth()
 | 
					                        .fillMaxHeight()
 | 
				
			||||||
                        .clickable(onClick = onCard),
 | 
					                        .fillMaxWidth(4f / 5f),
 | 
				
			||||||
                    throws = it,
 | 
					                ) {
 | 
				
			||||||
                    showDetail = showDetail,
 | 
					                    items(items = alterations.value) {
 | 
				
			||||||
                )
 | 
					                        RollAlteration(
 | 
				
			||||||
 | 
					                            modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
 | 
					                            alteration = it,
 | 
				
			||||||
 | 
					                            onClick = onAlteration,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
 | 
					        content = {
 | 
				
			||||||
 | 
					            Scaffold(
 | 
				
			||||||
 | 
					                containerColor = Color.Transparent,
 | 
				
			||||||
 | 
					                contentWindowInsets = NO_WINDOW_INSETS,
 | 
				
			||||||
 | 
					                topBar = {
 | 
				
			||||||
 | 
					                    TopAppBar(
 | 
				
			||||||
 | 
					                        colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
 | 
				
			||||||
 | 
					                        title = { },
 | 
				
			||||||
 | 
					                        navigationIcon = {
 | 
				
			||||||
 | 
					                            IconButton(onClick = onMenu) {
 | 
				
			||||||
 | 
					                                Icon(
 | 
				
			||||||
 | 
					                                    imageVector = Icons.Outlined.Menu,
 | 
				
			||||||
 | 
					                                    contentDescription = null,
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        actions = {
 | 
				
			||||||
 | 
					                            IconButton(onClick = onClose) {
 | 
				
			||||||
 | 
					                                Icon(
 | 
				
			||||||
 | 
					                                    imageVector = Icons.Outlined.Close,
 | 
				
			||||||
 | 
					                                    contentDescription = null,
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                content = { paddingValues ->
 | 
				
			||||||
 | 
					                    Box(
 | 
				
			||||||
 | 
					                        modifier = Modifier
 | 
				
			||||||
 | 
					                            .fillMaxSize()
 | 
				
			||||||
 | 
					                            .padding(paddingValues),
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        RollDice(
 | 
				
			||||||
 | 
					                            modifier = Modifier
 | 
				
			||||||
 | 
					                                .align(alignment = Alignment.Center)
 | 
				
			||||||
 | 
					                                .padding(horizontal = 16.dp),
 | 
				
			||||||
 | 
					                            dice = dice,
 | 
				
			||||||
 | 
					                            onDice = onDice,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        IconButton(
 | 
					                        AnimatedContent(
 | 
				
			||||||
            modifier = Modifier
 | 
					                            modifier = Modifier
 | 
				
			||||||
                .align(alignment = Alignment.TopEnd)
 | 
					                                .fillMaxWidth()
 | 
				
			||||||
                .padding(
 | 
					                                .align(Alignment.BottomCenter)
 | 
				
			||||||
                    top = 6.dp,
 | 
					                                .padding(all = 16.dp),
 | 
				
			||||||
                    end = 4.dp
 | 
					                            targetState = card.value,
 | 
				
			||||||
                ),
 | 
					                            transitionSpec = {
 | 
				
			||||||
            onClick = onClose
 | 
					                                val enter =
 | 
				
			||||||
        ) {
 | 
					                                    fadeIn() + slideInVertically { with(density) { 64.dp.roundToPx() } }
 | 
				
			||||||
            Icon(
 | 
					                                val exit =
 | 
				
			||||||
                imageVector = Icons.Outlined.Close,
 | 
					                                    fadeOut() + slideOutVertically { with(density) { -64.dp.roundToPx() } }
 | 
				
			||||||
                contentDescription = null,
 | 
					                                val transform = SizeTransform(clip = false)
 | 
				
			||||||
 | 
					                                enter togetherWith exit using transform
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            label = "RollOverlayDisplay",
 | 
				
			||||||
 | 
					                        ) {
 | 
				
			||||||
 | 
					                            when (it) {
 | 
				
			||||||
 | 
					                                null -> Box(
 | 
				
			||||||
 | 
					                                    modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                else -> ThrowsCard(
 | 
				
			||||||
 | 
					                                    modifier = Modifier
 | 
				
			||||||
 | 
					                                        .fillMaxWidth()
 | 
				
			||||||
 | 
					                                        .clickable(onClick = onCard),
 | 
				
			||||||
 | 
					                                    throws = it,
 | 
				
			||||||
 | 
					                                    showDetail = showDetail,
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    }
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
 | 
					@Preview(uiMode = UI_MODE_NIGHT_NO)
 | 
				
			||||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
 | 
					@Preview(uiMode = UI_MODE_NIGHT_YES)
 | 
				
			||||||
private fun RollOverlayPreview() {
 | 
					private fun RollOverlayPreview(
 | 
				
			||||||
 | 
					    @PreviewParameter(RollOverlayPreviewProvider::class) preview: DrawerValue
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    LexiconTheme {
 | 
					    LexiconTheme {
 | 
				
			||||||
        Surface {
 | 
					        Surface {
 | 
				
			||||||
            RollOverlayContent(
 | 
					            RollOverlayContent(
 | 
				
			||||||
                modifier = Modifier.fillMaxSize(),
 | 
					                modifier = Modifier.fillMaxSize(),
 | 
				
			||||||
 | 
					                drawer = rememberDrawerState(initialValue = preview),
 | 
				
			||||||
                dice = remember {
 | 
					                dice = remember {
 | 
				
			||||||
                    mutableStateOf(
 | 
					                    mutableStateOf(
 | 
				
			||||||
                        RollDiceUio(
 | 
					                        RollDiceUio(
 | 
				
			||||||
| 
						 | 
					@ -183,17 +266,30 @@ private fun RollOverlayPreview() {
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                alterations = remember {
 | 
				
			||||||
 | 
					                    mutableStateOf(
 | 
				
			||||||
 | 
					                        listOf(
 | 
				
			||||||
 | 
					                            RollAlterationUio(label = "Critique", checked = false),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                showDetail = remember {
 | 
					                showDetail = remember {
 | 
				
			||||||
                    mutableStateOf(true)
 | 
					                    mutableStateOf(true)
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                onMenu = { },
 | 
				
			||||||
 | 
					                onClose = { },
 | 
				
			||||||
                onDice = { },
 | 
					                onDice = { },
 | 
				
			||||||
                onCard = { },
 | 
					                onCard = { },
 | 
				
			||||||
                onClose = { },
 | 
					                onAlteration = { },
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private class RollOverlayPreviewProvider : PreviewParameterProvider<DrawerValue> {
 | 
				
			||||||
 | 
					    override val values: Sequence<DrawerValue> = sequenceOf(DrawerValue.Closed, DrawerValue.Open)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Stable
 | 
					@Stable
 | 
				
			||||||
interface BlurredRollOverlayHostState : BlurredOverlayHostState {
 | 
					interface BlurredRollOverlayHostState : BlurredOverlayHostState {
 | 
				
			||||||
    fun prepareRoll(roll: Roll)
 | 
					    fun prepareRoll(roll: Roll)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,21 @@
 | 
				
			||||||
package com.pixelized.rplexicon.ui.screens.rolls
 | 
					package com.pixelized.rplexicon.ui.screens.rolls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.app.Application
 | 
				
			||||||
import androidx.compose.runtime.State
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
 | 
					import androidx.lifecycle.AndroidViewModel
 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
import androidx.lifecycle.viewModelScope
 | 
					import androidx.lifecycle.viewModelScope
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.model.Alteration
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.model.CharacterSheet
 | 
				
			||||||
import com.pixelized.rplexicon.model.Roll
 | 
					import com.pixelized.rplexicon.model.Roll
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.repository.data.AlterationRepository
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
 | 
					import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.utilitary.extentions.context
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.extentions.icon
 | 
					import com.pixelized.rplexicon.utilitary.extentions.icon
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.extentions.switch
 | 
					import com.pixelized.rplexicon.utilitary.extentions.switch
 | 
				
			||||||
import dagger.hilt.android.lifecycle.HiltViewModel
 | 
					import dagger.hilt.android.lifecycle.HiltViewModel
 | 
				
			||||||
| 
						 | 
					@ -17,11 +26,18 @@ import javax.inject.Inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@HiltViewModel
 | 
					@HiltViewModel
 | 
				
			||||||
class RollOverlayViewModel @Inject constructor(
 | 
					class RollOverlayViewModel @Inject constructor(
 | 
				
			||||||
 | 
					    application: Application,
 | 
				
			||||||
 | 
					    private val characterSheetRepository: CharacterSheetRepository,
 | 
				
			||||||
    private val factory: ConvertRollIntoDisplayableFactory,
 | 
					    private val factory: ConvertRollIntoDisplayableFactory,
 | 
				
			||||||
) : ViewModel() {
 | 
					) : AndroidViewModel(application) {
 | 
				
			||||||
 | 
					    private val criticalLabel = context.getString(R.string.dice_roll_critical_label)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var rollJob: Job? = null
 | 
					    private var rollJob: Job? = null
 | 
				
			||||||
    private lateinit var roll: Roll
 | 
					    private lateinit var roll: Roll
 | 
				
			||||||
 | 
					    private var character: CharacterSheet? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val _alterations = mutableStateOf<List<RollAlterationUio>>(emptyList())
 | 
				
			||||||
 | 
					    val alterations: State<List<RollAlterationUio>> get() = _alterations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _dice = mutableStateOf(RollDiceUio())
 | 
					    private val _dice = mutableStateOf(RollDiceUio())
 | 
				
			||||||
    val dice: State<RollDiceUio> get() = _dice
 | 
					    val dice: State<RollDiceUio> get() = _dice
 | 
				
			||||||
| 
						 | 
					@ -34,14 +50,38 @@ class RollOverlayViewModel @Inject constructor(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun prepareRoll(roll: Roll) {
 | 
					    fun prepareRoll(roll: Roll) {
 | 
				
			||||||
        this.roll = roll
 | 
					        this.roll = roll
 | 
				
			||||||
 | 
					        this.character = characterSheetRepository.find(name = roll.character)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _dice.value = RollDiceUio(icon = roll.dices.maxOf { it.faces }.icon)
 | 
					        _dice.value = RollDiceUio(icon = roll.dices.maxOf { it.faces }.icon)
 | 
				
			||||||
        _card.value = null
 | 
					        _card.value = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _alterations.value = if (roll.canUseCriticalDice) {
 | 
				
			||||||
 | 
					            listOf(RollAlterationUio(label = criticalLabel, checked = false))
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            emptyList()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun onAlteration(id: String) {
 | 
				
			||||||
 | 
					        _alterations.value = _alterations.value.toMutableList().also { alterations ->
 | 
				
			||||||
 | 
					            val index = alterations.indexOfFirst { it.label == id }
 | 
				
			||||||
 | 
					            val alteration = alterations[index].let { it.copy(checked = it.checked.not()) }
 | 
				
			||||||
 | 
					            alterations.removeAt(index)
 | 
				
			||||||
 | 
					            alterations.add(index, alteration)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun roll() {
 | 
					    fun roll() {
 | 
				
			||||||
        rollJob?.cancel()
 | 
					        rollJob?.cancel()
 | 
				
			||||||
        rollJob = viewModelScope.launch {
 | 
					        rollJob = viewModelScope.launch {
 | 
				
			||||||
            val (dice, card) = factory.roll(roll = roll)
 | 
					            val isCritical = _alterations.value.firstOrNull { it.label == criticalLabel }?.checked ?: false
 | 
				
			||||||
 | 
					            val (dice, card) = factory.roll(
 | 
				
			||||||
 | 
					                roll = roll,
 | 
				
			||||||
 | 
					                diceMultiplier = when {
 | 
				
			||||||
 | 
					                    isCritical -> character?.criticalModifier ?: 2
 | 
				
			||||||
 | 
					                    else -> 1
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            // Start the roll animation.
 | 
					            // Start the roll animation.
 | 
				
			||||||
            _dice.value = _dice.value.copy(
 | 
					            _dice.value = _dice.value.copy(
 | 
				
			||||||
                animationSpec = RollDiceUio.TWEEN,
 | 
					                animationSpec = RollDiceUio.TWEEN,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.ui.screens.rolls.composable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
				
			||||||
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
				
			||||||
 | 
					import androidx.compose.foundation.clickable
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.Arrangement
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.Row
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.fillMaxWidth
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
 | 
					import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
 | 
					import androidx.compose.material3.Surface
 | 
				
			||||||
 | 
					import androidx.compose.material3.Switch
 | 
				
			||||||
 | 
					import androidx.compose.material3.Text
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
 | 
					import androidx.compose.ui.Alignment
 | 
				
			||||||
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Stable
 | 
				
			||||||
 | 
					data class RollAlterationUio(
 | 
				
			||||||
 | 
					    val label: String,
 | 
				
			||||||
 | 
					    val checked: Boolean
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					fun RollAlteration(
 | 
				
			||||||
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
 | 
					    alteration: RollAlterationUio,
 | 
				
			||||||
 | 
					    onClick: (id: String) -> Unit,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    Row(
 | 
				
			||||||
 | 
					        modifier = modifier
 | 
				
			||||||
 | 
					            .clickable { onClick(alteration.label) }
 | 
				
			||||||
 | 
					            .padding(horizontal = 16.dp, vertical = 8.dp),
 | 
				
			||||||
 | 
					        verticalAlignment = Alignment.CenterVertically,
 | 
				
			||||||
 | 
					        horizontalArrangement = Arrangement.SpaceBetween,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        Text(
 | 
				
			||||||
 | 
					            style = MaterialTheme.typography.bodyMedium,
 | 
				
			||||||
 | 
					            text = alteration.label,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Switch(
 | 
				
			||||||
 | 
					            enabled = true,
 | 
				
			||||||
 | 
					            checked = alteration.checked,
 | 
				
			||||||
 | 
					            onCheckedChange = { onClick(alteration.label) },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					@Preview(uiMode = UI_MODE_NIGHT_NO)
 | 
				
			||||||
 | 
					@Preview(uiMode = UI_MODE_NIGHT_YES)
 | 
				
			||||||
 | 
					private fun RollAlterationPreview(
 | 
				
			||||||
 | 
					    @PreviewParameter(RollAlterationPreviewProvider::class) preview: RollAlterationUio,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    LexiconTheme {
 | 
				
			||||||
 | 
					        Surface {
 | 
				
			||||||
 | 
					            RollAlteration(
 | 
				
			||||||
 | 
					                modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
 | 
					                alteration = preview,
 | 
				
			||||||
 | 
					                onClick = { },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private class RollAlterationPreviewProvider : PreviewParameterProvider<RollAlterationUio> {
 | 
				
			||||||
 | 
					    override val values: Sequence<RollAlterationUio> = sequenceOf(
 | 
				
			||||||
 | 
					        RollAlterationUio(label = "Critique", checked = false),
 | 
				
			||||||
 | 
					        RollAlterationUio(label = "Rage", checked = true),
 | 
				
			||||||
 | 
					        RollAlterationUio(label = "Bénédiction", checked = false),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -127,6 +127,7 @@
 | 
				
			||||||
    <string name="dice_roll_attack_damage_title">Jet de dommage : \"%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_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_spell_damage_title">Jet de dommage : \"%1$s\"</string>
 | 
				
			||||||
 | 
					    <string name="dice_roll_critical_label">Critique</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="dice_roll_saving_throw_title">JET DE SAUVEGARDE : %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>
 | 
					    <string name="dice_roll_saving_throw_detail">Sauvegarde de \"%1$s\"</string>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -127,6 +127,7 @@
 | 
				
			||||||
    <string name="dice_roll_attack_damage_title">%1$s DAMAGE</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_hit_title">%1$s HIT</string>
 | 
				
			||||||
    <string name="dice_roll_spell_damage_title">%1$s DAMAGE</string>
 | 
					    <string name="dice_roll_spell_damage_title">%1$s DAMAGE</string>
 | 
				
			||||||
 | 
					    <string name="dice_roll_critical_label">Critical</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="dice_roll_saving_throw_title">%1$s SAVING THROW</string>
 | 
					    <string name="dice_roll_saving_throw_title">%1$s SAVING THROW</string>
 | 
				
			||||||
    <string name="dice_roll_saving_throw_detail">%1$s save</string>
 | 
					    <string name="dice_roll_saving_throw_detail">%1$s save</string>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue