Add proper skills management.
This commit is contained in:
parent
6528b89f6b
commit
7b6f5b6430
45 changed files with 1355 additions and 706 deletions
|
|
@ -1,11 +1,11 @@
|
|||
package com.pixelized.rplexicon
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.model.Description
|
||||
import com.pixelized.rplexicon.repository.data.ActionRepository
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
|
|
@ -13,6 +13,7 @@ import com.pixelized.rplexicon.repository.data.DescriptionRepository
|
|||
import com.pixelized.rplexicon.repository.data.LexiconRepository
|
||||
import com.pixelized.rplexicon.repository.data.LocationRepository
|
||||
import com.pixelized.rplexicon.repository.data.QuestRepository
|
||||
import com.pixelized.rplexicon.repository.data.SkillRepository
|
||||
import com.pixelized.rplexicon.repository.data.SpellRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -31,6 +32,7 @@ class LauncherViewModel @Inject constructor(
|
|||
characterSheetRepository: CharacterSheetRepository,
|
||||
actionRepository: ActionRepository,
|
||||
spellRepository: SpellRepository,
|
||||
skillRepository: SkillRepository,
|
||||
descriptionRepository: DescriptionRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -46,6 +48,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
lexiconRepository.fetchLexicon()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Lexicon fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +56,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
locationRepository.fetchLocation()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Location fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +64,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
questRepository.fetchQuests()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Quest fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +72,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
characterSheetRepository.fetchCharacterSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("CharacterSheet fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +80,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
descriptionRepository.fetchDescription()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Skill/Spell description fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +90,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
alterationRepository.fetchAlterationSheet(sheets = characterSheetRepository.sheets)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Alteration lexicon fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -90,6 +98,7 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
actionRepository.fetchActions()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Action fail to update")
|
||||
}
|
||||
}
|
||||
|
|
@ -97,12 +106,25 @@ class LauncherViewModel @Inject constructor(
|
|||
try {
|
||||
spellRepository.fetchSpells()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Spell fail to update")
|
||||
}
|
||||
}
|
||||
awaitAll(alteration, action, spell)
|
||||
val skill = async {
|
||||
try {
|
||||
skillRepository.fetchSkills()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.tryEmit("Skill fail to update")
|
||||
}
|
||||
}
|
||||
awaitAll(alteration, action, spell, skill)
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LauncherViewModel"
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,12 @@ import com.pixelized.rplexicon.model.AssignedSpell
|
|||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.model.DiceThrow
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.model.Skill
|
||||
import com.pixelized.rplexicon.model.Throw
|
||||
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.SkillRepository
|
||||
import com.pixelized.rplexicon.repository.data.SpellRepository
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
|
||||
|
|
@ -41,6 +43,7 @@ class DiceThrowUseCase @Inject constructor(
|
|||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val actionRepository: ActionRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
private val skillRepository: SkillRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
) {
|
||||
fun roll(diceThrow: DiceThrow, alterationId: List<String>): DiceThrowResult? {
|
||||
|
|
@ -432,6 +435,16 @@ class DiceThrowUseCase @Inject constructor(
|
|||
alterations = alterations,
|
||||
)
|
||||
}
|
||||
|
||||
is DiceThrow.Skill -> {
|
||||
val skill = skillRepository.find(
|
||||
character = diceThrow.character,
|
||||
skill = diceThrow.skill,
|
||||
)
|
||||
skillThrow(
|
||||
skill = skill,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
|
|
@ -894,6 +907,50 @@ class DiceThrowUseCase @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun skillThrow(
|
||||
skill: Skill?,
|
||||
): DiceThrowResult {
|
||||
// retrieve some wording.
|
||||
val spellName = skill?.name
|
||||
val titleString = application.getString(R.string.dice_roll_spell_cast, spellName)
|
||||
|
||||
// create a list destined to contain all the values (rolled + bonus)
|
||||
val allValue = mutableListOf<Int>()
|
||||
|
||||
// main roll
|
||||
val result = roll(
|
||||
amount = skill?.effect?.amount ?: 1,
|
||||
faces = skill?.effect?.faces ?: 4,
|
||||
)
|
||||
allValue.add(result.value)
|
||||
|
||||
// build the result.
|
||||
return DiceThrowResult(
|
||||
dice = RollDiceUio(
|
||||
icon = (skill?.effect?.faces ?: 4).icon,
|
||||
result = "${result.value}",
|
||||
),
|
||||
card = ThrowsCardUio(
|
||||
title = titleString.uppercase(),
|
||||
highlight = spellName,
|
||||
dice = (skill?.effect?.faces ?: 4).icon,
|
||||
roll = allValue.toLabel(),
|
||||
result = "${allValue.sum()}",
|
||||
details = listOf(
|
||||
ThrowsCardUio.Detail(
|
||||
title = spellName,
|
||||
throws = ThrowsCardUio.Throw(
|
||||
dice = (skill?.effect?.faces ?: 4).icon,
|
||||
roll = "${skill?.effect?.amount ?: 1}d${skill?.effect?.faces ?: 4}",
|
||||
result = result.label,
|
||||
),
|
||||
result = "${result.value}",
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun roll(
|
||||
amount: Int = 1,
|
||||
faces: Int = 20,
|
||||
|
|
|
|||
|
|
@ -11,57 +11,13 @@ data class CharacterSheetFire(
|
|||
@set:PropertyName(HIT_POINT)
|
||||
var hitPoint: HitPoint? = null,
|
||||
|
||||
@get:PropertyName(RAGE)
|
||||
@set:PropertyName(RAGE)
|
||||
var rage: Int? = null,
|
||||
@get:PropertyName(SKILLS)
|
||||
@set:PropertyName(SKILLS)
|
||||
var skills: Map<String, Int> = emptyMap(),
|
||||
|
||||
@get:PropertyName(RELENTLESS_ENDURANCE)
|
||||
@set:PropertyName(RELENTLESS_ENDURANCE)
|
||||
var relentlessEndurance: Int? = null,
|
||||
|
||||
@get:PropertyName(DIVINE_CONDUIT)
|
||||
@set:PropertyName(DIVINE_CONDUIT)
|
||||
var divineConduit: Int? = null,
|
||||
|
||||
@get:PropertyName(BARDIC_INSPIRATION)
|
||||
@set:PropertyName(BARDIC_INSPIRATION)
|
||||
var bardicInspiration: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}1")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}1")
|
||||
var spell1: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}2")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}2")
|
||||
var spell2: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}3")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}3")
|
||||
var spell3: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}4")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}4")
|
||||
var spell4: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}5")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}5")
|
||||
var spell5: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}6")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}6")
|
||||
var spell6: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}7")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}7")
|
||||
var spell7: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}8")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}8")
|
||||
var spell8: Int? = null,
|
||||
|
||||
@get:PropertyName("${SPELL_SLOT_LEVEL_X}9")
|
||||
@set:PropertyName("${SPELL_SLOT_LEVEL_X}9")
|
||||
var spell9: Int? = null,
|
||||
@get:PropertyName(SPELLS)
|
||||
@set:PropertyName(SPELLS)
|
||||
var spells: Map<String, Int> = emptyMap(),
|
||||
) {
|
||||
@Keep
|
||||
@IgnoreExtraProperties
|
||||
|
|
@ -77,10 +33,8 @@ data class CharacterSheetFire(
|
|||
|
||||
companion object {
|
||||
const val HIT_POINT = "hit_point"
|
||||
const val RAGE = "rage"
|
||||
const val RELENTLESS_ENDURANCE = "relentless_endurance"
|
||||
const val DIVINE_CONDUIT = "divine_conduit"
|
||||
const val BARDIC_INSPIRATION = "bardic_inspiration"
|
||||
const val SPELL_SLOT_LEVEL_X = "spell_slot_level_"
|
||||
const val SKILLS = "skills"
|
||||
const val SPELLS = "spells"
|
||||
const val SPELL_PREFIX = "lvl_"
|
||||
}
|
||||
}
|
||||
|
|
@ -39,4 +39,5 @@ sealed class DiceThrow(val character: String) {
|
|||
class SpellAttack(character: String, val spell: String) : DiceThrow(character)
|
||||
class SpellDamage(character: String, val spell: String) : DiceThrow(character)
|
||||
class SpellEffect(character: String, val spell: String, val level: Int) : DiceThrow(character)
|
||||
class Skill(character: String, val skill: String) : DiceThrow(character)
|
||||
}
|
||||
9
app/src/main/java/com/pixelized/rplexicon/model/Skill.kt
Normal file
9
app/src/main/java/com/pixelized/rplexicon/model/Skill.kt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
data class Skill(
|
||||
val name: String,
|
||||
val amount: Int?,
|
||||
val rest: String?,
|
||||
val cost: String?,
|
||||
val effect: Throw?,
|
||||
)
|
||||
|
|
@ -75,16 +75,22 @@ class FirebaseRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun setToken(character: String, token: String, value: Int) {
|
||||
fun setSkill(character: String, name: String, value: Int) {
|
||||
val reference = database.getReference(
|
||||
"$PATH_CHARACTERS/$character/$token"
|
||||
"$PATH_CHARACTERS/$character/${CharacterSheetFire.SKILLS}/$name"
|
||||
)
|
||||
reference.setValue(value)
|
||||
}
|
||||
|
||||
fun setSpell(character: String, level: Int, value: Int) {
|
||||
val reference = database.getReference(
|
||||
"$PATH_CHARACTERS/$character/${CharacterSheetFire.SPELLS}/${CharacterSheetFire.SPELL_PREFIX}$level"
|
||||
)
|
||||
reference.setValue(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FirebaseRepository"
|
||||
|
||||
private const val PATH_CHARACTERS = "Characters"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.pixelized.rplexicon.model.Description
|
||||
|
||||
object Sheet {
|
||||
object Lexicon {
|
||||
|
|
@ -18,6 +17,7 @@ object Sheet {
|
|||
const val CHARACTER = "Feuille de personnage"
|
||||
const val ATTACK = "Attaques"
|
||||
const val MAGIC = "Magies"
|
||||
const val SKILL = "Capacités"
|
||||
const val MAGIC_LEXICON = "Lexique magique"
|
||||
const val ALTERATION = "Altérations"
|
||||
const val DESCRIPTION = "Descriptions"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.pixelized.rplexicon.repository.data
|
||||
|
||||
import com.pixelized.rplexicon.model.Skill
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.repository.parser.SkillParser
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SkillRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val skillParser: SkillParser,
|
||||
) {
|
||||
private val _skills = MutableStateFlow<Map<String, List<Skill>>>(emptyMap())
|
||||
val skills: StateFlow<Map<String, List<Skill>>> get() = _skills
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun find(character: String, skill: String): Skill? {
|
||||
return skills.value[character]?.firstOrNull { it.name == skill }
|
||||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchSkills() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.Character.ID, Sheet.Character.SKILL)
|
||||
val skills = skillParser.parse(data = request.execute())
|
||||
|
||||
_skills.emit(skills)
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.pixelized.rplexicon.repository.parser
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.Skill
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
class SkillParser @Inject constructor(
|
||||
private val throwParser: ThrowParser,
|
||||
) {
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(data: ValueRange): Map<String, List<Skill>> {
|
||||
val sheet = data.values.sheet()
|
||||
val values = hashMapOf<String, MutableList<Skill>>()
|
||||
|
||||
lateinit var sheetStructure: Map<String, Int>
|
||||
fun List<*>.parse(column: String): String? =
|
||||
(getOrNull(sheetStructure.getValue(column)) as? String)?.takeIf { it.isNotBlank() }
|
||||
|
||||
sheet?.forEachIndexed { index, row ->
|
||||
when {
|
||||
index == 0 -> {
|
||||
sheetStructure = row.checkSheetStructure(model = COLUMNS)
|
||||
}
|
||||
|
||||
row is List<*> -> {
|
||||
val character = row[0] as? String
|
||||
val name = row.parse(column = NAME)
|
||||
if (character?.isNotBlank() == true && name != null) {
|
||||
val skill = Skill(
|
||||
name = name,
|
||||
amount = row.parse(column = AMOUNT)?.toIntOrNull(),
|
||||
rest = row.parse(column = REST),
|
||||
cost = row.parse(column = COST),
|
||||
effect = throwParser.parse(row.parse(column = EFFECT)),
|
||||
)
|
||||
values.getOrPut(character) { mutableListOf() }.add(skill)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NAME = "Nom"
|
||||
private const val AMOUNT = "Quantité"
|
||||
private const val REST = "Récupération"
|
||||
private const val COST = "Coût"
|
||||
private const val EFFECT = "Effect"
|
||||
|
||||
private val COLUMNS = listOf(
|
||||
NAME,
|
||||
AMOUNT,
|
||||
REST,
|
||||
COST,
|
||||
EFFECT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -12,10 +13,12 @@ import androidx.compose.foundation.pager.PageSize
|
|||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.VerticalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -48,7 +51,11 @@ fun NumberPicker(
|
|||
) { index ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable { scope.launch { pager.animateScrollToPage(page = index) } }
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
onClick = { scope.launch { pager.animateScrollToPage(page = index) } },
|
||||
)
|
||||
.size(size = itemSize),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -30,8 +29,7 @@ import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
|||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class CounterEditDialogUio(
|
||||
val id: String,
|
||||
data class SkillEditDialogUio(
|
||||
val label: String,
|
||||
val value: Int,
|
||||
val max: Int,
|
||||
|
|
@ -39,13 +37,12 @@ data class CounterEditDialogUio(
|
|||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HandleCounterEditDialog(
|
||||
dialog: State<CounterEditDialogUio?>,
|
||||
fun HandleSkillEditDialog(
|
||||
dialog: State<SkillEditDialogUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (String, Int) -> Unit,
|
||||
onConfirm: (name: String, value: Int) -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
val scope = rememberCoroutineScope()
|
||||
val pager = rememberPagerState(initialPage = it.value) { it.max + 1 }
|
||||
Dialog(
|
||||
properties = remember { DialogProperties(usePlatformDefaultWidth = false) },
|
||||
|
|
@ -75,7 +72,7 @@ fun HandleCounterEditDialog(
|
|||
pager = pager,
|
||||
)
|
||||
TextButton(
|
||||
onClick = { onConfirm(it.id, pager.currentPage) },
|
||||
onClick = { onConfirm(it.label, pager.currentPage) },
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.pixelized.rplexicon.ui.composable.edit
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CutCornerShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.NumberPicker
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class SpellEditDialogUio(
|
||||
val level: Int,
|
||||
val value: Int,
|
||||
val max: Int,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HandleSpellEditDialog(
|
||||
dialog: State<SpellEditDialogUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (level: Int, value: Int) -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
val pager = rememberPagerState(initialPage = it.value) { it.max + 1 }
|
||||
Dialog(
|
||||
properties = remember { DialogProperties(usePlatformDefaultWidth = false) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.ddBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
text = stringResource(id = R.string.spell_level_chooser_label, it.level),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 64.dp, height = 1.dp)
|
||||
.background(color = MaterialTheme.lexicon.colorScheme.characterSheet.innerBorder),
|
||||
)
|
||||
NumberPicker(
|
||||
modifier = Modifier.width(width = 128.dp),
|
||||
pager = pager,
|
||||
)
|
||||
TextButton(
|
||||
onClick = { onConfirm(it.level, pager.currentPage) },
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,8 +57,8 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.Profici
|
|||
import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio.ID.*
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPagePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.AttackActionViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsActionViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.AttacksViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPage
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPagePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationViewModel
|
||||
|
|
@ -78,8 +78,8 @@ import kotlinx.coroutines.launch
|
|||
fun CharacterSheetScreen(
|
||||
viewModel: CharacterSheetViewModel = hiltViewModel(),
|
||||
proficiencyViewModel: ProficiencyViewModel = hiltViewModel(),
|
||||
attackViewModel: AttackActionViewModel = hiltViewModel(),
|
||||
spellsViewModel: SpellsActionViewModel = hiltViewModel(),
|
||||
attacksViewModel: AttacksViewModel = hiltViewModel(),
|
||||
spellsViewModel: SpellsViewModel = hiltViewModel(),
|
||||
alterationsViewModel: AlterationViewModel = hiltViewModel(),
|
||||
) {
|
||||
val snack = LocalSnack.current
|
||||
|
|
@ -96,7 +96,7 @@ fun CharacterSheetScreen(
|
|||
)
|
||||
val pagerState = rememberPagerState {
|
||||
val haveSheet = proficiencyViewModel.sheet.value != null
|
||||
val haveAction = attackViewModel.attacks.value.isNotEmpty()
|
||||
val haveAction = attacksViewModel.attacks.value.isNotEmpty()
|
||||
val haveSpell = spellsViewModel.spells.value.isNotEmpty()
|
||||
val haveAlteration = alterationsViewModel.alterations.value.isNotEmpty()
|
||||
haveSheet.toInt() + (haveAction || haveSpell).toInt() + haveAlteration.toInt()
|
||||
|
|
@ -140,7 +140,7 @@ fun CharacterSheetScreen(
|
|||
actions = {
|
||||
ActionPage(
|
||||
sheetState = sheetState,
|
||||
attackViewModel = attackViewModel,
|
||||
attacksViewModel = attacksViewModel,
|
||||
spellsViewModel = spellsViewModel,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.pixelized.rplexicon.repository.authentication.FirebaseRepository
|
|||
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.SkillRepository
|
||||
import com.pixelized.rplexicon.repository.data.SpellRepository
|
||||
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
|
|
@ -28,6 +29,7 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
private val alterationRepository: AlterationRepository,
|
||||
private val actionRepository: ActionRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
private val skillRepository: SkillRepository,
|
||||
private val firebaseRepository: FirebaseRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
|
@ -99,7 +101,18 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
awaitAll(alterations, actions, spells)
|
||||
val skill = async {
|
||||
try {
|
||||
if (force || skillRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
skillRepository.fetchSkills()
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
awaitAll(alterations, actions, spells, skill)
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.ui.screens.rolls.composable
|
||||
package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
|
|
@ -30,17 +30,17 @@ import androidx.compose.ui.unit.dp
|
|||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
data class RollAlterationUio(
|
||||
data class AlterationItemUio(
|
||||
val label: String,
|
||||
val subLabel: String?,
|
||||
val checked: Boolean,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RollAlteration(
|
||||
fun AlterationItem(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
alteration: RollAlterationUio,
|
||||
alteration: AlterationItemUio,
|
||||
onInfo: (id: String) -> Unit,
|
||||
onClick: (id: String) -> Unit,
|
||||
) {
|
||||
|
|
@ -90,11 +90,11 @@ fun RollAlteration(
|
|||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun RollAlterationPreview(
|
||||
@PreviewParameter(RollAlterationPreviewProvider::class) preview: RollAlterationUio,
|
||||
@PreviewParameter(RollAlterationPreviewProvider::class) preview: AlterationItemUio,
|
||||
) {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
RollAlteration(
|
||||
AlterationItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
alteration = preview,
|
||||
onInfo = { },
|
||||
|
|
@ -104,19 +104,19 @@ private fun RollAlterationPreview(
|
|||
}
|
||||
}
|
||||
|
||||
private class RollAlterationPreviewProvider : PreviewParameterProvider<RollAlterationUio> {
|
||||
override val values: Sequence<RollAlterationUio> = sequenceOf(
|
||||
RollAlterationUio(
|
||||
private class RollAlterationPreviewProvider : PreviewParameterProvider<AlterationItemUio> {
|
||||
override val values: Sequence<AlterationItemUio> = sequenceOf(
|
||||
AlterationItemUio(
|
||||
label = "Critique",
|
||||
checked = false,
|
||||
subLabel = null,
|
||||
),
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = "Rage",
|
||||
checked = true,
|
||||
subLabel = "Barbare",
|
||||
),
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = "Bénédiction",
|
||||
checked = false,
|
||||
subLabel = "Clerc",
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
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.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
|
||||
@Composable
|
||||
fun AttackHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(id = R.string.character_sheet_attack_title).let {
|
||||
AnnotatedString(
|
||||
text = it,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, it.length),
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Divider(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
color = MaterialTheme.lexicon.colorScheme.placeholder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun SkillHeaderPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
SkillHeader()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Column
|
|||
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.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -20,7 +21,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -29,6 +29,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.Attack
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
|
|
@ -51,13 +52,14 @@ data class AttackUio(
|
|||
@Composable
|
||||
fun Attack(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
padding: PaddingValues = PaddingValues(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 8.dp),
|
||||
weapon: AttackUio,
|
||||
onHit: (String) -> Unit,
|
||||
onDamage: (String) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.heightIn(min = 52.dp)
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
|
|
@ -79,7 +81,7 @@ fun Attack(
|
|||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontStyle = FontStyle.Italic,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(id = weapon.type),
|
||||
|
|
@ -87,26 +89,32 @@ fun Attack(
|
|||
weapon.range?.let { range ->
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontStyle = FontStyle.Italic,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = range,
|
||||
)
|
||||
}
|
||||
}
|
||||
weapon.hit?.let { dice ->
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { weapon.name.let(onHit) }
|
||||
)
|
||||
}
|
||||
weapon.damage?.let { dice ->
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { weapon.name.let(onDamage) }
|
||||
)
|
||||
if (weapon.hit != null || weapon.damage != null) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
weapon.hit?.let { dice ->
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { weapon.name.let(onHit) }
|
||||
)
|
||||
}
|
||||
weapon.damage?.let { dice ->
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { weapon.name.let(onDamage) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
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.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
|
||||
@Composable
|
||||
fun SkillHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(id = R.string.character_sheet_skill_title).let {
|
||||
AnnotatedString(
|
||||
text = it,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, it.length),
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Divider(
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
color = MaterialTheme.lexicon.colorScheme.placeholder,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun SkillHeaderPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
SkillHeader()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
||||
|
||||
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.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
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.R
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.CounterButton
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
data class SkillItemUio(
|
||||
val label: String,
|
||||
val translate: String?,
|
||||
val rest: String?,
|
||||
val cost: String?,
|
||||
val effect: Dice?,
|
||||
val value: Int?,
|
||||
val max: Int?,
|
||||
val haveDetail: Boolean,
|
||||
) {
|
||||
class Dice(
|
||||
@DrawableRes val icon: Int,
|
||||
val label: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SkillItem(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 8.dp),
|
||||
skill: SkillItemUio,
|
||||
onInfo: (SkillItemUio) -> Unit,
|
||||
onThrow: (SkillItemUio) -> Unit,
|
||||
onSkill: (SkillItemUio) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable { onSkill(skill) }
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = skill.label,
|
||||
)
|
||||
skill.translate?.let {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Light,
|
||||
fontStyle = FontStyle.Italic,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
skill.cost?.let {
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
skill.rest?.let {
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (skill.haveDetail) {
|
||||
IconButton(onClick = { onInfo(skill) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
skill.effect?.let { effect ->
|
||||
DiceButton(
|
||||
icon = effect.icon,
|
||||
text = effect.label,
|
||||
onClick = { onThrow(skill) },
|
||||
)
|
||||
}
|
||||
|
||||
skill.max?.let {
|
||||
CounterButton(
|
||||
value = skill.value,
|
||||
max = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun CounterItemPreview(
|
||||
@PreviewParameter(CounterItemPreviewProvider::class) preview: SkillItemUio
|
||||
) {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
SkillItem(
|
||||
skill = preview,
|
||||
onSkill = { },
|
||||
onInfo = { },
|
||||
onThrow = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberTokenListStatePreview(): State<List<SkillItemUio>> = remember {
|
||||
val provider = CounterItemPreviewProvider()
|
||||
mutableStateOf(provider.values.toList())
|
||||
}
|
||||
|
||||
private class CounterItemPreviewProvider : PreviewParameterProvider<SkillItemUio> {
|
||||
override val values: Sequence<SkillItemUio> = sequenceOf(
|
||||
SkillItemUio(
|
||||
label = "Endurance Implacable",
|
||||
translate = "Relentless Endurance",
|
||||
rest = "Récupération repos long",
|
||||
cost = null,
|
||||
effect = null,
|
||||
value = 1,
|
||||
max = 1,
|
||||
haveDetail = true,
|
||||
),
|
||||
SkillItemUio(
|
||||
label = "Apparence inspirante",
|
||||
translate = "Mantle of Inspiration",
|
||||
rest = null,
|
||||
cost = "Inspiration bardique",
|
||||
effect = SkillItemUio.Dice(icon = R.drawable.ic_d6_24, label = "2d6"),
|
||||
value = null,
|
||||
max = null,
|
||||
haveDetail = true,
|
||||
),
|
||||
SkillItemUio(
|
||||
label = "Renvoi des morts-vivants",
|
||||
translate = "Turn Undead",
|
||||
rest = "Récupération repos long",
|
||||
cost = "Conduit divin",
|
||||
effect = null,
|
||||
value = null,
|
||||
max = null,
|
||||
haveDetail = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,14 +2,13 @@ package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
|||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.background
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -21,13 +20,13 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
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.R
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.CounterButton
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
|
|
@ -46,77 +45,62 @@ data class SpellHeaderUio(
|
|||
@Composable
|
||||
fun SpellHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(top = 8.dp, bottom = 4.dp),
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
header: SpellHeaderUio,
|
||||
onSpell: (level: Int, value: Int, max: Int) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(color = MaterialTheme.colorScheme.surface)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable(enabled = header.count != null) {
|
||||
header.count?.let { onSpell(header.level, it.value, it.max) }
|
||||
}
|
||||
.padding(horizontal = 16.dp)
|
||||
.heightIn(min = 32.dp),
|
||||
.then(other = modifier),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues = padding)
|
||||
.align(alignment = Alignment.Center),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
Surface {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(
|
||||
id = when (header.level) {
|
||||
0 -> R.string.character_sheet_action_spell_level_0
|
||||
else -> R.string.character_sheet_action_spell_level_X
|
||||
},
|
||||
header.level
|
||||
).let { label ->
|
||||
AnnotatedString(
|
||||
text = label,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, label.length),
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues = padding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(
|
||||
id = when (header.level) {
|
||||
0 -> R.string.character_sheet_action_spell_level_0
|
||||
else -> R.string.character_sheet_action_spell_level_X
|
||||
},
|
||||
header.level
|
||||
).let { label ->
|
||||
AnnotatedString(
|
||||
text = label,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, label.length),
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
header.count?.let { count ->
|
||||
CounterButton(
|
||||
modifier = Modifier.alignByBaseline().offset(x = 4.dp),
|
||||
value = count.value,
|
||||
max = count.max,
|
||||
)
|
||||
},
|
||||
)
|
||||
header.count?.let { count ->
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = "${count.value}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
text = "/",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
text = "${count.max}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.align(alignment = Alignment.BottomCenter),
|
||||
modifier = Modifier.padding(paddingValues = padding),
|
||||
color = MaterialTheme.lexicon.colorScheme.placeholder,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.Spell
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.common.DiceButton
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.icon
|
||||
|
||||
|
|
@ -59,7 +60,7 @@ data class SpellUio(
|
|||
@Composable
|
||||
fun Spell(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
padding: PaddingValues = PaddingValues(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 8.dp),
|
||||
spell: SpellUio,
|
||||
onClick: (String) -> Unit,
|
||||
onHit: (String) -> Unit,
|
||||
|
|
@ -106,27 +107,25 @@ fun Spell(
|
|||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = spell.castingTime,
|
||||
)
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
text = "-",
|
||||
)
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = spell.duration,
|
||||
|
|
@ -134,27 +133,25 @@ fun Spell(
|
|||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
if (spell.ritual) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Medium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(id = R.string.spell_detail_ritual),
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
text = "-",
|
||||
)
|
||||
}
|
||||
Text(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Normal,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = spell.range,
|
||||
|
|
@ -162,33 +159,39 @@ fun Spell(
|
|||
}
|
||||
}
|
||||
|
||||
spell.hit?.let { dice ->
|
||||
DiceButton(
|
||||
modifier = Modifier.padding(end = 4.dp),
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { spell.name.let(onHit) }
|
||||
)
|
||||
}
|
||||
spell.effect?.let { dice ->
|
||||
if (spell.changeWithLevel) {
|
||||
OutlinedButton(
|
||||
border = BorderStroke(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
onClick = { onCast(spell.name) },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.character_sheet_action_spell_cast),
|
||||
if (spell.hit != null || spell.effect != null) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
spell.hit?.let { dice ->
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { spell.name.let(onHit) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { spell.name.let(onEffect) }
|
||||
)
|
||||
spell.effect?.let { dice ->
|
||||
if (spell.changeWithLevel) {
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
border = BorderStroke(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
onClick = { onCast(spell.name) },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.character_sheet_action_spell_cast),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
DiceButton(
|
||||
icon = dice.icon,
|
||||
text = dice.label,
|
||||
onClick = { spell.name.let(onEffect) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -203,7 +206,6 @@ private fun SpellPreview(
|
|||
LexiconTheme {
|
||||
Surface {
|
||||
Spell(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
spell = preview,
|
||||
onClick = { },
|
||||
onHit = { },
|
||||
|
|
|
|||
|
|
@ -63,11 +63,6 @@ fun CharacterSheetHeader(
|
|||
LabelPoint(label = it)
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.lexicon.colorScheme.placeholder,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.common
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Composable
|
||||
fun CounterButton(
|
||||
modifier: Modifier = Modifier,
|
||||
value: Int?,
|
||||
max: Int?,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.minimumInteractiveComponentSize()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
enabled = onClick != null,
|
||||
onClick = { onClick?.invoke() },
|
||||
)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = "${value ?: 0}",
|
||||
)
|
||||
max?.let {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "/"
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "$it",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun CounterPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
CounterButton(
|
||||
value = 1,
|
||||
max = 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.composable.actions
|
||||
package com.pixelized.rplexicon.ui.screens.character.composable.common
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
|
|
@ -6,7 +6,7 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.material.minimumInteractiveComponentSize
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -18,22 +18,19 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Composable
|
||||
fun DiceButton(
|
||||
modifier: Modifier = Modifier,
|
||||
minWidth: Dp = 42.dp,
|
||||
@DrawableRes icon: Int,
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.sizeIn(minWidth = minWidth)
|
||||
.minimumInteractiveComponentSize()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
|
|
@ -3,52 +3,51 @@ package com.pixelized.rplexicon.ui.screens.character.pages.actions
|
|||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ModalBottomSheetState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.LocalRollOverlay
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.edit.HandleCounterEditDialog
|
||||
import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog
|
||||
import com.pixelized.rplexicon.ui.composable.edit.HandleSkillEditDialog
|
||||
import com.pixelized.rplexicon.ui.composable.edit.HandleSpellEditDialog
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSpellDetail
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.Attack
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackHeader
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillHeader
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItem
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItemUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.Spell
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeader
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.rememberTokenListStatePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberAttackListStatePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellListStatePreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.composable.TokenItem
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.composable.TokenItemUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.composable.rememberTokenListStatePreview
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
|
|
@ -56,9 +55,9 @@ import kotlinx.coroutines.launch
|
|||
fun ActionPage(
|
||||
sheetState: ModalBottomSheetState,
|
||||
headerViewModel: HeaderViewModel = hiltViewModel(),
|
||||
attackViewModel: AttackActionViewModel = hiltViewModel(),
|
||||
spellsViewModel: SpellsActionViewModel = hiltViewModel(),
|
||||
tokenViewModel: TokenViewModel = hiltViewModel(),
|
||||
attacksViewModel: AttacksViewModel = hiltViewModel(),
|
||||
spellsViewModel: SpellsViewModel = hiltViewModel(),
|
||||
skillViewModel: SkillsViewModel = hiltViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenNavHost.current
|
||||
val overlay = LocalRollOverlay.current
|
||||
|
|
@ -67,27 +66,34 @@ fun ActionPage(
|
|||
ActionsPageContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
header = headerViewModel.header,
|
||||
attacks = attackViewModel.attacks,
|
||||
tokens = tokenViewModel.tokens,
|
||||
attacks = attacksViewModel.attacks,
|
||||
tokens = skillViewModel.skills,
|
||||
spells = spellsViewModel.spells,
|
||||
onHitPoint = headerViewModel::toggleHitPointDialog,
|
||||
onAttackHit = { id ->
|
||||
attackViewModel.onHitRoll(id)?.let {
|
||||
attacksViewModel.onHitRoll(id)?.let {
|
||||
overlay.prepareRoll(diceThrow = it)
|
||||
overlay.showOverlay()
|
||||
}
|
||||
},
|
||||
onAttackDamage = { id ->
|
||||
attackViewModel.onDamageRoll(id)?.let {
|
||||
attacksViewModel.onDamageRoll(id)?.let {
|
||||
overlay.prepareRoll(diceThrow = it)
|
||||
overlay.showOverlay()
|
||||
}
|
||||
},
|
||||
onToken = {
|
||||
tokenViewModel.showTokenEditDialog(dialog = it)
|
||||
onSkillCount = {
|
||||
skillViewModel.showSkillEditDialog(item = it)
|
||||
},
|
||||
onSkillThrow = {
|
||||
overlay.prepareRoll(diceThrow = skillViewModel.onSkillRoll(it.label))
|
||||
overlay.showOverlay()
|
||||
},
|
||||
onSkillInfo = {
|
||||
skillViewModel.showSkillDetailDialog(item = it)
|
||||
},
|
||||
onSpellLevel = { level: Int, value: Int, max: Int ->
|
||||
tokenViewModel.showSpellTokenEditDialog(level = level, value = value, max = max)
|
||||
spellsViewModel.showSpellEditDialog(level = level, value = value, max = max)
|
||||
},
|
||||
onSpell = { spell ->
|
||||
screen.navigateToSpellDetail(
|
||||
|
|
@ -120,10 +126,21 @@ fun ActionPage(
|
|||
onConfirm = headerViewModel::applyHitPointChange,
|
||||
)
|
||||
|
||||
HandleCounterEditDialog(
|
||||
dialog = tokenViewModel.dialog,
|
||||
onDismissRequest = tokenViewModel::hideCounterEditDialog,
|
||||
onConfirm = tokenViewModel::applyCounterValue
|
||||
HandleSpellEditDialog(
|
||||
dialog = spellsViewModel.spellEditDialog,
|
||||
onDismissRequest = spellsViewModel::hideSpellEditDialog,
|
||||
onConfirm = spellsViewModel::applySpellChange
|
||||
)
|
||||
|
||||
HandleSkillEditDialog(
|
||||
dialog = skillViewModel.skillEditDialog,
|
||||
onDismissRequest = skillViewModel::hideSkillEditDialog,
|
||||
onConfirm = skillViewModel::applySkillChange
|
||||
)
|
||||
|
||||
HandleSkillDetailDialog(
|
||||
dialog = skillViewModel.skillDetailDialog,
|
||||
onDismissRequest = skillViewModel::hideSkillDetailDialog
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -134,96 +151,104 @@ fun ActionsPageContent(
|
|||
lazyListState: LazyListState = rememberLazyListState(),
|
||||
header: State<CharacterSheetHeaderUio?>,
|
||||
attacks: State<List<AttackUio>>,
|
||||
tokens: State<List<TokenItemUio>>,
|
||||
tokens: State<List<SkillItemUio>>,
|
||||
spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>>,
|
||||
onHitPoint: () -> Unit,
|
||||
onAttackHit: (id: String) -> Unit,
|
||||
onAttackDamage: (id: String) -> Unit,
|
||||
onToken: (TokenItemUio) -> Unit,
|
||||
onSkillThrow: (SkillItemUio) -> Unit,
|
||||
onSkillCount: (SkillItemUio) -> Unit,
|
||||
onSkillInfo: (SkillItemUio) -> Unit,
|
||||
onSpellLevel: (level: Int, value: Int, max: Int) -> Unit,
|
||||
onSpell: (id: String) -> Unit,
|
||||
onSpellHit: (id: String) -> Unit,
|
||||
onSpellDamage: (id: String) -> Unit,
|
||||
onCast: (id: String) -> Unit,
|
||||
) {
|
||||
LazyColumn(
|
||||
Column(
|
||||
modifier = modifier,
|
||||
state = lazyListState,
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
) {
|
||||
stickyHeader {
|
||||
CharacterSheetHeader(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
header = header,
|
||||
onHitPoint = onHitPoint,
|
||||
CharacterSheetHeader(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
header = header,
|
||||
onHitPoint = onHitPoint,
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
) {
|
||||
if (attacks.value.isNotEmpty()) {
|
||||
stickyHeader {
|
||||
AttackHeader()
|
||||
}
|
||||
items(items = attacks.value) {
|
||||
Attack(
|
||||
weapon = it,
|
||||
onHit = onAttackHit,
|
||||
onDamage = onAttackDamage,
|
||||
)
|
||||
}
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens.value.isNotEmpty()) {
|
||||
stickyHeader {
|
||||
SkillHeader()
|
||||
}
|
||||
items(items = tokens.value) {
|
||||
SkillItem(
|
||||
skill = it,
|
||||
onInfo = onSkillInfo,
|
||||
onThrow = onSkillThrow,
|
||||
onSkill = onSkillCount,
|
||||
)
|
||||
}
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
spells.value.forEach { entry ->
|
||||
stickyHeader {
|
||||
SpellHeader(
|
||||
header = entry.first,
|
||||
onSpell = onSpellLevel,
|
||||
)
|
||||
}
|
||||
items(items = entry.second) {
|
||||
Spell(
|
||||
spell = it,
|
||||
onClick = onSpell,
|
||||
onHit = onSpellHit,
|
||||
onEffect = onSpellDamage,
|
||||
onCast = onCast,
|
||||
)
|
||||
}
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HandleSkillDetailDialog(
|
||||
dialog: State<SkillDetailUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
Dialog(
|
||||
properties = remember { DialogProperties(usePlatformDefaultWidth = false) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
SkillDetail(
|
||||
detail = it,
|
||||
onClose = onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
|
||||
items(items = attacks.value) {
|
||||
Attack(
|
||||
weapon = it,
|
||||
onHit = onAttackHit,
|
||||
onDamage = onAttackDamage,
|
||||
)
|
||||
}
|
||||
|
||||
if (tokens.value.isNotEmpty()) {
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
}
|
||||
stickyHeader {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
text = stringResource(id = R.string.token_label_title).let {
|
||||
AnnotatedString(
|
||||
text = it,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, it.length),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
items(items = tokens.value) {
|
||||
TokenItem(
|
||||
counter = it,
|
||||
onClick = onToken,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
spells.value.forEach { entry ->
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
stickyHeader {
|
||||
SpellHeader(
|
||||
header = entry.first,
|
||||
onSpell = onSpellLevel,
|
||||
)
|
||||
}
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
items(items = entry.second) {
|
||||
Spell(
|
||||
spell = it,
|
||||
onClick = onSpell,
|
||||
onHit = onSpellHit,
|
||||
onEffect = onSpellDamage,
|
||||
onCast = onCast,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +267,9 @@ fun ActionPagePreview() {
|
|||
onHitPoint = { },
|
||||
onAttackHit = { },
|
||||
onAttackDamage = { },
|
||||
onToken = { },
|
||||
onSkillCount = { },
|
||||
onSkillThrow = { },
|
||||
onSkillInfo = { },
|
||||
onSpellLevel = { _, _, _ -> },
|
||||
onSpell = { },
|
||||
onSpellHit = { },
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import kotlinx.coroutines.withContext
|
|||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AttackActionViewModel @Inject constructor(
|
||||
class AttacksViewModel @Inject constructor(
|
||||
application: Application,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.actions
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
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.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 SkillDetailUio(
|
||||
val name: String,
|
||||
val original: String?,
|
||||
val description: String,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun SkillDetail(
|
||||
modifier: Modifier = Modifier,
|
||||
detail: SkillDetailUio,
|
||||
onClose: () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.padding(all = 16.dp)
|
||||
.ddBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
.then(other = modifier),
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp)
|
||||
.weight(weight = 1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = AnnotatedString(
|
||||
text = detail.name,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = Integer.min(1, detail.name.length),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
detail.original?.let {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
fontStyle = FontStyle.Italic,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onClose) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 24.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = detail.description,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun AlterationDetailPreview() {
|
||||
LexiconTheme {
|
||||
SkillDetail(
|
||||
detail = SkillDetailUio(
|
||||
name = "Endurance implacable",
|
||||
original = "Relentless Endurance",
|
||||
description = "Lorsque vous tombez à 0 point de vie, mais que vous n'êtes pas tué sur le coup, vous pouvez passer à 1 point de vie à la place. Vous devez terminer un repos long avant de pouvoir utiliser cette capacité de nouveau."
|
||||
),
|
||||
onClose = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.actions
|
||||
|
||||
import android.app.Application
|
||||
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.model.DiceThrow
|
||||
import com.pixelized.rplexicon.repository.authentication.FirebaseRepository
|
||||
import com.pixelized.rplexicon.repository.data.DescriptionRepository
|
||||
import com.pixelized.rplexicon.repository.data.SkillRepository
|
||||
import com.pixelized.rplexicon.ui.composable.edit.SkillEditDialogUio
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItemUio
|
||||
import com.pixelized.rplexicon.utilitary.extentions.icon
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SkillsViewModel @Inject constructor(
|
||||
private val skillRepository: SkillRepository,
|
||||
private val firebaseRepository: FirebaseRepository,
|
||||
private val descriptionRepository: DescriptionRepository,
|
||||
application: Application,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : AndroidViewModel(application) {
|
||||
private val character = savedStateHandle.characterSheetArgument.name
|
||||
|
||||
private val _skillEditDialog = mutableStateOf<SkillEditDialogUio?>(null)
|
||||
val skillEditDialog: State<SkillEditDialogUio?> get() = _skillEditDialog
|
||||
|
||||
private val _skillDetailDialog = mutableStateOf<SkillDetailUio?>(null)
|
||||
val skillDetailDialog: State<SkillDetailUio?> get() = _skillDetailDialog
|
||||
|
||||
private val _skills = mutableStateOf<List<SkillItemUio>>(emptyList())
|
||||
val skills: State<List<SkillItemUio>> get() = _skills
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
skillRepository.skills
|
||||
.combine(firebaseRepository.getCharacter(character = character)) { sheets, fire ->
|
||||
sheets[character] to fire
|
||||
}
|
||||
.collect { data ->
|
||||
val (values, fire) = data
|
||||
|
||||
val skills = values?.map { skill ->
|
||||
val description = descriptionRepository.find(name = skill.name)
|
||||
SkillItemUio(
|
||||
label = skill.name,
|
||||
translate = description?.original,
|
||||
rest = skill.rest,
|
||||
cost = skill.cost,
|
||||
effect = skill.effect?.let {
|
||||
SkillItemUio.Dice(
|
||||
icon = it.faces.icon,
|
||||
label = "${it.amount}d${it.faces}",
|
||||
)
|
||||
},
|
||||
value = fire.skills[skill.name],
|
||||
max = skill.amount,
|
||||
haveDetail = description?.description != null,
|
||||
)
|
||||
} ?: emptyList()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
_skills.value = skills
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onSkillRoll(name: String): DiceThrow = DiceThrow.Skill(
|
||||
character = character,
|
||||
skill = name,
|
||||
)
|
||||
|
||||
fun showSkillDetailDialog(item: SkillItemUio) {
|
||||
_skillDetailDialog.value = descriptionRepository
|
||||
.find(name = item.label)
|
||||
?.let { description ->
|
||||
SkillDetailUio(
|
||||
name = item.label,
|
||||
original = description.original,
|
||||
description = description.description
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hideSkillDetailDialog() {
|
||||
_skillDetailDialog.value = null
|
||||
}
|
||||
|
||||
fun showSkillEditDialog(item: SkillItemUio) {
|
||||
if (item.max != null) {
|
||||
_skillEditDialog.value = SkillEditDialogUio(
|
||||
label = item.label,
|
||||
value = item.value ?: 0,
|
||||
max = item.max,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hideSkillEditDialog() {
|
||||
_skillEditDialog.value = null
|
||||
}
|
||||
|
||||
fun applySkillChange(id: String, value: Int) {
|
||||
firebaseRepository.setSkill(character = character, name = id, value = value)
|
||||
hideSkillEditDialog()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import com.pixelized.rplexicon.model.Throw
|
|||
import com.pixelized.rplexicon.repository.authentication.FirebaseRepository
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.repository.data.SpellRepository
|
||||
import com.pixelized.rplexicon.ui.composable.edit.SpellEditDialogUio
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLevelUio
|
||||
|
|
@ -36,7 +37,7 @@ import javax.inject.Inject
|
|||
import kotlin.math.max
|
||||
|
||||
@HiltViewModel
|
||||
class SpellsActionViewModel @Inject constructor(
|
||||
class SpellsViewModel @Inject constructor(
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val firebaseRepository: FirebaseRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
|
|
@ -48,6 +49,9 @@ class SpellsActionViewModel @Inject constructor(
|
|||
private var character: CharacterSheet? = null
|
||||
private var characterFire: CharacterSheetFire? = null
|
||||
|
||||
private val _editDialog = mutableStateOf<SpellEditDialogUio?>(null)
|
||||
val spellEditDialog: State<SpellEditDialogUio?> get() = _editDialog
|
||||
|
||||
private val _spells = mutableStateOf<List<Pair<SpellHeaderUio, List<SpellUio>>>>(emptyList())
|
||||
val spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>> get() = _spells
|
||||
|
||||
|
|
@ -138,7 +142,7 @@ class SpellsActionViewModel @Inject constructor(
|
|||
character = characterName,
|
||||
spell = name,
|
||||
)
|
||||
return when (character?.isWarlock?: false) {
|
||||
return when (character?.isWarlock ?: false) {
|
||||
true -> false
|
||||
else -> (character?.highestSpellLevel() ?: 1) > (assignedSpell?.spell?.level ?: 1)
|
||||
}
|
||||
|
|
@ -202,6 +206,23 @@ class SpellsActionViewModel @Inject constructor(
|
|||
return DiceThrow.SpellDamage(character = characterName, spell = id)
|
||||
}
|
||||
|
||||
fun showSpellEditDialog(level: Int, value: Int, max: Int) {
|
||||
_editDialog.value = SpellEditDialogUio(
|
||||
level = level,
|
||||
value = value,
|
||||
max = max,
|
||||
)
|
||||
}
|
||||
|
||||
fun hideSpellEditDialog() {
|
||||
_editDialog.value = null
|
||||
}
|
||||
|
||||
fun applySpellChange(level: Int, value: Int) {
|
||||
firebaseRepository.setSpell(character = characterName, level = level, value = value)
|
||||
hideSpellEditDialog()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to build a readable String from a Throw.
|
||||
* Create a string following the format "amount 'd' faces '+' modifiers + amount * level 'd' faces"
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.actions
|
||||
|
||||
import android.app.Application
|
||||
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.model.CharacterSheetFire
|
||||
import com.pixelized.rplexicon.repository.authentication.FirebaseRepository
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.ui.composable.edit.CounterEditDialogUio
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.composable.TokenItemUio
|
||||
import com.pixelized.rplexicon.utilitary.extentions.context
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TokenViewModel @Inject constructor(
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val firebaseRepository: FirebaseRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
application: Application,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : AndroidViewModel(application) {
|
||||
private val character = savedStateHandle.characterSheetArgument.name
|
||||
|
||||
private val _dialog = mutableStateOf<CounterEditDialogUio?>(null)
|
||||
val dialog: State<CounterEditDialogUio?> get() = _dialog
|
||||
|
||||
private val _counters = mutableStateOf<List<TokenItemUio>>(emptyList())
|
||||
val tokens: State<List<TokenItemUio>> get() = _counters
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
characterRepository.data
|
||||
.combine(firebaseRepository.getCharacter(character = character)) { sheets, fire ->
|
||||
sheets.getValue(character) to fire
|
||||
}
|
||||
.collect { data ->
|
||||
val (character, fire) = data
|
||||
val counters = mutableListOf<TokenItemUio>()
|
||||
character.rage?.let {
|
||||
counters.add(
|
||||
TokenItemUio(
|
||||
id = CharacterSheetFire.RAGE,
|
||||
icon = R.drawable.ic_fist_24,
|
||||
label = R.string.token_label_rage,
|
||||
value = fire.rage ?: 0,
|
||||
max = it,
|
||||
)
|
||||
)
|
||||
}
|
||||
character.relentlessEndurance?.let {
|
||||
counters.add(
|
||||
TokenItemUio(
|
||||
id = CharacterSheetFire.RELENTLESS_ENDURANCE,
|
||||
icon = R.drawable.ic_burning_passion_24,
|
||||
label = R.string.token_label_relentless_endurance,
|
||||
value = fire.relentlessEndurance ?: 0,
|
||||
max = it,
|
||||
)
|
||||
)
|
||||
}
|
||||
character.bardicInspiration?.let {
|
||||
counters.add(
|
||||
TokenItemUio(
|
||||
id = CharacterSheetFire.BARDIC_INSPIRATION,
|
||||
icon = R.drawable.ic_lyre_24,
|
||||
label = R.string.token_label_bardic_inspiration,
|
||||
value = fire.bardicInspiration ?: 0,
|
||||
max = it,
|
||||
)
|
||||
)
|
||||
}
|
||||
character.divineConduit?.let {
|
||||
counters.add(
|
||||
TokenItemUio(
|
||||
id = CharacterSheetFire.DIVINE_CONDUIT,
|
||||
icon = R.drawable.ic_embrassed_energy_24,
|
||||
label = R.string.token_label_divine_conduit,
|
||||
value = fire.divineConduit ?: 0,
|
||||
max = it,
|
||||
)
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
_counters.value = counters
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showTokenEditDialog(dialog: TokenItemUio) {
|
||||
_dialog.value = CounterEditDialogUio(
|
||||
id = dialog.id,
|
||||
label = context.getString(dialog.label),
|
||||
value = dialog.value,
|
||||
max = dialog.max,
|
||||
)
|
||||
}
|
||||
|
||||
fun showSpellTokenEditDialog(level: Int, value: Int, max: Int) {
|
||||
_dialog.value = CounterEditDialogUio(
|
||||
id = CharacterSheetFire.SPELL_SLOT_LEVEL_X + level,
|
||||
label = context.getString(R.string.spell_level_chooser_label, "$level"),
|
||||
value = value,
|
||||
max = max,
|
||||
)
|
||||
}
|
||||
|
||||
fun hideCounterEditDialog() {
|
||||
_dialog.value = null
|
||||
}
|
||||
|
||||
fun applyCounterValue(id: String, value: Int) {
|
||||
firebaseRepository.setToken(character = character, token = id, value = value)
|
||||
_dialog.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.rplexicon.ui.screens.rolls.composable
|
||||
package com.pixelized.rplexicon.ui.screens.character.pages.alteration
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
|
|
@ -17,10 +17,8 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetail
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlteration
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItem
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.preview.rememberRollAlterations
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -53,7 +51,7 @@ fun AlterationPage(
|
|||
@Composable
|
||||
fun AlterationPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
alterations: State<List<RollAlterationUio>>,
|
||||
alterations: State<List<AlterationItemUio>>,
|
||||
onAlterationInfo: (String) -> Unit,
|
||||
onAlterationClick: (String) -> Unit,
|
||||
) {
|
||||
|
|
@ -62,7 +60,7 @@ fun AlterationPageContent(
|
|||
contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp),
|
||||
) {
|
||||
items(items = alterations.value) {
|
||||
RollAlteration(
|
||||
AlterationItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
alteration = it,
|
||||
onInfo = onAlterationInfo,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ import com.pixelized.rplexicon.R
|
|||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.DescriptionRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.factory.AlterationFactory
|
||||
import com.pixelized.rplexicon.utilitary.extentions.context
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -30,8 +29,8 @@ class AlterationViewModel @Inject constructor(
|
|||
) : AndroidViewModel(application) {
|
||||
private val character = savedStateHandle.characterSheetArgument.name
|
||||
|
||||
private val _alterations = mutableStateOf<List<RollAlterationUio>>(emptyList())
|
||||
val alterations: State<List<RollAlterationUio>> get() = _alterations
|
||||
private val _alterations = mutableStateOf<List<AlterationItemUio>>(emptyList())
|
||||
val alterations: State<List<AlterationItemUio>> get() = _alterations
|
||||
|
||||
private val _alterationDetail = mutableStateOf<AlterationDetailUio?>(null)
|
||||
val alterationDetail: State<AlterationDetailUio?> get() = _alterationDetail
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.alteration.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.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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
data class TokenItemUio(
|
||||
val id: String,
|
||||
@DrawableRes val icon: Int,
|
||||
@StringRes val label: Int,
|
||||
val value: Int,
|
||||
val max: Int,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun TokenItem(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
counter: TokenItemUio,
|
||||
onClick: (TokenItemUio) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable { onClick(counter) }
|
||||
.heightIn(min = 52.dp)
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = counter.icon),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.alignByBaseline()
|
||||
.weight(weight = 1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = stringResource(id = counter.label),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = "${counter.value}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "/"
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Light,
|
||||
text = "${counter.max}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberTokenListStatePreview(): State<List<TokenItemUio>> = remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
TokenItemUio(
|
||||
id = "1",
|
||||
icon = R.drawable.ic_fist_24,
|
||||
label = R.string.token_label_rage,
|
||||
value = 2,
|
||||
max = 2,
|
||||
),
|
||||
TokenItemUio(
|
||||
id = "2",
|
||||
icon = R.drawable.ic_embrassed_energy_24,
|
||||
label = R.string.token_label_divine_conduit,
|
||||
value = 2,
|
||||
max = 4,
|
||||
),
|
||||
TokenItemUio(
|
||||
id = "3",
|
||||
icon = R.drawable.ic_lyre_24,
|
||||
label = R.string.token_label_bardic_inspiration,
|
||||
value = 2,
|
||||
max = 3,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun CounterItemPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
TokenItem(
|
||||
counter = TokenItemUio(
|
||||
id = "0",
|
||||
icon = R.drawable.ic_burning_passion_24,
|
||||
label = R.string.token_label_relentless_endurance,
|
||||
value = 2,
|
||||
max = 2,
|
||||
),
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,10 +67,10 @@ import com.pixelized.rplexicon.R
|
|||
import com.pixelized.rplexicon.model.DiceThrow
|
||||
import com.pixelized.rplexicon.ui.composable.BlurredOverlayHostState
|
||||
import com.pixelized.rplexicon.ui.composable.ModalNavigationDrawer
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetail
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlteration
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationDetail
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItem
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
|
||||
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.ThrowsCard
|
||||
|
|
@ -136,7 +136,7 @@ private fun RollOverlayContent(
|
|||
drawer: DrawerState,
|
||||
dice: State<RollDiceUio?>,
|
||||
card: State<ThrowsCardUio?>,
|
||||
alterations: State<List<RollAlterationUio>>,
|
||||
alterations: State<List<AlterationItemUio>>,
|
||||
showDetail: State<Boolean>,
|
||||
onMenu: () -> Unit,
|
||||
onMenuClose: () -> Unit,
|
||||
|
|
@ -185,7 +185,7 @@ private fun RollOverlayContent(
|
|||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
) {
|
||||
items(items = alterations.value) {
|
||||
RollAlteration(
|
||||
AlterationItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
alteration = it,
|
||||
onInfo = onAlterationInfo,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import com.pixelized.rplexicon.business.DiceThrowUseCase
|
|||
import com.pixelized.rplexicon.model.DiceThrow
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.DescriptionRepository
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
|
||||
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.factory.AlterationFactory
|
||||
|
|
@ -36,8 +36,8 @@ class RollOverlayViewModel @Inject constructor(
|
|||
private var diceThrow: DiceThrow? = null
|
||||
private var rollJob: Job? = null
|
||||
|
||||
private val _alterations = mutableStateOf<List<RollAlterationUio>>(emptyList())
|
||||
val alterations: State<List<RollAlterationUio>> get() = _alterations
|
||||
private val _alterations = mutableStateOf<List<AlterationItemUio>>(emptyList())
|
||||
val alterations: State<List<AlterationItemUio>> get() = _alterations
|
||||
|
||||
private val _dice = mutableStateOf<RollDiceUio?>(null)
|
||||
val dice: State<RollDiceUio?> get() = _dice
|
||||
|
|
|
|||
|
|
@ -5,18 +5,20 @@ import com.pixelized.rplexicon.model.DiceThrow
|
|||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.repository.data.ActionRepository
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.SkillRepository
|
||||
import com.pixelized.rplexicon.repository.data.SpellRepository
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
|
||||
import javax.inject.Inject
|
||||
|
||||
class AlterationFactory @Inject constructor(
|
||||
private val actionRepository: ActionRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
private val skillRepository: SkillRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
) {
|
||||
fun convert(character: String, alterations: List<Alteration>): List<RollAlterationUio> {
|
||||
fun convert(character: String, alterations: List<Alteration>): List<AlterationItemUio> {
|
||||
return alterations.map {
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = it.name,
|
||||
checked = alterationRepository.getStatus(character, it.name),
|
||||
subLabel = it.source,
|
||||
|
|
@ -24,7 +26,7 @@ class AlterationFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun convertDiceThrow(diceThrow: DiceThrow): List<RollAlterationUio> {
|
||||
fun convertDiceThrow(diceThrow: DiceThrow): List<AlterationItemUio> {
|
||||
val properties = when (diceThrow) {
|
||||
is DiceThrow.Initiative -> listOf(Property.INITIATIVE, Property.DEXTERITY)
|
||||
is DiceThrow.Strength -> listOf(Property.STRENGTH, Property.STRENGTH_THROW)
|
||||
|
|
@ -91,12 +93,17 @@ class AlterationFactory @Inject constructor(
|
|||
val spell = spellRepository.find(diceThrow.character, spell = diceThrow.spell)
|
||||
spell?.effect?.modifier ?: emptyList()
|
||||
}
|
||||
|
||||
is DiceThrow.Skill -> {
|
||||
val skill = skillRepository.find(diceThrow.character, skill = diceThrow.skill)
|
||||
skill?.effect?.modifier ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
return alterationRepository
|
||||
.getAlterations(character = diceThrow.character, *properties.toTypedArray())
|
||||
.map {
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = it.name,
|
||||
checked = alterationRepository.getStatus(diceThrow.character, it.name),
|
||||
subLabel = it.source,
|
||||
|
|
|
|||
|
|
@ -4,29 +4,29 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
fun rememberRollAlterations() = remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = "Rage",
|
||||
subLabel = "Barbare",
|
||||
checked = false,
|
||||
),
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = "Inspiration bardique",
|
||||
subLabel = "Barde",
|
||||
checked = false
|
||||
),
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = "Bénédiction",
|
||||
subLabel = "Clerc",
|
||||
checked = false,
|
||||
),
|
||||
RollAlterationUio(
|
||||
AlterationItemUio(
|
||||
label = "Cape de protection",
|
||||
subLabel = "Équipement",
|
||||
checked = true
|
||||
|
|
|
|||
|
|
@ -16,18 +16,7 @@ fun CharacterSheet.spell(level: Int): Int? = when (level) {
|
|||
else -> null
|
||||
}
|
||||
|
||||
fun CharacterSheetFire.spell(level: Int): Int? = when (level) {
|
||||
1 -> spell1
|
||||
2 -> spell2
|
||||
3 -> spell3
|
||||
4 -> spell4
|
||||
5 -> spell5
|
||||
6 -> spell6
|
||||
7 -> spell7
|
||||
8 -> spell8
|
||||
9 -> spell9
|
||||
else -> null
|
||||
}
|
||||
fun CharacterSheetFire.spell(level: Int): Int? = spells["${CharacterSheetFire.SPELL_PREFIX}$level"]
|
||||
|
||||
fun CharacterSheet.highestSpellLevel(): Int = when {
|
||||
spell9 != null -> 9
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19.84,18.75v2.72c64.27,50.96 95.31,115.85 89.44,179.25 -10.6,-55 -41.76,-104.8 -89.44,-138.91v23.53c55.24,45.18 82.41,114.31 72.97,185.25 -0.4,2.33 -0.76,4.66 -1.06,7 -0.09,0.51 -0.16,1.02 -0.25,1.53h0.06c-6.55,53.8 11.2,108.57 49.59,156.03 6.41,11.07 13.98,21.8 22.69,32.13C95.25,406.66 59.08,335.53 53.22,262.41c-11.11,83 15.11,163.21 90.69,230.22L188.5,492.63c0.03,0.03 0.06,0.06 0.09,0.09h130.47c0.03,-0.03 0.06,-0.06 0.09,-0.09h43.63c75.58,-67.01 101.8,-147.22 90.69,-230.22 -5.75,71.77 -40.7,141.62 -106.85,201.5 9.56,-11.75 17.68,-24.02 24.28,-36.69 34.1,-45.58 49.6,-97.28 43.41,-148.1 -0,-0.02 0,-0.04 0,-0.06 -0.41,-3.31 -0.91,-6.61 -1.5,-9.91 -9.36,-74.25 21.31,-146.35 82.31,-190.88L495.13,55.63c-52.75,34.07 -87.21,86.5 -98.53,144.84 -5.85,-64.21 26.1,-129.92 92.12,-181.13l-0.47,-0.59h-28.06c-72.05,64.34 -99.85,149.67 -72.5,228.06 2.89,8.29 5.11,16.68 6.66,25.09 0,0.04 0.03,0.08 0.03,0.13 0.44,3.43 0.93,6.88 1.53,10.31h0.03c2.3,19.37 1.12,38.89 -3.4,58.16 -0.04,-28.38 -6.78,-57.15 -20.44,-85.06 -40.06,-81.86 -20.77,-171.43 52.41,-236.69h-31.03c-50.15,46.62 -66.32,91.56 -57.44,151.09 -21.49,-59.17 -19.42,-103.58 20.69,-151.09L152,18.75c40.1,47.51 42.18,91.93 20.69,151.09 8.89,-59.53 -7.27,-104.47 -57.41,-151.09L83.16,18.75c73.17,65.26 92.46,154.82 52.41,236.69 -14.9,30.45 -21.52,61.92 -20.25,92.78 -6.15,-21.75 -8.02,-43.91 -5.41,-65.87 0.6,-3.44 1.12,-6.88 1.56,-10.31 1.55,-8.46 3.78,-16.88 6.69,-25.22 27.35,-78.39 -0.45,-163.72 -72.5,-228.06L19.84,18.75zM254.09,38.44c16.4,0 27.02,6.18 34.72,16.59 7.69,10.41 11.97,25.73 11.97,43 0,18.66 -6.89,38.56 -15.97,49.5l-10.13,12.22 15.59,2.94c12.52,2.35 21.72,8.77 29.44,19 7.72,10.23 13.57,24.36 17.69,40.69 7.52,29.84 9.14,66.52 9.38,99.34h-23.31l-0.81,-70.5 -18.69,0.22 0.97,86.44 -7.75,111.63c47.06,-43.67 71.99,-94.3 76.16,-146.31 8.21,61.34 -11.15,120.61 -67,170.13L295.53,473.31v0.22h-32.94L262.59,333.81h-18.69v139.72L212.62,473.53v-0.22h-9.65c-55.85,-49.52 -75.24,-108.79 -67.03,-170.13 4.13,51.56 28.66,101.78 74.94,145.19L203.78,345.72l2.6,-94.13 -18.69,-0.53 -1.94,70.65h-24.38c0.24,-32.83 1.88,-69.5 9.41,-99.35 4.12,-16.33 9.97,-30.46 17.69,-40.69 7.72,-10.23 16.92,-16.65 29.44,-19l15.59,-2.94 -10.13,-12.22c-9.08,-10.94 -15.97,-30.83 -15.97,-49.5 0,-17.27 4.28,-32.59 11.97,-43 7.69,-10.41 18.32,-16.59 34.72,-16.59z" />
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M179.81,20.72v81.25L135.78,75.62l17.56,46.94 -115.66,-20.94 84.72,49.91H20v27.34l110.47,14.88 96.59,-29.19c-11.3,-11.87 -18.59,-30.74 -18.59,-52 0,-35.93 20.87,-65.06 46.62,-65.06 25.75,0 46.63,29.14 46.63,65.06 0,20.85 -7.04,39.38 -17.97,51.28l99.03,29.91 112.5,-15.16V151.53H394.19l84.72,-49.9 -120.44,21.78 17.87,-47.72 -48.66,29.13V20.72H179.81zM495.28,223.34l-112.5,22.44 -55.4,-13.12 -28.03,118.31 16.59,145h51.69L329.25,351.22l46.53,27.84 -21.31,-56.94 124.44,22.5 -91.13,-53.69h107.5v-67.59zM20,223.75v67.19h108.81l-91.13,53.69L157.31,322.97 136.35,379l38.47,-23 -28.59,139.97h48.15L207.28,351.56 185.6,232.72l-55.13,13.06L20,223.75z" />
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M227.23,21.78c-1.85,0 -3.7,0.05 -5.57,0.16 -15.31,0.88 -30.76,5.3 -39.49,10.86l-0.01,73.15c2.88,-0.09 5.78,-0.15 8.68,-0.14 23.38,0.04 47.1,3.29 68.47,9.51l0.01,-87.51c-7.03,-3.52 -19.18,-6.03 -32.09,-6.03zM307.97,30.94c-11.93,0.15 -23.08,2.36 -29.97,5.6l-0.01,77.6v7.66c38.49,15.67 64.81,42.48 58.74,78.76l-0.96,5.73 -5.56,1.67c-17.45,5.25 -34.87,9.7 -52.22,13.34L277.98,246.53c25.56,-0.7 51.33,-2.69 77.14,-6.1l0.02,-197.93c-8.28,-5.56 -23.51,-10.24 -38.84,-11.33 -2.79,-0.2 -5.58,-0.27 -8.34,-0.24zM143.22,46.29c-1.18,-0.01 -2.37,-0.01 -3.59,0.02 -4.18,0.1 -8.53,0.47 -12.9,1.15 -15.67,2.45 -31.48,8.56 -40.41,15.4l-0.01,72.96c18.81,-15.81 46.7,-25.14 77.15,-28.54l0.01,-57.97c-4.82,-1.75 -12.02,-2.92 -20.25,-3.02zM401.62,49.75c-10.8,0.12 -20.72,1.93 -27.04,4.66l-0.02,183.18c25.07,-4.02 50.16,-9.41 75.12,-16.36l1.99,-158.45c-8.35,-5.9 -23.65,-11.02 -39.05,-12.55 -3.7,-0.37 -7.4,-0.52 -11,-0.48zM178.84,123.96c-53.72,0.7 -101.41,20.36 -97.89,66.6 15.84,-3.92 30.84,-5.89 44.94,-6.1 34.84,-0.51 64.21,9.7 87.32,27.61 34.61,-3.11 69.85,-10 105.41,-20.31 0.14,-41.29 -74.1,-68.66 -139.78,-67.8zM129.97,202.61c-1.3,-0 -2.6,0.01 -3.92,0.05 -17.26,0.44 -36.45,4.03 -57.57,11.04 5.79,53.81 26.33,106.41 58.5,143.35 6.23,7.15 12.86,13.71 19.88,19.61 29.3,9.28 69.26,12.92 110.53,12.14 3.78,-55.81 -8.72,-108.36 -36.19,-142.74 -21.26,-26.61 -51.06,-43.39 -91.23,-43.44zM259.29,224.89c-9.36,1.64 -18.69,3.02 -28,4.15 1.54,1.74 3.04,3.52 4.5,5.35 3.15,3.94 6.09,8.06 8.87,12.33 9.92,0.14 19.87,0.13 29.86,-0.11L259.29,246.61v-21.72zM451.11,240.23c-65.41,17.83 -131.46,25.41 -195.85,25.32 17,35.14 23.83,78.09 21.01,122.6 42.48,-2.08 85.03,-8.23 118.19,-15.98 26.69,-32.78 47.37,-77.12 56.65,-131.93zM400.51,389.9c-38.33,9.15 -87.95,16.06 -136.87,17.45 -47.67,1.36 -94.34,-2.23 -129.45,-15.26l-0.01,78.93c27.19,12.57 76.41,20.2 127.32,20.3 51.22,0.09 104.21,-7.17 139,-20.77l0.01,-80.65z" />
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M108.66,35.06c-15.05,0.14 -33.41,5.38 -46.97,15.81 -10.75,8.28 -18.78,19.27 -21.19,34.44 0.21,-0.13 0.41,-0.25 0.63,-0.38 -0.84,2.82 -1.31,5.79 -1.31,8.88 0,17.09 13.85,30.94 30.94,30.94 14.29,0 26.32,-9.7 29.88,-22.88 0.03,-0.12 0.07,-0.23 0.09,-0.34 0.48,-2.08 0.77,-4.04 0.9,-5.84 0.03,-0.33 0.02,-0.65 0.03,-0.97 0,-0.13 0.03,-0.25 0.03,-0.38 0.01,-0.18 -0,-0.35 0,-0.53 0,-1.53 -0.1,-3.04 -0.31,-4.5 -1.37,-8.02 -6.78,-12.16 -12.59,-13.72 -8.53,-2.29 -19.06,0.64 -23.75,18.16l-0.47,-0.13C62.04,74.12 72.21,63.88 83.94,60.78c2.48,-0.65 5.05,-1 7.66,-0.97 9.07,0.13 18.44,4.88 24.56,17.13 0.09,0.17 0.17,0.35 0.25,0.53 5.21,15.23 2.11,43.32 -3.34,57.63 -7.29,18.75 -22.38,40.5 -47.69,65.5C6.99,258.25 4,329.82 39.97,388.81 75.94,447.8 152.13,493.56 254.44,493.56c102.31,0 178.47,-45.76 214.44,-104.75 35.88,-58.85 32.98,-130.23 -25,-187.81l-0.41,-0.41h-0.03c-25.31,-25 -40.37,-46.75 -47.66,-65.5 -5.23,-16.45 -9.09,-42.99 -2.65,-57.63 0.06,-0.13 0.13,-0.25 0.19,-0.38 0.03,-0.05 0.04,-0.11 0.06,-0.16 6.12,-12.25 15.49,-17 24.56,-17.13 2.6,-0.04 5.18,0.31 7.66,0.97 11.72,3.1 21.87,13.34 19.34,32.84l-0.44,0.13c-4.69,-17.52 -15.22,-20.45 -23.75,-18.16 -4.41,1.18 -8.6,3.85 -10.97,8.56 -0.01,0.04 -0.02,0.09 -0.03,0.13 -0.98,3.01 -1.5,6.2 -1.5,9.53 0,17.09 13.85,30.94 30.94,30.94 17.09,0 30.94,-13.85 30.94,-30.94 0,-4.36 -0.91,-8.49 -2.53,-12.25 -3.06,-13.24 -10.6,-23.11 -20.44,-30.69 -14.46,-11.13 -34.39,-16.36 -49.94,-15.78 -13.38,0.5 -24.85,4.11 -33.22,10.53 -3.41,2.62 -6.38,5.7 -8.84,9.38 -69.46,35.51 -138.89,38.75 -208.34,-7.75 -0.64,-0.56 -1.29,-1.11 -1.97,-1.63 -8.37,-6.42 -19.84,-10.03 -33.22,-10.53 -0.97,-0.04 -1.96,-0.04 -2.97,-0.03zM161.44,88.19c6.34,2.65 12.67,4.99 19,7.03v313.06c-30.73,-8.26 -57.89,-22 -77.37,-41.31 -17.1,-16.94 -28.08,-38.63 -28.91,-63.6 -0.83,-24.97 8.27,-52.7 28.97,-82.63 41.32,-59.75 57.16,-103.6 58.31,-132.56zM347.47,89.59c1.6,28.97 17.59,72.37 58.25,131.16 20.69,29.92 29.8,57.66 28.97,82.63 -0.83,24.97 -11.81,46.65 -28.91,63.59 -19.02,18.85 -45.37,32.4 -75.22,40.72L330.56,94.84c5.64,-1.61 11.27,-3.35 16.91,-5.25zM311.87,99.59v312.53c-8.21,1.63 -16.61,2.88 -25.13,3.78L286.75,104.09c8.38,-1.12 16.75,-2.63 25.13,-4.5zM199.12,100.37c8.45,1.97 16.89,3.44 25.34,4.44v311.34c-8.59,-0.84 -17.06,-2.05 -25.34,-3.63L199.12,100.38zM268.06,105.94v311.38c-4.54,0.2 -9.09,0.31 -13.66,0.31 -3.76,0 -7.51,-0.05 -11.25,-0.19L243.16,106.28c8.29,0.31 16.61,0.18 24.91,-0.34z" />
|
||||
</vector>
|
||||
|
|
@ -116,8 +116,10 @@
|
|||
<string name="character_sheet_proficiency_stealth">Discrétion</string>
|
||||
<string name="character_sheet_proficiency_survival">Survie</string>
|
||||
<string name="character_sheet_action_spell_cast">Lancer</string>
|
||||
<string name="character_sheet_action_spell_level_0">Cantrip</string>
|
||||
<string name="character_sheet_action_spell_level_X">Sort de niveau %1$s</string>
|
||||
<string name="character_sheet_action_spell_level_0">Sorts mineurs</string>
|
||||
<string name="character_sheet_action_spell_level_X">Sorts de niveau %1$s</string>
|
||||
<string name="character_sheet_skill_title">Capacités</string>
|
||||
<string name="character_sheet_attack_title">Attaques</string>
|
||||
|
||||
<string name="dice_roll_mastery_proficiency">Maîtrise \"%1$s\" </string>
|
||||
<string name="dice_roll_mastery_expertise">Expertise \"%1$s\" </string>
|
||||
|
|
@ -156,10 +158,4 @@
|
|||
<string name="alteration_target">Cible : %1$s</string>
|
||||
|
||||
<string name="no_available_description">Aucune description disponnible</string>
|
||||
|
||||
<string name="token_label_title">Capacité</string>
|
||||
<string name="token_label_rage">Rage</string>
|
||||
<string name="token_label_relentless_endurance">Endurance Implacable</string>
|
||||
<string name="token_label_bardic_inspiration">Inspiration Bardique</string>
|
||||
<string name="token_label_divine_conduit">Conduit Divin</string>
|
||||
</resources>
|
||||
|
|
@ -118,6 +118,8 @@
|
|||
<string name="character_sheet_action_spell_cast">Cast</string>
|
||||
<string name="character_sheet_action_spell_level_0">Cantrip</string>
|
||||
<string name="character_sheet_action_spell_level_X">Spell level %1$s</string>
|
||||
<string name="character_sheet_skill_title">Skills</string>
|
||||
<string name="character_sheet_attack_title">Attacks</string>
|
||||
|
||||
<string name="dice_roll_mastery_proficiency">%1$s proficiency</string>
|
||||
<string name="dice_roll_mastery_expertise">%1$s expertise</string>
|
||||
|
|
@ -156,10 +158,4 @@
|
|||
<string name="alteration_target">Target: %1$s</string>
|
||||
|
||||
<string name="no_available_description">No available description</string>
|
||||
|
||||
<string name="token_label_title">Skill</string>
|
||||
<string name="token_label_rage">Rage</string>
|
||||
<string name="token_label_relentless_endurance">Relentless Endurance</string>
|
||||
<string name="token_label_bardic_inspiration">Bardic Inspiration</string>
|
||||
<string name="token_label_divine_conduit">Divine Conduit</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue