Add realtime database to the APP.
This commit is contained in:
parent
e2209bf005
commit
ee81f9082d
50 changed files with 1478 additions and 342 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue