Add realtime database to the APP.
This commit is contained in:
parent
e2209bf005
commit
ee81f9082d
50 changed files with 1478 additions and 342 deletions
4
app/src/debug/res/drawable/ic_half_heart_24.xml
Normal file
4
app/src/debug/res/drawable/ic_half_heart_24.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="24dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000" android:pathData="M367.84,23.56c-1.25,-0.01 -2.5,0 -3.75,0.03 -39.56,1.01 -80.22,21.15 -108.66,61.94 -35.39,-50.06 -91.96,-68.93 -140.94,-58.9C61.6,37.46 17.57,83.13 19.84,154.19c2.38,74.37 56.07,131.62 109.84,179.97 26.89,24.17 54.03,46.28 75.47,67 21.43,20.72 36.76,40.13 41.31,57.13l9.03,33.69 9,-33.69c3.99,-14.88 19.58,-34.12 41.59,-55.03 22.01,-20.91 50,-43.8 77.47,-68.78 54.94,-49.96 109.17,-108.88 106.9,-180.88 -2.19,-69.7 -45.2,-115.58 -96.75,-127.13 -8.46,-1.9 -17.13,-2.87 -25.88,-2.91zM139.87,42.69c39.62,-0.27 81.75,20.09 107.53,64.75l7.75,13.44L255.16,239.75c-1.48,-0.18 -2.97,-0.28 -4.5,-0.28 -20.71,0 -37.53,16.78 -37.53,37.5 0,20.72 16.82,37.5 37.53,37.5 1.53,0 3.02,-0.1 4.5,-0.28v117.66c-8.78,-15.03 -21.8,-29.43 -37,-44.13 -1.05,-1.01 -2.12,-2.05 -3.19,-3.06 13.68,-2.09 24.19,-13.89 24.19,-28.16 0,-15.74 -12.79,-28.5 -28.53,-28.5 -15.3,0 -27.78,12.05 -28.47,27.19 -13.03,-11.32 -26.6,-22.92 -39.97,-34.94 -52.87,-47.54 -101.56,-101.07 -103.65,-166.66 -2.02,-63.34 34.62,-99.42 79.72,-108.66 7.05,-1.44 14.29,-2.2 21.63,-2.25zM180.62,84.44c-20.71,0 -37.5,16.78 -37.5,37.5 0,4.62 0.85,9.04 2.38,13.12 -0.14,-0 -0.27,0 -0.41,0 -12.15,0 -22,9.85 -22,22 0,12.15 9.85,22 22,22 12.15,0 22,-9.85 22,-22v-0.13c4.2,1.62 8.76,2.5 13.53,2.5 20.72,0 37.5,-16.78 37.5,-37.5 0,-20.72 -16.78,-37.5 -37.5,-37.5zM206.94,168.13c-10.83,0.55 -19.44,9.53 -19.44,20.5 0,11.32 9.18,20.5 20.5,20.5s20.5,-9.18 20.5,-20.5 -9.18,-20.5 -20.5,-20.5c-0.35,0 -0.71,-0.02 -1.06,0zM173.09,220.56c-14.36,0 -26,11.64 -26,26 0,14.36 11.64,26.03 26,26.03s26.03,-11.67 26.03,-26.03c0,-14.36 -11.67,-26 -26.03,-26z"/>
|
||||
</vector>
|
||||
|
|
@ -6,20 +6,21 @@ data class CharacterSheet(
|
|||
val proficiency: Int, // Bonus de maîtrise
|
||||
val level: Int, // Niveau
|
||||
val characterClass: String, // Classe
|
||||
val hitPoint: String, // Point de vie
|
||||
val maxHitPoint: String, // Point de vie MAX
|
||||
val lifeDice: Int, // Dé de vie
|
||||
val spell1: Counter?,
|
||||
val spell2: Counter?,
|
||||
val spell3: Counter?,
|
||||
val spell4: Counter?,
|
||||
val spell5: Counter?,
|
||||
val spell6: Counter?,
|
||||
val spell7: Counter?,
|
||||
val spell8: Counter?,
|
||||
val spell9: Counter?,
|
||||
val hitPoint: Int, // Point de vie MAX
|
||||
val rage: Int?,
|
||||
val relentlessEndurance: Int?,
|
||||
val divineConduit: Int?,
|
||||
val bardicInspiration: Int?,
|
||||
val spell1: Int?,
|
||||
val spell2: Int?,
|
||||
val spell3: Int?,
|
||||
val spell4: Int?,
|
||||
val spell5: Int?,
|
||||
val spell6: Int?,
|
||||
val spell7: Int?,
|
||||
val spell8: Int?,
|
||||
val spell9: Int?,
|
||||
val dC: Int?,
|
||||
val criticalModifier: Int, // Critical Dice Multiplier
|
||||
val armorClass: Int, // Classe d'armure
|
||||
val speed: Int, // Vitesse
|
||||
val strength: Int, // Force
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
package com.pixelized.rplexicon.model
|
||||
|
||||
import com.google.firebase.database.PropertyName
|
||||
|
||||
data class CharacterSheetFire(
|
||||
@get:PropertyName(HIT_POINT)
|
||||
@set:PropertyName(HIT_POINT)
|
||||
var hitPoint: HitPoint? = null,
|
||||
|
||||
@get:PropertyName(RAGE)
|
||||
@set:PropertyName(RAGE)
|
||||
var rage: Int? = null,
|
||||
|
||||
@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,
|
||||
) {
|
||||
data class HitPoint(
|
||||
@get:PropertyName("additional")
|
||||
@set:PropertyName("additional")
|
||||
var additional: Int? = null,
|
||||
|
||||
@get:PropertyName("value")
|
||||
@set:PropertyName("value")
|
||||
var value: Int? = null,
|
||||
)
|
||||
|
||||
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_"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import android.accounts.Account
|
|||
import android.content.Context
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.google.android.gms.auth.api.identity.SignInCredential
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
|
|
@ -17,32 +18,30 @@ import javax.inject.Singleton
|
|||
class AuthenticationRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
private val signInCredential = mutableStateOf<SignInCredential?>(null)
|
||||
val isAuthenticated: State<Boolean> = derivedStateOf { signInCredential.value != null }
|
||||
|
||||
val credential: GoogleAccountCredential
|
||||
get() {
|
||||
val credential = GoogleAccountCredential
|
||||
.usingOAuth2(context, capabilities)
|
||||
.setBackOff(ExponentialBackOff())
|
||||
|
||||
credential.selectedAccount = signInCredential.value?.let {
|
||||
Account(it.id, ACCOUNT_TYPE)
|
||||
}
|
||||
|
||||
return credential
|
||||
}
|
||||
// private val signInCredential = mutableStateOf<SignInCredential?>(null)
|
||||
// val isAuthenticated: State<Boolean> = derivedStateOf { signInCredential.value != null }
|
||||
//
|
||||
// val credential: GoogleAccountCredential by derivedStateOf {
|
||||
// GoogleAccountCredential
|
||||
// .usingOAuth2(context, capabilities)
|
||||
// .setBackOff(ExponentialBackOff())
|
||||
// .also {
|
||||
// it.selectedAccount = signInCredential.value?.let {
|
||||
// Account(it.id, ACCOUNT_TYPE)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun updateAuthenticationState(
|
||||
credential: SignInCredential? = null,
|
||||
) {
|
||||
signInCredential.value = credential
|
||||
// signInCredential.value = credential
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ACCOUNT_TYPE = "google"
|
||||
private val capabilities = listOf(
|
||||
SheetsScopes.SPREADSHEETS_READONLY,
|
||||
)
|
||||
// private const val ACCOUNT_TYPE = "google"
|
||||
// private val capabilities = listOf(
|
||||
// SheetsScopes.SPREADSHEETS_READONLY,
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.pixelized.rplexicon.repository.authentication
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import com.google.firebase.database.DataSnapshot
|
||||
import com.google.firebase.database.DatabaseError
|
||||
import com.google.firebase.database.ValueEventListener
|
||||
import com.google.firebase.database.ktx.database
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.CharacterSheetFire
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FirebaseRepository @Inject constructor(
|
||||
application: Application,
|
||||
) {
|
||||
private val database = Firebase.database(
|
||||
url = application.getString(R.string.firebase_realtime_database),
|
||||
)
|
||||
|
||||
private val _error = MutableSharedFlow<Exception>()
|
||||
val error: SharedFlow<Exception> get() = _error
|
||||
|
||||
init {
|
||||
Firebase.database.setPersistenceEnabled(true)
|
||||
}
|
||||
|
||||
fun getCharacter(character: String): Flow<CharacterSheetFire> {
|
||||
return callbackFlow {
|
||||
// reference to the node
|
||||
val reference = database.getReference("$PATH_CHARACTERS/$character")
|
||||
// build a register the callback
|
||||
val listener = reference.addValueEventListener(object : ValueEventListener {
|
||||
override fun onDataChange(dataSnapshot: DataSnapshot) {
|
||||
val value = try {
|
||||
dataSnapshot.getValue(CharacterSheetFire::class.java)
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit(exception)
|
||||
null
|
||||
}
|
||||
if (value != null) {
|
||||
trySend(value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(error: DatabaseError) {
|
||||
Log.w(TAG, "Failed to read value.", error.toException())
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
awaitClose {
|
||||
reference.removeEventListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setCharacterHitPoint(character: String, value: Int, extra: Int) {
|
||||
val reference = database.getReference(
|
||||
"$PATH_CHARACTERS/$character/${CharacterSheetFire.HIT_POINT}"
|
||||
)
|
||||
reference.setValue(
|
||||
CharacterSheetFire.HitPoint(
|
||||
additional = extra,
|
||||
value = value,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun setToken(character: String, token: String, value: Int) {
|
||||
val reference = database.getReference(
|
||||
"$PATH_CHARACTERS/$character/$token"
|
||||
)
|
||||
reference.setValue(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FirebaseRepository"
|
||||
|
||||
private const val PATH_CHARACTERS = "Characters"
|
||||
}
|
||||
}
|
||||
|
|
@ -26,22 +26,55 @@ class AlterationRepository @Inject constructor(
|
|||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
/**
|
||||
* get all [Alteration] for a character
|
||||
* @return a list of alterations.
|
||||
*/
|
||||
fun getAlterations(character: String): List<Alteration> {
|
||||
return _assignedAlterations.value[character] ?: emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* get [Alteration] for a character that affect at least one [Property]
|
||||
* @param character the character name
|
||||
* @param properties the property list to filter on.
|
||||
* @return a list of alterations.
|
||||
*/
|
||||
fun getAlterations(character: String, vararg properties: Property): List<Alteration> {
|
||||
return getAlterations(character = character).filter {
|
||||
it.status.keys.any { key -> properties.contains(key) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check the activation state of a given alteration for given character.
|
||||
* @param character the character name
|
||||
* @param alteration the alteration name
|
||||
* @return true if the alteration is activated otherwise false
|
||||
*/
|
||||
fun getStatus(character: String, alteration: String): Boolean {
|
||||
return _status.value[character + alteration]
|
||||
?: _assignedAlterations.value[character]?.firstOrNull { it.name == alteration }?.active
|
||||
?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* get a map of [Property] and [Alteration.Status] for a given player if the alteration is active.
|
||||
* @param character the character name
|
||||
* @return a map of [Property] and [Alteration.Status]
|
||||
*/
|
||||
fun getActiveAlterationsStatus(character: String): Map<Property, List<Alteration.Status>> {
|
||||
val status = hashMapOf<Property, MutableList<Alteration.Status>>()
|
||||
_assignedAlterations.value[character]?.forEach { alteration ->
|
||||
if (_status.value[character + alteration.name] ?: alteration.active) {
|
||||
alteration.status.forEach {
|
||||
status.getOrPut(it.key) { mutableListOf() }.add(it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
suspend fun setStatus(character: String, alteration: String, value: Boolean?) {
|
||||
_status.emit(if (value != null) {
|
||||
_status.value.toMutableMap().also { it[character + alteration] = value }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.pixelized.rplexicon.utilitary.Update
|
|||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,13 @@ package com.pixelized.rplexicon.repository.parser
|
|||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.repository.parser.alteration.CounterParser
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class CharacterSheetParser @Inject constructor(
|
||||
private val counterParser: CounterParser,
|
||||
) {
|
||||
class CharacterSheetParser @Inject constructor() {
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(value: ValueRange): Map<String, CharacterSheet> {
|
||||
|
|
@ -51,38 +48,21 @@ class CharacterSheetParser @Inject constructor(
|
|||
proficiency = item.parseInt(MASTERY) ?: 2,
|
||||
level = item.parseInt(LEVEL) ?: 2,
|
||||
characterClass = item.parseString(CLASS) ?: "",
|
||||
hitPoint = item.parseString(HIT_POINT) ?: "1",
|
||||
maxHitPoint = item.parseString(MAX_HIT_POINT) ?: "1",
|
||||
lifeDice = item.parseInt(LIFE_DICE) ?: 0,
|
||||
spell1 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_1)
|
||||
),
|
||||
spell2 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_2)
|
||||
),
|
||||
spell3 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_3)
|
||||
),
|
||||
spell4 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_4)
|
||||
),
|
||||
spell5 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_5)
|
||||
),
|
||||
spell6 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_6)
|
||||
),
|
||||
spell7 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_7)
|
||||
),
|
||||
spell8 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_8)
|
||||
),
|
||||
spell9 = counterParser.parseCounter(
|
||||
value = item.parseString(SPELL_LEVEL_9)
|
||||
),
|
||||
hitPoint = item.parseInt(MAX_HIT_POINT) ?: 1,
|
||||
rage = item.parseInt(RAGE),
|
||||
relentlessEndurance = item.parseInt(RELENTLESS_ENDURANCE),
|
||||
divineConduit = item.parseInt(DIVINE_CONDUIT),
|
||||
bardicInspiration = item.parseInt(BARDIC_INSPIRATION),
|
||||
spell1 = item.parseInt(SPELL_LEVEL_1),
|
||||
spell2 = item.parseInt(SPELL_LEVEL_2),
|
||||
spell3 = item.parseInt(SPELL_LEVEL_3),
|
||||
spell4 = item.parseInt(SPELL_LEVEL_4),
|
||||
spell5 = item.parseInt(SPELL_LEVEL_5),
|
||||
spell6 = item.parseInt(SPELL_LEVEL_6),
|
||||
spell7 = item.parseInt(SPELL_LEVEL_7),
|
||||
spell8 = item.parseInt(SPELL_LEVEL_8),
|
||||
spell9 = item.parseInt(SPELL_LEVEL_9),
|
||||
dC = item.parseInt(DD_SAVE_THROW),
|
||||
criticalModifier = item.parseInt(CRITICAL_MODIFIER) ?: 2,
|
||||
armorClass = item.parseInt(ARMOR_CLASS) ?: 10,
|
||||
speed = item.parseInt(SPEED) ?: 10,
|
||||
strength = item.parseInt(STRENGTH) ?: 10,
|
||||
|
|
@ -131,9 +111,11 @@ class CharacterSheetParser @Inject constructor(
|
|||
private const val RACE = "Race"
|
||||
private const val LEVEL = "Niveau"
|
||||
private const val CLASS = "Classe"
|
||||
private const val HIT_POINT = "Point de vie"
|
||||
private const val MAX_HIT_POINT = "Point de vie MAX"
|
||||
private const val LIFE_DICE = "Dé de vie"
|
||||
private const val RAGE = "Rage"
|
||||
private const val RELENTLESS_ENDURANCE = "Endurance implacable"
|
||||
private const val DIVINE_CONDUIT = "Conduit divin"
|
||||
private const val BARDIC_INSPIRATION = "Inspiration bardique"
|
||||
private const val SPELL_LEVEL_1 = "Sort de niveau 1"
|
||||
private const val SPELL_LEVEL_2 = "Sort de niveau 2"
|
||||
private const val SPELL_LEVEL_3 = "Sort de niveau 3"
|
||||
|
|
@ -144,7 +126,6 @@ class CharacterSheetParser @Inject constructor(
|
|||
private const val SPELL_LEVEL_8 = "Sort de niveau 8"
|
||||
private const val SPELL_LEVEL_9 = "Sort de niveau 9"
|
||||
private const val DD_SAVE_THROW = "DD sauvergarde des sorts"
|
||||
private const val CRITICAL_MODIFIER = "Dé de critique"
|
||||
private const val ARMOR_CLASS = "Classe d'armure"
|
||||
private const val SPEED = "Vitesse"
|
||||
private const val MASTERY = "Bonus de maîtrise"
|
||||
|
|
@ -185,9 +166,11 @@ class CharacterSheetParser @Inject constructor(
|
|||
RACE,
|
||||
LEVEL,
|
||||
CLASS,
|
||||
HIT_POINT,
|
||||
MAX_HIT_POINT,
|
||||
LIFE_DICE,
|
||||
RAGE,
|
||||
RELENTLESS_ENDURANCE,
|
||||
DIVINE_CONDUIT,
|
||||
BARDIC_INSPIRATION,
|
||||
SPELL_LEVEL_1,
|
||||
SPELL_LEVEL_2,
|
||||
SPELL_LEVEL_3,
|
||||
|
|
@ -198,7 +181,6 @@ class CharacterSheetParser @Inject constructor(
|
|||
SPELL_LEVEL_8,
|
||||
SPELL_LEVEL_9,
|
||||
DD_SAVE_THROW,
|
||||
CRITICAL_MODIFIER,
|
||||
ARMOR_CLASS,
|
||||
SPEED,
|
||||
MASTERY,
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class IndicatorStyle(
|
|||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun IndicatorStepPreview() {
|
||||
fun IndicatorStepPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
IndicatorStep(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package com.pixelized.rplexicon.ui.composable
|
||||
|
||||
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.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.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.graphics.graphicsLayer
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.lerp
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NumberPicker(
|
||||
modifier: Modifier = Modifier,
|
||||
itemSize: Dp = 42.dp,
|
||||
itemAmount: Int = 5,
|
||||
from: Int = 0,
|
||||
pager: PagerState,
|
||||
) {
|
||||
VerticalPager(
|
||||
modifier = Modifier
|
||||
.height(height = itemSize * itemAmount)
|
||||
.then(other = modifier),
|
||||
state = pager,
|
||||
contentPadding = PaddingValues(vertical = itemSize * itemAmount.floorDiv(2)),
|
||||
pageSize = PageSize.Fixed(itemSize),
|
||||
) { index ->
|
||||
Box(
|
||||
modifier = Modifier.size(size = itemSize),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.graphicsLayer {
|
||||
val fraction = 1f -
|
||||
((pager.currentPage - index) + pager.currentPageOffsetFraction)
|
||||
.absoluteValue
|
||||
.coerceIn(0f, 1f)
|
||||
lerp(start = 0.5f, stop = 1f, fraction = fraction).let {
|
||||
scaleX = it
|
||||
scaleY = it
|
||||
}
|
||||
lerp(start = 0.35f, stop = 1f, fraction = fraction).let {
|
||||
alpha = it
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
text = "${index + from}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun NumberPickerPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
NumberPicker(
|
||||
pager = rememberPagerState(initialPage = 20) { 99 }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.ui.composable.NumberPicker
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class CounterEditDialogUio(
|
||||
val id: String,
|
||||
val label: String,
|
||||
val value: Int,
|
||||
val max: Int,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HandleCounterEditDialog(
|
||||
dialog: State<CounterEditDialogUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (String, 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 = it.label,
|
||||
)
|
||||
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.id, pager.currentPage) },
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.pixelized.rplexicon.ui.composable.edit
|
||||
|
||||
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.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
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 com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.NumberPicker
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class HpPointDialogUio(
|
||||
val value: Int,
|
||||
val max: Int,
|
||||
val extra: Int,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HandleHitPointEditDialog(
|
||||
dialog: State<HpPointDialogUio?>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (hp: Int, extra: Int) -> Unit,
|
||||
) {
|
||||
dialog.value?.let {
|
||||
val hpPager = rememberPagerState(initialPage = it.value) { it.max + 1 }
|
||||
val extraPager = rememberPagerState(initialPage = it.extra) { 99 }
|
||||
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),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
text = stringResource(id = R.string.character_sheet_title_hp),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = 64.dp, height = 1.dp)
|
||||
.background(color = MaterialTheme.lexicon.colorScheme.characterSheet.innerBorder),
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
NumberPicker(
|
||||
modifier = Modifier.width(width = 64.dp),
|
||||
pager = hpPager,
|
||||
)
|
||||
Text(
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = "+"
|
||||
)
|
||||
NumberPicker(
|
||||
modifier = Modifier.width(width = 64.dp),
|
||||
pager = extraPager,
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
onClick = { onConfirm(hpPager.currentPage, extraPager.currentPage) },
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
private fun HandleHitPointEditDialogPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
val dialog = remember {
|
||||
mutableStateOf(
|
||||
HpPointDialogUio(
|
||||
value = 20,
|
||||
max = 30,
|
||||
extra = 0,
|
||||
)
|
||||
)
|
||||
}
|
||||
HandleHitPointEditDialog(
|
||||
dialog = dialog,
|
||||
onDismissRequest = { },
|
||||
onConfirm = { _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.NavOptionsBuilder
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.HOME_ROUTE
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet
|
||||
|
|
@ -26,7 +27,7 @@ val LocalScreenNavHost = staticCompositionLocalOf<NavHostController> {
|
|||
@Composable
|
||||
fun ScreenNavHost(
|
||||
navHostController: NavHostController = rememberNavController(),
|
||||
startDestination: String = HOME_ROUTE,
|
||||
startDestination: String = AUTHENTICATION_ROUTE,
|
||||
) {
|
||||
val lexiconListState = rememberLazyListState()
|
||||
val questListState = rememberLazyListState()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
|
|
@ -49,6 +50,7 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.LocalActivity
|
||||
import com.pixelized.rplexicon.LocalSnack
|
||||
|
|
@ -70,6 +72,9 @@ sealed class AuthenticationStateUio {
|
|||
@Stable
|
||||
data object Initial : AuthenticationStateUio()
|
||||
|
||||
@Stable
|
||||
data object Progress : AuthenticationStateUio()
|
||||
|
||||
@Stable
|
||||
data object Success : AuthenticationStateUio()
|
||||
|
||||
|
|
@ -104,6 +109,11 @@ fun AuthenticationScreen(
|
|||
|
||||
HandleAuthenticationState(
|
||||
state = state,
|
||||
onProgress = {
|
||||
Dialog(onDismissRequest = { }) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
},
|
||||
onSignIn = {
|
||||
screen.navigateToHome(option = rootOption())
|
||||
},
|
||||
|
|
@ -119,20 +129,24 @@ fun AuthenticationScreen(
|
|||
@Composable
|
||||
fun HandleAuthenticationState(
|
||||
state: State<AuthenticationStateUio>,
|
||||
onProgress: @Composable () -> Unit,
|
||||
onSignIn: suspend CoroutineScope.() -> Unit,
|
||||
onSignInError: suspend CoroutineScope.(exception: Exception?) -> Unit,
|
||||
) {
|
||||
when (val dummy = state.value) {
|
||||
AuthenticationStateUio.Initial -> Unit
|
||||
is AuthenticationStateUio.Success -> {
|
||||
LaunchedEffect(key1 = "Authentication.Success", block = onSignIn)
|
||||
}
|
||||
is AuthenticationStateUio.Initial -> Unit
|
||||
|
||||
is AuthenticationStateUio.Failure -> {
|
||||
LaunchedEffect(key1 = "Authentication.Error") {
|
||||
onSignInError(dummy.exception)
|
||||
}
|
||||
}
|
||||
is AuthenticationStateUio.Progress -> onProgress()
|
||||
|
||||
is AuthenticationStateUio.Success -> LaunchedEffect(
|
||||
key1 = "Authentication.Success",
|
||||
block = onSignIn,
|
||||
)
|
||||
|
||||
is AuthenticationStateUio.Failure -> LaunchedEffect(
|
||||
key1 = "Authentication.Error",
|
||||
block = { onSignInError(dummy.exception) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import androidx.lifecycle.AndroidViewModel
|
|||
import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
|
||||
import com.google.android.gms.auth.api.identity.Identity
|
||||
import com.google.android.gms.auth.api.identity.SignInCredential
|
||||
import com.google.firebase.auth.GoogleAuthProvider
|
||||
import com.google.firebase.auth.ktx.auth
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.repository.authentication.AuthenticationRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -30,11 +33,12 @@ class AuthenticationViewModel @Inject constructor(
|
|||
private val context: Context get() = getApplication()
|
||||
private var launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>? = null
|
||||
|
||||
private val state = mutableStateOf(
|
||||
when (repository.isAuthenticated.value) {
|
||||
true -> AuthenticationStateUio.Success
|
||||
else -> AuthenticationStateUio.Initial
|
||||
}
|
||||
private val state = mutableStateOf<AuthenticationStateUio>(
|
||||
AuthenticationStateUio.Initial
|
||||
// when (repository.isAuthenticated.value) {
|
||||
// true -> AuthenticationStateUio.Success
|
||||
// else -> AuthenticationStateUio.Initial
|
||||
// }
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -43,13 +47,28 @@ class AuthenticationViewModel @Inject constructor(
|
|||
contract = ActivityResultContracts.StartIntentSenderForResult(),
|
||||
onResult = {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
val credential: SignInCredential = Identity
|
||||
state.value = AuthenticationStateUio.Progress
|
||||
// sign in request succeed. retrieve google credential
|
||||
val googleCredential: SignInCredential = Identity
|
||||
.getSignInClient(context)
|
||||
.getSignInCredentialFromIntent(it.data)
|
||||
|
||||
state.value = AuthenticationStateUio.Success
|
||||
repository.updateAuthenticationState(credential = credential)
|
||||
// build firebase credential
|
||||
val firebaseCredential = GoogleAuthProvider
|
||||
.getCredential(googleCredential.googleIdToken, null)
|
||||
// sign in to Firebase
|
||||
Firebase.auth
|
||||
.signInWithCredential(firebaseCredential)
|
||||
.addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
state.value = AuthenticationStateUio.Success
|
||||
repository.updateAuthenticationState(credential = googleCredential)
|
||||
} else {
|
||||
state.value = AuthenticationStateUio.Failure(task.exception)
|
||||
repository.updateAuthenticationState(credential = null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.value = AuthenticationStateUio.Initial
|
||||
repository.updateAuthenticationState(credential = null)
|
||||
}
|
||||
},
|
||||
|
|
@ -60,10 +79,13 @@ class AuthenticationViewModel @Inject constructor(
|
|||
fun signIn(activity: Activity) {
|
||||
state.value = AuthenticationStateUio.Initial
|
||||
|
||||
// build a request to sign in with google credential.
|
||||
// At that point we do only use google sign in service
|
||||
val request: GetSignInIntentRequest = GetSignInIntentRequest.builder()
|
||||
.setServerClientId(context.getString(R.string.google_sign_in_id))
|
||||
.build()
|
||||
|
||||
// use the pre register launcher to start the sign in request intent.
|
||||
Identity.getSignInClient(activity).getSignInIntent(request)
|
||||
.addOnSuccessListener { result ->
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -38,21 +38,22 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.zIndex
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.LocalRollOverlay
|
||||
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.IndicatorStep
|
||||
import com.pixelized.rplexicon.ui.composable.IndicatorStepPreview
|
||||
import com.pixelized.rplexicon.ui.composable.Loader
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderPreview
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.*
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio.ID.*
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage
|
||||
|
|
@ -114,11 +115,10 @@ fun CharacterSheetScreen(
|
|||
onBack = {
|
||||
screen.popBackStack()
|
||||
},
|
||||
header = {
|
||||
CharacterSheetHeader(
|
||||
modifier = Modifier.zIndex(1f),
|
||||
pagerState = pagerState,
|
||||
header = viewModel.header,
|
||||
indicator = {
|
||||
IndicatorStep(
|
||||
count = pagerState.pageCount,
|
||||
selectedIndex = pagerState.currentPage,
|
||||
)
|
||||
},
|
||||
loader = {
|
||||
|
|
@ -135,7 +135,6 @@ fun CharacterSheetScreen(
|
|||
},
|
||||
actions = {
|
||||
ActionPage(
|
||||
character = viewModel.character,
|
||||
sheetState = sheetState,
|
||||
attackViewModel = attackViewModel,
|
||||
spellsViewModel = spellsViewModel,
|
||||
|
|
@ -182,11 +181,11 @@ private fun CharacterSheetContent(
|
|||
refreshState: PullRefreshState,
|
||||
onRefresh: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
header: @Composable ColumnScope.() -> Unit,
|
||||
loader: @Composable BoxScope.() -> Unit,
|
||||
proficiencies: @Composable PagerScope.() -> Unit,
|
||||
actions: @Composable PagerScope.() -> Unit,
|
||||
alterations: @Composable PagerScope.() -> Unit,
|
||||
indicator: @Composable ColumnScope.() -> Unit,
|
||||
sheet: @Composable () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
|
|
@ -195,6 +194,7 @@ private fun CharacterSheetContent(
|
|||
contentWindowInsets = NO_WINDOW_INSETS,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
modifier = Modifier.shadow(elevation = 4.dp),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
|
|
@ -232,9 +232,8 @@ private fun CharacterSheetContent(
|
|||
content = {
|
||||
Column(
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
header()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -255,9 +254,9 @@ private fun CharacterSheetContent(
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
loader()
|
||||
}
|
||||
indicator()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -288,11 +287,11 @@ private fun CharacterScreenPreview(
|
|||
refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }),
|
||||
onRefresh = { },
|
||||
onBack = { },
|
||||
header = { CharacterSheetHeaderPreview() },
|
||||
loader = { },
|
||||
proficiencies = { ProficiencyPreview() },
|
||||
actions = { ActionPagePreview() },
|
||||
alterations = { AlterationPagePreview() },
|
||||
indicator = { IndicatorStepPreview() },
|
||||
sheet = { SpellLevelChooserPreview() },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,60 +3,31 @@ package com.pixelized.rplexicon.ui.screens.character
|
|||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.SpellRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetHeaderUioFactory
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.toActiveStatus
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class CharacterSheetViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val actionRepository: ActionRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
headerFactory: CharacterSheetHeaderUioFactory,
|
||||
) : ViewModel() {
|
||||
val character = savedStateHandle.characterSheetArgument.name
|
||||
|
||||
private val _header = mutableStateOf<CharacterSheetHeaderUio?>(null)
|
||||
val header: State<CharacterSheetHeaderUio?> get() = _header
|
||||
|
||||
private val _isLoading = mutableStateOf(false)
|
||||
val isLoading: State<Boolean> get() = _isLoading
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
characterRepository.data
|
||||
.combine(alterationRepository.assignedAlterations) { sheets, _ -> sheets }
|
||||
.collect { sheets ->
|
||||
_header.value = withContext(Dispatchers.Default) {
|
||||
val alterations =
|
||||
alterationRepository.getAlterations(character = character)
|
||||
headerFactory.convert(
|
||||
model = sheets.getValue(character),
|
||||
alterations = alterations.toActiveStatus(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
update(force = false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.padding
|
||||
|
|
@ -50,12 +51,15 @@ data class AttackUio(
|
|||
@Composable
|
||||
fun Attack(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
weapon: AttackUio,
|
||||
onHit: (String) -> Unit,
|
||||
onDamage: (String) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
modifier = Modifier
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -116,9 +120,7 @@ private fun WeaponPreview(
|
|||
LexiconTheme {
|
||||
Surface {
|
||||
Attack(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
weapon = preview,
|
||||
onHit = { },
|
||||
onDamage = { },
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ 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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
|
|
@ -37,24 +39,30 @@ data class SpellHeaderUio(
|
|||
@Stable
|
||||
data class Count(
|
||||
val value: Int,
|
||||
val max: Int?,
|
||||
val max: Int,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpellHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(top = 8.dp, bottom = 4.dp),
|
||||
header: SpellHeaderUio,
|
||||
onSpell: (level: Int, value: Int, max: Int) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(color = MaterialTheme.colorScheme.surface)
|
||||
.clickable(enabled = header.count != null) {
|
||||
header.count?.let { onSpell(header.level, it.value, it.max) }
|
||||
}
|
||||
.padding(horizontal = 16.dp)
|
||||
.heightIn(min = 32.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(paddingValues = padding)
|
||||
.align(alignment = Alignment.Center),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
|
|
@ -88,23 +96,22 @@ fun SpellHeader(
|
|||
header.count?.let { count ->
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = "${count.value}",
|
||||
)
|
||||
count.max?.let { max ->
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
text = "/",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
text = "$max",
|
||||
)
|
||||
}
|
||||
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}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +132,7 @@ private fun SpellHeaderPreview(
|
|||
Surface {
|
||||
SpellHeader(
|
||||
header = preview,
|
||||
onSpell = { _, _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -58,6 +59,7 @@ data class SpellUio(
|
|||
@Composable
|
||||
fun Spell(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(vertical = 8.dp, horizontal = 16.dp),
|
||||
spell: SpellUio,
|
||||
onClick: (String) -> Unit,
|
||||
onHit: (String) -> Unit,
|
||||
|
|
@ -67,6 +69,7 @@ fun Spell(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.clickable { onClick(spell.name) }
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@ package com.pixelized.rplexicon.ui.screens.character.composable.character
|
|||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.padding
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -19,9 +17,9 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.ui.composable.IndicatorStep
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetHeaderUio(
|
||||
|
|
@ -31,55 +29,58 @@ data class CharacterSheetHeaderUio(
|
|||
val dC: LabelPointUio?,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun CharacterSheetHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
pagerState: PagerState,
|
||||
padding : PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||
header: State<CharacterSheetHeaderUio?>,
|
||||
onHitPoint : () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shadowElevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.padding(padding),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 16.dp,
|
||||
alignment = Alignment.CenterHorizontally
|
||||
),
|
||||
) {
|
||||
header.value?.armorClass?.let { LabelPoint(label = it) }
|
||||
header.value?.hitPoint?.let { LabelPoint(label = it) }
|
||||
header.value?.dC?.let { LabelPoint(label = it) }
|
||||
header.value?.speed?.let { LabelPoint(label = it,) }
|
||||
header.value?.armorClass?.let {
|
||||
LabelPoint(label = it)
|
||||
}
|
||||
header.value?.hitPoint?.let {
|
||||
LabelPoint(label = it, onClick = onHitPoint)
|
||||
}
|
||||
header.value?.dC?.let {
|
||||
LabelPoint(label = it)
|
||||
}
|
||||
header.value?.speed?.let {
|
||||
LabelPoint(label = it)
|
||||
}
|
||||
}
|
||||
|
||||
IndicatorStep(
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
count = pagerState.pageCount,
|
||||
selectedIndex = pagerState.currentPage,
|
||||
Divider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
color = MaterialTheme.lexicon.colorScheme.placeholder,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
fun CharacterSheetHeaderPreview() {
|
||||
private fun CharacterSheetHeaderPreview() {
|
||||
LexiconTheme {
|
||||
Surface {
|
||||
CharacterSheetHeader(
|
||||
header = rememberCharacterHeaderStatePreview(),
|
||||
pagerState = rememberPagerState { 2 },
|
||||
onHitPoint = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,19 @@ package com.pixelized.rplexicon.ui.screens.character.composable.character
|
|||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -22,7 +26,7 @@ import com.pixelized.rplexicon.R
|
|||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
|
||||
@Stable
|
||||
class LabelPointUio(
|
||||
data class LabelPointUio(
|
||||
val label: Int?,
|
||||
val value: String?,
|
||||
val max: String? = null,
|
||||
|
|
@ -35,9 +39,16 @@ fun LabelPoint(
|
|||
valueStyle: TextStyle = MaterialTheme.typography.headlineMedium,
|
||||
maxStyle: TextStyle = MaterialTheme.typography.titleMedium,
|
||||
label: LabelPointUio,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
enabled = onClick != null, onClick = { onClick?.invoke() }
|
||||
)
|
||||
.then(other = modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
label.label?.let {
|
||||
|
|
@ -53,6 +64,10 @@ fun LabelPoint(
|
|||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = valueStyle,
|
||||
color = when (onClick) {
|
||||
null -> MaterialTheme.colorScheme.onSurface
|
||||
else -> MaterialTheme.colorScheme.primary
|
||||
},
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = label.value ?: "0"
|
||||
)
|
||||
|
|
@ -91,6 +106,7 @@ private fun LabelPointPreview() {
|
|||
value = "13",
|
||||
max = "/ 15"
|
||||
),
|
||||
onClick = { },
|
||||
)
|
||||
LabelPoint(
|
||||
label = LabelPointUio(
|
||||
|
|
|
|||
|
|
@ -1,42 +1,34 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.factory
|
||||
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.Alteration
|
||||
import com.pixelized.rplexicon.model.Attack
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.pages.actions.HeaderViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class CharacterSheetHeaderUioFactory @Inject constructor() {
|
||||
|
||||
fun convert(
|
||||
model: CharacterSheet,
|
||||
alterations: Map<Property, List<Alteration.Status>>,
|
||||
sheetHeaderData: HeaderViewModel.SheetHeaderData?,
|
||||
fireHeaderData: HeaderViewModel.FireHeaderData?,
|
||||
): CharacterSheetHeaderUio {
|
||||
// compute alteration for the CA.
|
||||
val armorClassAlteration: Int = alterations[Property.ARMOR_CLASS]?.sumOf {
|
||||
it.bonus.sumOf { bonus -> bonus.value }
|
||||
} ?: 0
|
||||
|
||||
return CharacterSheetHeaderUio(
|
||||
armorClass = LabelPointUio(
|
||||
label = R.string.character_sheet_title_ca,
|
||||
value = "${model.armorClass + armorClassAlteration}",
|
||||
value = sheetHeaderData?.ca?.let { "$it" } ?: " ",
|
||||
max = null,
|
||||
),
|
||||
speed = LabelPointUio(
|
||||
label = R.string.character_sheet_title_speed,
|
||||
value = "${model.speed}",
|
||||
value = sheetHeaderData?.speed?.let { "$it" } ?: " ",
|
||||
max = "m",
|
||||
),
|
||||
hitPoint = LabelPointUio(
|
||||
label = R.string.character_sheet_title_hp,
|
||||
value = model.hitPoint,
|
||||
max = "/ ${model.maxHitPoint}",
|
||||
value = convertToHitPointLabel(hitPoint = fireHeaderData),
|
||||
max = sheetHeaderData?.hpMax?.let { "/ $it" } ?: " ",
|
||||
),
|
||||
dC = model.dC?.let {
|
||||
dC = sheetHeaderData?.dc?.let {
|
||||
LabelPointUio(
|
||||
label = R.string.character_sheet_title_dc,
|
||||
value = "$it",
|
||||
|
|
@ -44,4 +36,12 @@ class CharacterSheetHeaderUioFactory @Inject constructor() {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToHitPointLabel(hitPoint: HeaderViewModel.FireHeaderData?): String {
|
||||
return when {
|
||||
hitPoint?.hp == null -> "?"
|
||||
hitPoint.extra == 0 -> "${hitPoint.hp}"
|
||||
else -> "${hitPoint.hp}+${hitPoint.extra}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ 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.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
|
||||
|
|
@ -13,15 +15,22 @@ 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.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.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.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSpellDetail
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.Attack
|
||||
|
|
@ -30,18 +39,26 @@ 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.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)
|
||||
@Composable
|
||||
fun ActionPage(
|
||||
character: String,
|
||||
sheetState: ModalBottomSheetState,
|
||||
headerViewModel: HeaderViewModel = hiltViewModel(),
|
||||
attackViewModel: AttackActionViewModel = hiltViewModel(),
|
||||
spellsViewModel: SpellsActionViewModel = hiltViewModel(),
|
||||
tokenViewModel: TokenViewModel = hiltViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenNavHost.current
|
||||
val overlay = LocalRollOverlay.current
|
||||
|
|
@ -49,8 +66,11 @@ fun ActionPage(
|
|||
|
||||
ActionsPageContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
header = headerViewModel.header,
|
||||
attacks = attackViewModel.attacks,
|
||||
tokens = tokenViewModel.tokens,
|
||||
spells = spellsViewModel.spells,
|
||||
onHitPoint = headerViewModel::toggleHitPointDialog,
|
||||
onAttackHit = { id ->
|
||||
attackViewModel.onHitRoll(id)?.let {
|
||||
overlay.prepareRoll(diceThrow = it)
|
||||
|
|
@ -63,9 +83,15 @@ fun ActionPage(
|
|||
overlay.showOverlay()
|
||||
}
|
||||
},
|
||||
onToken = {
|
||||
tokenViewModel.showTokenEditDialog(dialog = it)
|
||||
},
|
||||
onSpellLevel = { level: Int, value: Int, max: Int ->
|
||||
tokenViewModel.showSpellTokenEditDialog(level = level, value = value, max = max)
|
||||
},
|
||||
onSpell = { spell ->
|
||||
screen.navigateToSpellDetail(
|
||||
character = character,
|
||||
character = headerViewModel.character,
|
||||
spell = spell,
|
||||
)
|
||||
},
|
||||
|
|
@ -87,6 +113,18 @@ fun ActionPage(
|
|||
}
|
||||
},
|
||||
)
|
||||
|
||||
HandleHitPointEditDialog(
|
||||
dialog = headerViewModel.hitPointDialog,
|
||||
onDismissRequest = headerViewModel::toggleHitPointDialog,
|
||||
onConfirm = headerViewModel::applyHitPointChange,
|
||||
)
|
||||
|
||||
HandleCounterEditDialog(
|
||||
dialog = tokenViewModel.dialog,
|
||||
onDismissRequest = tokenViewModel::hideCounterEditDialog,
|
||||
onConfirm = tokenViewModel::applyCounterValue
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
@ -94,10 +132,15 @@ fun ActionPage(
|
|||
fun ActionsPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
lazyListState: LazyListState = rememberLazyListState(),
|
||||
header: State<CharacterSheetHeaderUio?>,
|
||||
attacks: State<List<AttackUio>>,
|
||||
tokens: State<List<TokenItemUio>>,
|
||||
spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>>,
|
||||
onHitPoint: () -> Unit,
|
||||
onAttackHit: (id: String) -> Unit,
|
||||
onAttackDamage: (id: String) -> Unit,
|
||||
onToken: (TokenItemUio) -> Unit,
|
||||
onSpellLevel: (level: Int, value: Int, max: Int) -> Unit,
|
||||
onSpell: (id: String) -> Unit,
|
||||
onSpellHit: (id: String) -> Unit,
|
||||
onSpellDamage: (id: String) -> Unit,
|
||||
|
|
@ -106,28 +149,73 @@ fun ActionsPageContent(
|
|||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = lazyListState,
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
contentPadding = PaddingValues(bottom = 8.dp),
|
||||
) {
|
||||
stickyHeader {
|
||||
CharacterSheetHeader(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
header = header,
|
||||
onHitPoint = onHitPoint,
|
||||
)
|
||||
}
|
||||
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
|
||||
items(items = attacks.value) {
|
||||
Attack(
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
weapon = it,
|
||||
onHit = onAttackHit,
|
||||
onDamage = onAttackDamage,
|
||||
)
|
||||
}
|
||||
|
||||
spells.value.forEach { entry ->
|
||||
if (tokens.value.isNotEmpty()) {
|
||||
items(count = 1) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
}
|
||||
stickyHeader {
|
||||
SpellHeader(
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp),
|
||||
header = entry.first,
|
||||
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(
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
spell = it,
|
||||
onClick = onSpell,
|
||||
onHit = onSpellHit,
|
||||
|
|
@ -147,10 +235,15 @@ fun ActionPagePreview() {
|
|||
Surface {
|
||||
ActionsPageContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
header = rememberCharacterHeaderStatePreview(),
|
||||
attacks = rememberAttackListStatePreview(),
|
||||
tokens = rememberTokenListStatePreview(),
|
||||
spells = rememberSpellListStatePreview(),
|
||||
onHitPoint = { },
|
||||
onAttackHit = { },
|
||||
onAttackDamage = { },
|
||||
onToken = { },
|
||||
onSpellLevel = { _, _, _ -> },
|
||||
onSpell = { },
|
||||
onSpellHit = { },
|
||||
onSpellDamage = { },
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
|||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.factory.AttackUioFactory
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.toActiveStatus
|
||||
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
|
||||
|
|
@ -40,18 +40,22 @@ class AttackActionViewModel @Inject constructor(
|
|||
init {
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
actionRepository.data.collect { sheets ->
|
||||
_attacks.value = withContext(Dispatchers.Default) {
|
||||
val alterations = alterationRepository.getAlterations(character = character)
|
||||
sheets[character]?.map { action ->
|
||||
attackFactory.convert(
|
||||
characterSheet = model,
|
||||
alterations = alterations.toActiveStatus(),
|
||||
attack = action,
|
||||
actionRepository.data
|
||||
.combine(alterationRepository.assignedAlterations) { actions, _ -> actions }
|
||||
.collect { sheets ->
|
||||
_attacks.value = withContext(Dispatchers.Default) {
|
||||
val alterations = alterationRepository.getActiveAlterationsStatus(
|
||||
character = character,
|
||||
)
|
||||
} ?: emptyList()
|
||||
sheets[character]?.map { action ->
|
||||
attackFactory.convert(
|
||||
characterSheet = model,
|
||||
alterations = alterations,
|
||||
attack = action,
|
||||
)
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package com.pixelized.rplexicon.ui.screens.character.pages.actions
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
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.HpPointDialogUio
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetHeaderUioFactory
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.sum
|
||||
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 HeaderViewModel @Inject constructor(
|
||||
private val headerFactory: CharacterSheetHeaderUioFactory,
|
||||
private val firebaseRepository: FirebaseRepository,
|
||||
characterRepository: CharacterSheetRepository,
|
||||
alterationRepository: AlterationRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
val character = savedStateHandle.characterSheetArgument.name
|
||||
|
||||
private val sheetData = mutableStateOf<SheetHeaderData?>(null)
|
||||
private val fireData = mutableStateOf<FireHeaderData?>(null)
|
||||
|
||||
val header: State<CharacterSheetHeaderUio?> = derivedStateOf {
|
||||
headerFactory.convert(
|
||||
sheetHeaderData = sheetData.value,
|
||||
fireHeaderData = fireData.value,
|
||||
)
|
||||
}
|
||||
|
||||
private val _hitPointDialog = mutableStateOf<HpPointDialogUio?>(null)
|
||||
val hitPointDialog: State<HpPointDialogUio?> get() = _hitPointDialog
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
launch(context = Dispatchers.IO) {
|
||||
characterRepository.data
|
||||
.combine(alterationRepository.assignedAlterations) { sheets, _ -> sheets }
|
||||
.collect { sheets ->
|
||||
val character = sheets.getValue(character)
|
||||
val alterations = alterationRepository.getActiveAlterationsStatus(
|
||||
character = character.name,
|
||||
)
|
||||
val data = SheetHeaderData(
|
||||
hpMax = character.hitPoint + alterations[Property.HIT_POINT].sum,
|
||||
speed = character.speed,
|
||||
ca = character.armorClass + alterations[Property.ARMOR_CLASS].sum,
|
||||
dc = character.dC,
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
sheetData.value = data
|
||||
}
|
||||
}
|
||||
}
|
||||
launch(context = Dispatchers.IO) {
|
||||
firebaseRepository.getCharacter(character = character).collect { sheets ->
|
||||
val data = FireHeaderData(
|
||||
hp = sheets.hitPoint?.value ?: 1,
|
||||
extra = sheets.hitPoint?.additional ?: 0,
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
fireData.value = data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleHitPointDialog() {
|
||||
_hitPointDialog.value = if (_hitPointDialog.value == null) {
|
||||
HpPointDialogUio(
|
||||
value = fireData.value?.hp ?: 10,
|
||||
extra = fireData.value?.extra ?: 0,
|
||||
max = sheetData.value?.hpMax ?: 10,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun applyHitPointChange(hp: Int, extra: Int) {
|
||||
firebaseRepository.setCharacterHitPoint(
|
||||
character = character,
|
||||
value = hp,
|
||||
extra = extra,
|
||||
)
|
||||
_hitPointDialog.value = null
|
||||
}
|
||||
|
||||
@Stable
|
||||
data class SheetHeaderData(
|
||||
val hpMax: Int,
|
||||
val speed: Int,
|
||||
val ca: Int,
|
||||
val dc: Int?,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class FireHeaderData(
|
||||
val hp: Int,
|
||||
val extra: Int,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,10 +7,13 @@ import androidx.lifecycle.AndroidViewModel
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.model.AssignedSpell
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.model.CharacterSheetFire
|
||||
import com.pixelized.rplexicon.model.DiceThrow
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
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.navigation.screens.characterSheetArgument
|
||||
|
|
@ -26,6 +29,7 @@ import com.pixelized.rplexicon.utilitary.extentions.local.spell
|
|||
import com.pixelized.rplexicon.utilitary.extentions.modifier
|
||||
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
|
||||
|
|
@ -33,14 +37,16 @@ import kotlin.math.max
|
|||
|
||||
@HiltViewModel
|
||||
class SpellsActionViewModel @Inject constructor(
|
||||
application: Application,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val characterRepository: CharacterSheetRepository,
|
||||
private val firebaseRepository: FirebaseRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
application: Application,
|
||||
spellFactory: SpellUioFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : AndroidViewModel(application) {
|
||||
private val character = savedStateHandle.characterSheetArgument.name
|
||||
private val model: CharacterSheet get() = characterRepository.data.value.getValue(character)
|
||||
private val characterName = savedStateHandle.characterSheetArgument.name
|
||||
private val character: CharacterSheet
|
||||
get() = characterRepository.data.value.getValue(characterName)
|
||||
|
||||
private val _spells = mutableStateOf<List<Pair<SpellHeaderUio, List<SpellUio>>>>(emptyList())
|
||||
val spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>> get() = _spells
|
||||
|
|
@ -51,95 +57,107 @@ class SpellsActionViewModel @Inject constructor(
|
|||
init {
|
||||
// TODO rework that part. use factory
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
spellRepository.spells.collect {
|
||||
_spells.value = withContext(Dispatchers.Default) {
|
||||
if (model.isWarlock) {
|
||||
it[character]
|
||||
?.sortedBy { it.spell.name }
|
||||
?.sortedBy { it.spell.level }
|
||||
?.groupBy { it.spell.level == 0 }
|
||||
?.map { entry ->
|
||||
if (entry.key) {
|
||||
SpellHeaderUio(
|
||||
level = 0,
|
||||
count = null,
|
||||
)
|
||||
} else {
|
||||
val firstSpellSlot = model.firstSpellSlot()
|
||||
SpellHeaderUio(
|
||||
level = firstSpellSlot ?: 1,
|
||||
count = model.spell(level = firstSpellSlot ?: 1)?.let {
|
||||
SpellHeaderUio.Count(
|
||||
it.value,
|
||||
it.max
|
||||
)
|
||||
}
|
||||
)
|
||||
} to entry.value.map {
|
||||
spellFactory.toUio(
|
||||
assignedSpell = it,
|
||||
characterSheet = model
|
||||
)
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
} else {
|
||||
it[character]
|
||||
?.sortedBy { it.spell.name }
|
||||
?.sortedBy { it.spell.level }
|
||||
?.groupBy { it.spell.level }
|
||||
?.map { entry ->
|
||||
SpellHeaderUio(
|
||||
level = entry.key,
|
||||
count = model.spell(level = entry.key)?.let {
|
||||
SpellHeaderUio.Count(
|
||||
it.value,
|
||||
it.max
|
||||
launch(Dispatchers.IO) {
|
||||
characterRepository.data
|
||||
.combine(spellRepository.spells) { sheets, spells ->
|
||||
Struct(sheets = sheets, spells = spells)
|
||||
}
|
||||
.combine(firebaseRepository.getCharacter(character = characterName)) { struct, fire ->
|
||||
struct.also { it.fire = fire }
|
||||
}
|
||||
.collect { data ->
|
||||
val spells = data.spells[characterName]
|
||||
val character = data.sheets.getValue(characterName)
|
||||
_spells.value = withContext(Dispatchers.Default) {
|
||||
if (character.isWarlock) {
|
||||
spells
|
||||
?.sortedBy { it.spell.name }
|
||||
?.sortedBy { it.spell.level }
|
||||
?.groupBy { it.spell.level == 0 }
|
||||
?.map { entry ->
|
||||
if (entry.key) {
|
||||
SpellHeaderUio(
|
||||
level = 0,
|
||||
count = null,
|
||||
)
|
||||
},
|
||||
) to entry.value.map {
|
||||
spellFactory.toUio(
|
||||
assignedSpell = it,
|
||||
characterSheet = model
|
||||
)
|
||||
} else {
|
||||
val firstSpellSlot = character.firstSpellSlot()
|
||||
SpellHeaderUio(
|
||||
level = firstSpellSlot ?: 1,
|
||||
count = character.spell(level = firstSpellSlot ?: 1)
|
||||
?.let { max ->
|
||||
SpellHeaderUio.Count(
|
||||
value = data.fire.spell(
|
||||
level = firstSpellSlot ?: 1
|
||||
) ?: 0,
|
||||
max = max
|
||||
)
|
||||
}
|
||||
)
|
||||
} to entry.value.map {
|
||||
spellFactory.toUio(
|
||||
assignedSpell = it,
|
||||
characterSheet = character
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
?: emptyList()
|
||||
} else {
|
||||
spells
|
||||
?.sortedBy { it.spell.name }
|
||||
?.sortedBy { it.spell.level }
|
||||
?.groupBy { it.spell.level }
|
||||
?.map { entry ->
|
||||
SpellHeaderUio(
|
||||
level = entry.key,
|
||||
count = character.spell(level = entry.key)?.let { max ->
|
||||
SpellHeaderUio.Count(
|
||||
value = data.fire.spell(level = entry.key) ?: 0,
|
||||
max = max
|
||||
)
|
||||
},
|
||||
) to entry.value.map {
|
||||
spellFactory.toUio(
|
||||
assignedSpell = it,
|
||||
characterSheet = character
|
||||
)
|
||||
}
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldDisplaySpellLevelChooser(name: String): Boolean {
|
||||
val assignedSpell = spellRepository.find(
|
||||
character = character,
|
||||
character = characterName,
|
||||
spell = name,
|
||||
)
|
||||
return when (model.isWarlock) {
|
||||
return when (character.isWarlock) {
|
||||
true -> false
|
||||
else -> model.highestSpellLevel() > (assignedSpell?.spell?.level ?: 1)
|
||||
else -> character.highestSpellLevel() > (assignedSpell?.spell?.level ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareSpellCast(name: String) {
|
||||
val assignedSpell = spellRepository.find(
|
||||
character = character,
|
||||
character = characterName,
|
||||
spell = name,
|
||||
)
|
||||
if (assignedSpell != null) {
|
||||
val icon = assignedSpell.effect?.faces?.icon ?: R.drawable.ic_d20_24
|
||||
val base = assignedSpell.effect?.toString(character = model, level = 1) ?: ""
|
||||
val base = assignedSpell.effect?.toString(character = character, level = 1) ?: ""
|
||||
_preparedSpellLevel.value = SpellChooserUio(
|
||||
name = name,
|
||||
spells = List(
|
||||
size = max(0, model.highestSpellLevel() + 1 - assignedSpell.spell.level)
|
||||
size = max(0, character.highestSpellLevel() + 1 - assignedSpell.spell.level)
|
||||
) { index ->
|
||||
val level = assignedSpell.spell.level + index
|
||||
val remaining = model.spell(level)?.value
|
||||
val max = model.spell(level)?.max
|
||||
val remaining = character.spell(level)
|
||||
val max = character.spell(level)
|
||||
|
||||
SpellLevelUio(
|
||||
spell = assignedSpell.spell.name,
|
||||
|
|
@ -148,7 +166,7 @@ class SpellsActionViewModel @Inject constructor(
|
|||
max = max,
|
||||
icon = icon,
|
||||
value = base + (assignedSpell.level
|
||||
?.toString(character = model, level = index)
|
||||
?.toString(character = character, level = index)
|
||||
?.let { " + $it" }
|
||||
?: "")
|
||||
)
|
||||
|
|
@ -158,26 +176,26 @@ class SpellsActionViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun onCastSpell(id: String): DiceThrow {
|
||||
val spell = spellRepository.find(character = character, spell = id)
|
||||
val spell = spellRepository.find(character = characterName, spell = id)
|
||||
return onCastSpell(
|
||||
id = id,
|
||||
level = when (model.isWarlock) {
|
||||
true -> model.firstSpellSlot() ?: 1
|
||||
level = when (character.isWarlock) {
|
||||
true -> character.firstSpellSlot() ?: 1
|
||||
else -> spell?.spell?.level ?: 1
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun onCastSpell(id: String, level: Int): DiceThrow {
|
||||
return DiceThrow.SpellEffect(character = character, spell = id, level = level)
|
||||
return DiceThrow.SpellEffect(character = characterName, spell = id, level = level)
|
||||
}
|
||||
|
||||
fun onSpellHitRoll(id: String): DiceThrow {
|
||||
return DiceThrow.SpellAttack(character = character, spell = id)
|
||||
return DiceThrow.SpellAttack(character = characterName, spell = id)
|
||||
}
|
||||
|
||||
fun onSpellDamageRoll(id: String): DiceThrow {
|
||||
return DiceThrow.SpellDamage(character = character, spell = id)
|
||||
return DiceThrow.SpellDamage(character = characterName, spell = id)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -216,4 +234,11 @@ class SpellsActionViewModel @Inject constructor(
|
|||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private class Struct(
|
||||
val sheets: Map<String, CharacterSheet>,
|
||||
val spells: Map<String, List<AssignedSpell>>,
|
||||
) {
|
||||
lateinit var fire: CharacterSheetFire
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -34,12 +34,12 @@ fun AlterationPage(
|
|||
|
||||
AlterationPageContent(
|
||||
alterations = viewModel.alterations,
|
||||
onInfo = {
|
||||
onAlterationInfo = {
|
||||
viewModel.showAlterationDetail(id = it)
|
||||
},
|
||||
onClick = {
|
||||
onAlterationClick = {
|
||||
scope.launch {
|
||||
viewModel.toggle(alteration = it)
|
||||
viewModel.toggleAlteration(alteration = it)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -54,8 +54,8 @@ fun AlterationPage(
|
|||
fun AlterationPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
alterations: State<List<RollAlterationUio>>,
|
||||
onInfo: (String) -> Unit,
|
||||
onClick: (String) -> Unit,
|
||||
onAlterationInfo: (String) -> Unit,
|
||||
onAlterationClick: (String) -> Unit,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
|
|
@ -65,8 +65,8 @@ fun AlterationPageContent(
|
|||
RollAlteration(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
alteration = it,
|
||||
onInfo = onInfo,
|
||||
onClick = onClick,
|
||||
onInfo = onAlterationInfo,
|
||||
onClick = onAlterationClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -98,8 +98,8 @@ fun AlterationPagePreview() {
|
|||
Surface {
|
||||
AlterationPageContent(
|
||||
alterations = rememberRollAlterations(),
|
||||
onInfo = { },
|
||||
onClick = { },
|
||||
onAlterationInfo = { },
|
||||
onAlterationClick = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.navigation.screens.characterSheetArgument
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
|
||||
|
|
@ -18,7 +20,7 @@ import javax.inject.Inject
|
|||
|
||||
@HiltViewModel
|
||||
class AlterationViewModel @Inject constructor(
|
||||
private val repository: AlterationRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val factory: AlterationFactory,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
|
@ -32,24 +34,28 @@ class AlterationViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
repository.assignedAlterations.collect {
|
||||
_alterations.value = withContext(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
alterationRepository.assignedAlterations.collect {
|
||||
val alterations = it[character] ?: emptyList()
|
||||
factory.convert(character = character, alterations = alterations)
|
||||
.sortedBy { it.label }
|
||||
.sortedBy { it.subLabel }
|
||||
val data = factory.convert(character = character, alterations = alterations)
|
||||
.sortedBy { alteration -> alteration.label }
|
||||
.sortedBy { alteration -> alteration.subLabel }
|
||||
withContext(Dispatchers.Main) {
|
||||
_alterations.value = data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun toggle(alteration: String) {
|
||||
val value = repository.getStatus(character = character, alteration = alteration)
|
||||
repository.setStatus(character = character, alteration = alteration, value.not())
|
||||
suspend fun toggleAlteration(alteration: String) {
|
||||
val value = alterationRepository.getStatus(character = character, alteration = alteration)
|
||||
alterationRepository.setStatus(character = character, alteration = alteration, value.not())
|
||||
}
|
||||
|
||||
fun showAlterationDetail(id: String) {
|
||||
val alteration = repository.getAlterations(character = character).firstOrNull { it.name == id }
|
||||
val alteration = alterationRepository.getAlterations(character = character)
|
||||
.firstOrNull { it.name == id }
|
||||
if (alteration != null) {
|
||||
_alterationDetail.value = AlterationDetailUio(
|
||||
name = id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
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 = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,9 +13,9 @@ import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
|
|||
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio
|
||||
import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetUioFactory
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.toActiveStatus
|
||||
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
|
||||
|
|
@ -36,15 +36,18 @@ class ProficiencyViewModel @Inject constructor(
|
|||
init {
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
characterRepository.data.collect {
|
||||
_sheet.value = withContext(Dispatchers.Default) {
|
||||
val alterations = alterationRepository.getAlterations(character)
|
||||
characterSheetFactory.convert(
|
||||
sheet = it.getValue(key = character),
|
||||
alterations = alterations.toActiveStatus()
|
||||
)
|
||||
characterRepository.data
|
||||
.combine(alterationRepository.assignedAlterations) { sheets, _ -> sheets }
|
||||
.collect {
|
||||
_sheet.value = withContext(Dispatchers.Default) {
|
||||
val alterations =
|
||||
alterationRepository.getActiveAlterationsStatus(character)
|
||||
characterSheetFactory.convert(
|
||||
sheet = it.getValue(key = character),
|
||||
alterations = alterations,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ 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
|
||||
|
|
@ -30,6 +31,7 @@ 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.ddBorder
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
||||
@Stable
|
||||
|
|
@ -48,9 +50,12 @@ fun AlterationDetail(
|
|||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.padding(all = 24.dp)
|
||||
.padding(all = 16.dp)
|
||||
.ddBorder(
|
||||
inner = remember { RoundedCornerShape(size = 8.dp) },
|
||||
outline = remember { CutCornerShape(size = 16.dp) },
|
||||
)
|
||||
.then(other = modifier),
|
||||
shape = remember { RoundedCornerShape(size = 24.dp) },
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.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
|
||||
|
|
@ -38,6 +39,7 @@ data class RollAlterationUio(
|
|||
@Composable
|
||||
fun RollAlteration(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
alteration: RollAlterationUio,
|
||||
onInfo: (id: String) -> Unit,
|
||||
onClick: (id: String) -> Unit,
|
||||
|
|
@ -46,7 +48,7 @@ fun RollAlteration(
|
|||
modifier = Modifier
|
||||
.clickable { onClick(alteration.label) }
|
||||
.heightIn(min = 52.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(paddingValues = padding)
|
||||
.then(other = modifier),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ fun lightColorScheme(
|
|||
),
|
||||
placeholder: Color = Color(red = 230, green = 225, blue = 229),
|
||||
sheet: LexiconColors.CharacterSheet = LexiconColors.CharacterSheet(
|
||||
innerBorder = base.onSurface.copy(alpha = 0.35f),
|
||||
outlineBorder = base.onSurface.copy(alpha = 0.6f),
|
||||
innerBorder = base.onSurface.copy(alpha = 0.1f),
|
||||
outlineBorder = base.onSurface.copy(alpha = 0.3f),
|
||||
),
|
||||
) = colorScheme(
|
||||
base = base,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class LexiconTypography(
|
|||
val stamp: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = stampFontFamily,
|
||||
),
|
||||
val bodyDropCap: TextStyle = base.displaySmall.copy(
|
||||
val bodyDropCap: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = zallFontFamily,
|
||||
baselineShift = BaselineShift(-0.1f),
|
||||
letterSpacing = (-3).sp
|
||||
|
|
|
|||
|
|
@ -92,6 +92,10 @@ fun Modifier.ddBorder(
|
|||
color = colorScheme.characterSheet.outlineBorder,
|
||||
shape = outline,
|
||||
)
|
||||
.background(
|
||||
shape = outline,
|
||||
color = colorScheme.base.surfaceColorAtElevation(elevation.value)
|
||||
)
|
||||
.padding(
|
||||
horizontal = horizontalSpacing,
|
||||
vertical = verticalSpacing,
|
||||
|
|
|
|||
|
|
@ -13,18 +13,6 @@ fun List<Alteration>.toStatus(): Map<Property, List<Alteration.Status>> {
|
|||
return status
|
||||
}
|
||||
|
||||
fun List<Alteration>.toActiveStatus(): Map<Property, List<Alteration.Status>> {
|
||||
val status = hashMapOf<Property, MutableList<Alteration.Status>>()
|
||||
forEach { alteration ->
|
||||
if (alteration.active) {
|
||||
alteration.status.forEach {
|
||||
status.getOrPut(it.key) { mutableListOf() }.add(it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
val List<Alteration.Status>?.sum: Int
|
||||
get() = this?.sumOf { alt -> alt.bonus.sumOf { it.value } } ?: 0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
package com.pixelized.rplexicon.utilitary.extentions.local
|
||||
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.model.Counter
|
||||
import com.pixelized.rplexicon.model.CharacterSheetFire
|
||||
|
||||
fun CharacterSheet.spell(level: Int): Counter? = when (level) {
|
||||
fun CharacterSheet.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? = when (level) {
|
||||
1 -> spell1
|
||||
2 -> spell2
|
||||
3 -> spell3
|
||||
|
|
|
|||
4
app/src/main/res/drawable/ic_bloody_stash_24.xml
Normal file
4
app/src/main/res/drawable/ic_bloody_stash_24.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="24dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000" android:pathData="M244.19,21.97C347.89,119.18 428.1,216.27 494.72,304.06L494.72,191.66c-7.06,-6.39 -14.15,-12.68 -21.34,-18.84 2.99,-3.46 4.81,-7.97 4.81,-12.91 0,-10.93 -8.85,-19.78 -19.78,-19.78 -6.28,0 -11.88,2.96 -15.5,7.53 -43.25,-34.45 -88.29,-64.96 -134,-91.31 1.01,-2.03 1.59,-4.27 1.59,-6.69 0,-8.35 -6.77,-15.13 -15.13,-15.13 -5.42,0 -10.14,2.85 -12.81,7.13 -12.76,-6.88 -25.57,-13.45 -38.38,-19.69zM104.63,40.09c5.11,5.18 10.18,10.36 15.22,15.59 -4.22,1.17 -7.38,4.29 -8.35,9 -1.81,8.84 4.73,19.92 14.63,24.72 3.64,1.77 7.24,2.42 10.41,2.12 -2.54,4.11 -4,8.97 -4,14.16 0,14.89 12.05,26.94 26.94,26.94 8.68,0 16.41,-4.1 21.34,-10.47 88.69,101.2 165.6,208.4 235.16,306.72 -7.4,6.05 -12.13,15.23 -12.13,25.53 0,18.21 14.76,32.97 32.97,32.97 6.27,0 12.13,-1.77 17.12,-4.81 3.09,4.35 6.19,8.76 9.25,13.06h31.53L494.72,381.28c-41.36,-69.22 -154.77,-193.34 -184.53,-213.31 24.14,33.4 45.1,64.34 64.81,94.03 -87.49,-95.75 -183.44,-179.59 -270.38,-221.91zM76.4,55.34c-1.87,0.09 -3.74,0.55 -5.53,1.41 -7.18,3.43 -10.21,12.04 -6.78,19.22 3.43,7.18 12.04,10.21 19.22,6.78 7.18,-3.43 10.21,-12.04 6.78,-19.22 -2.57,-5.38 -8.08,-8.44 -13.69,-8.19zM432.15,75.72c-1.45,0.05 -2.86,0.36 -4.16,0.97 -6.92,3.26 -8.27,13.63 -3,23.16 5.26,9.53 15.14,14.63 22.06,11.38 6.92,-3.26 8.26,-13.63 3,-23.16 -4.28,-7.74 -11.6,-12.56 -17.91,-12.34zM281.03,110.97c-1.98,0.12 -3.85,0.72 -5.44,1.78 -6.37,4.24 -6.17,14.69 0.44,23.34 6.61,8.65 17.1,12.24 23.47,8 6.37,-4.24 6.17,-14.69 -0.44,-23.34 -4.95,-6.49 -12.08,-10.15 -18.03,-9.78zM25.19,149.28c91.02,100.04 158.7,190.4 212,271.6 -8.48,-1.36 -18.44,4.95 -20.25,13.59 -6.22,29.78 12.9,53.91 47.59,61.15L390.19,495.62c-97.97,-139.74 -234.78,-282.95 -365,-346.34zM55.22,251.13c-3.81,0.08 -7.2,1.33 -9.41,3.84 -5.05,5.74 -2.15,15.78 6.47,22.44 8.62,6.65 19.7,7.4 24.75,1.66 5.05,-5.74 2.18,-15.79 -6.44,-22.44 -4.85,-3.74 -10.48,-5.6 -15.38,-5.5zM102.41,289.25c-14.89,0 -26.94,12.05 -26.94,26.94 0,14.89 12.05,26.97 26.94,26.97 14.89,0 26.97,-12.08 26.97,-26.97 0,-14.89 -12.08,-26.94 -26.97,-26.94zM182.13,392.72c-10.93,0 -19.78,8.85 -19.78,19.78 0,10.93 8.85,19.78 19.78,19.78 10.93,0 19.78,-8.85 19.78,-19.78 0,-10.93 -8.85,-19.78 -19.78,-19.78z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_burning_passion_24.xml
Normal file
9
app/src/main/res/drawable/ic_burning_passion_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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>
|
||||
9
app/src/main/res/drawable/ic_embrassed_energy_24.xml
Normal file
9
app/src/main/res/drawable/ic_embrassed_energy_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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>
|
||||
9
app/src/main/res/drawable/ic_fist_24.xml
Normal file
9
app/src/main/res/drawable/ic_fist_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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>
|
||||
4
app/src/main/res/drawable/ic_layered_armor_24.xml
Normal file
4
app/src/main/res/drawable/ic_layered_armor_24.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="24dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#000000" android:pathData="M129.66,21.19L37.94,79.78c3.54,26.81 8.91,53.55 16.13,80.13L240.72,39.59l-19.28,-12.5c-31.28,-0.88 -62.2,-2.84 -91.78,-5.91zM383.13,21.81c-40.51,3.97 -83.5,5.94 -126.47,5.84l204.63,132.72c7.11,-25.89 12.49,-51.92 16.09,-78.03l-94.25,-60.53zM257.94,50.75L59.47,178.66c8.02,26.32 17.86,52.46 29.53,78.31l243.25,-158 -74.31,-48.22zM349.41,110.09l-74.56,48.44 151.28,98.78c11.71,-25.8 21.59,-51.91 29.69,-78.19l-106.41,-69.03zM257.72,169.66L97,274.06c12.2,25.17 26.14,50.06 41.84,74.56l196.09,-128.53 -77.22,-50.44zM352,231.22l-77.53,50.84 101.4,67.19c15.82,-24.6 29.9,-49.58 42.22,-74.88L352,231.22zM257.47,293.19l-108.35,71.03c13.56,20.06 28.33,39.85 44.28,59.31l132.03,-85.28 -67.97,-45.06zM342.44,349.5L274.5,393.41l47.03,30.38c15.85,-19.34 30.51,-38.99 44.03,-58.94L342.44,349.5zM257.47,404.38L205.5,437.97c16.23,18.93 33.61,37.54 52.16,55.78 18.39,-18.15 35.64,-36.68 51.78,-55.53l-52.09,-33.63 0.13,-0.22z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_lyre_24.xml
Normal file
9
app/src/main/res/drawable/ic_lyre_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<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>
|
||||
|
|
@ -154,4 +154,10 @@
|
|||
|
||||
<string name="alteration_source">Source : %1$s</string>
|
||||
<string name="alteration_target">Cible : %1$s</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>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="google_spreadsheet_api_key">AIzaSyBmagVOEyB68tTJ5QMFMzMQZIHG_4XVCOo</string>
|
||||
<string name="google_sign_in_id" translatable="false">62913404482-ergqkjiuvint49q8lm555j21vvb6af7s.apps.googleusercontent.com</string>
|
||||
<string name="google_spreadsheet_api_key" translatable="false">AIzaSyBmagVOEyB68tTJ5QMFMzMQZIHG_4XVCOo</string>
|
||||
<string name="firebase_realtime_database" translatable="false">https://rp-lexicon-default-rtdb.europe-west1.firebasedatabase.app/</string>
|
||||
</resources>
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
<string name="map_label">Coordinates</string>
|
||||
|
||||
<string name="character_sheet_title">Character sheet</string>
|
||||
<string name="character_sheet_title_hp">HP</string>
|
||||
<string name="character_sheet_title_hp">Hit Point</string>
|
||||
<string name="character_sheet_title_ca">CA</string>
|
||||
<string name="character_sheet_title_dc">DC</string>
|
||||
<string name="character_sheet_title_speed">Speed</string>
|
||||
|
|
@ -154,4 +154,10 @@
|
|||
|
||||
<string name="alteration_source">Source: %1$s</string>
|
||||
<string name="alteration_target">Target: %1$s</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