Add dice roll sharing

This commit is contained in:
Thomas Andres Gomez 2023-12-22 16:08:16 +01:00
parent 41a094009d
commit 980a7e7929
37 changed files with 745 additions and 327 deletions

View file

@ -35,4 +35,4 @@
# This rule will properly ProGuard all the model classes in
# the package com.yourcompany.models.
# Modify this rule to fit the structure of your app.
-keepclassmembers class com.pixelized.rplexicon.data.model.CharacterSheetFire.** { *; }
-keepclassmembers class com.pixelized.rplexicon.data.network.CharacterSheetFire.** { *; }

View file

@ -10,6 +10,8 @@ import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.Skill
import com.pixelized.rplexicon.data.model.Throw
import com.pixelized.rplexicon.data.network.NetworkThrow
import com.pixelized.rplexicon.data.network.NetworkThrow.Bonus
import com.pixelized.rplexicon.data.repository.character.ActionRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
@ -38,6 +40,7 @@ import kotlin.math.min
data class DiceThrowResult(
val dice: RollDiceUio,
val card: ThrowsCardUio,
val network: NetworkThrow,
)
@Suppress("KotlinConstantConditions")
@ -664,7 +667,19 @@ class DiceThrowUseCase @Inject constructor(
result = "${result.value}",
),
) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + mastery,
)
),
network = NetworkThrow(
timestamp = System.currentTimeMillis(),
title = abilityTitleString.uppercase(),
result = rollResult,
isCriticalSuccess = if (result.value == 20) true else null,
isCriticalFailure = if (result.value == 1) true else null,
details = bonus(abilityTitleString, "1d20", result.label) +
diceAlterationBonus.detail() +
flatAlterationBonus.detail() +
relatedStatBonus.detail() +
mastery.detail()
),
)
}
}
@ -771,7 +786,19 @@ class DiceThrowUseCase @Inject constructor(
result = "${result.value}",
),
) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + flatBonus,
)
),
network = NetworkThrow(
timestamp = System.currentTimeMillis(),
title = titleString.uppercase(),
result = rollResult,
isCriticalSuccess = if (canMakeCriticalRoll && result.value == 20) true else null,
isCriticalFailure = if (canMakeCriticalRoll && result.value == 1) true else null,
details = bonus(
title = titleString,
dice = "${diceThrow?.amount}d${diceThrow?.faces}",
result = result.label
) + diceAlterationBonus.detail() + flatAlterationBonus.detail() + relatedStatBonus.detail() + flatBonus.detail()
),
)
}
}
@ -857,7 +884,17 @@ class DiceThrowUseCase @Inject constructor(
result = "${result.value}",
),
) + levelBonus + relatedStatBonus,
)
),
network = NetworkThrow(
timestamp = System.currentTimeMillis(),
title = titleString.uppercase(),
result = rollResult,
details = bonus(
title = titleString,
dice = "${spell?.effect?.amount}d${spell?.effect?.faces}",
result = result.label
) + levelBonus.detail() + relatedStatBonus.detail()
),
)
}
}
@ -904,13 +941,13 @@ class DiceThrowUseCase @Inject constructor(
),
card = ThrowsCardUio(
title = titleString.uppercase(),
highlight = spellName,
highlight = spellName ?: "",
dice = (skill?.effect?.faces ?: 4).icon,
roll = allValue.toLabel(),
result = rollResult,
details = listOf(
ThrowsCardUio.Detail(
title = spellName,
title = spellName ?: "",
throws = ThrowsCardUio.Throw(
dice = (skill?.effect?.faces ?: 4).icon,
roll = "${skill?.effect?.amount ?: 1}d${skill?.effect?.faces ?: 4}",
@ -921,11 +958,27 @@ class DiceThrowUseCase @Inject constructor(
result = "${result.value}",
),
) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus,
)
),
network = NetworkThrow(
timestamp = System.currentTimeMillis(),
title = titleString.uppercase(),
result = rollResult,
details = bonus(
title = titleString,
dice = "${skill?.effect?.amount}d${skill?.effect?.faces}",
result = result.label
) + diceAlterationBonus.detail() + flatAlterationBonus.detail() + relatedStatBonus.detail()
),
)
}
}
private fun bonus(title: String, dice: String, result: String): List<Bonus> =
listOf(Bonus(title, dice, result))
private fun List<ThrowsCardUio.Detail>.detail(): List<Bonus> =
map { Bonus(it.title, it.throws?.roll, it.result) }
/**
* Helper class to track rolls and declare helper method.
*/
@ -994,7 +1047,7 @@ class DiceThrowUseCase @Inject constructor(
critical = status.critical,
)
ThrowsCardUio.Detail(
title = dice.title,
title = dice.title ?: "",
throws = ThrowsCardUio.Throw(
dice = dice.faces.icon,
advantage = dice.advantage,

View file

@ -2,7 +2,7 @@ package com.pixelized.rplexicon.business
import com.pixelized.rplexicon.data.model.AssignedSpell
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.network.CharacterSheetFire
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.factory.SpellUioFactory

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.data.model
package com.pixelized.rplexicon.data.network
import androidx.annotation.Keep
import com.google.firebase.database.IgnoreExtraProperties

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.data.model
package com.pixelized.rplexicon.data.network
import androidx.annotation.Keep
import com.google.firebase.database.IgnoreExtraProperties

View file

@ -0,0 +1,64 @@
package com.pixelized.rplexicon.data.network
import androidx.annotation.Keep
import com.google.firebase.database.IgnoreExtraProperties
import com.google.firebase.database.PropertyName
@Keep
@IgnoreExtraProperties
data class NetworkThrow(
@get:PropertyName(TIMESTAMP)
@set:PropertyName(TIMESTAMP)
var timestamp: Long = 0L,
@get:PropertyName(TITLE)
@set:PropertyName(TITLE)
var title: String = "",
@get:PropertyName(RESULT)
@set:PropertyName(RESULT)
var result: String = "",
@get:PropertyName(CRITICAL)
@set:PropertyName(CRITICAL)
var isCriticalSuccess: Boolean? = null,
@get:PropertyName(FAIL)
@set:PropertyName(FAIL)
var isCriticalFailure: Boolean? = null,
@get:PropertyName(DETAILS)
@set:PropertyName(DETAILS)
var details: List<Bonus> = emptyList(),
) {
@Keep
@IgnoreExtraProperties
data class Bonus(
@get:PropertyName(LABEL)
@set:PropertyName(LABEL)
var label: String = "",
@get:PropertyName(DICE)
@set:PropertyName(DICE)
var dice: String? = null,
@get:PropertyName(RESULT)
@set:PropertyName(RESULT)
var result: String = "",
) {
companion object {
const val LABEL = "label"
const val DICE = "dice"
const val RESULT = "result"
}
}
companion object {
const val TIMESTAMP = "timestamp"
const val TITLE = "title"
const val RESULT = "result"
const val CRITICAL = "crit"
const val FAIL = "fail"
const val DETAILS = "details"
}
}

View file

@ -0,0 +1,17 @@
package com.pixelized.rplexicon.data.network
import androidx.annotation.Keep
import com.google.firebase.database.IgnoreExtraProperties
import com.google.firebase.database.PropertyName
@Keep
@IgnoreExtraProperties
data class NetworkThrowMap(
@get:PropertyName(CHARACTERS_THROWS)
@set:PropertyName(CHARACTERS_THROWS)
var details: Map<String, NetworkThrow> = emptyMap(),
) {
companion object {
const val CHARACTERS_THROWS = "CharactersThrows"
}
}

View file

@ -8,8 +8,11 @@ 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.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.model.CharacterSheetFireMap
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.network.CharacterSheetFireMap
import com.pixelized.rplexicon.data.network.NetworkThrow
import com.pixelized.rplexicon.data.network.NetworkThrowMap
import com.pixelized.rplexicon.data.network.NetworkThrowMap.Companion.CHARACTERS_THROWS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -167,8 +170,46 @@ class FirebaseRepository @Inject constructor(
reference.setValue(value)
}
fun sendThrow(character: String?, throws: NetworkThrow) {
character?.let {
val reference = database.getReference("$PATH_THROWS/")
reference.updateChildren(mapOf(it to throws))
}
}
fun getThrows(): Flow<NetworkThrowMap> {
return callbackFlow {
// reference to the node
val reference = database.getReference("/")
// build a register the callback
val listener = reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val value = try {
dataSnapshot.getValue(NetworkThrowMap::class.java)
} catch (exception: Exception) {
Log.e(TAG, "Failed to parse value.", exception)
_error.tryEmit(exception)
null
}
if (value != null) {
trySend(value)
}
}
override fun onCancelled(error: DatabaseError) {
Log.e(TAG, "Failed to read value.", error.toException())
cancel()
}
})
awaitClose {
reference.removeEventListener(listener)
}
}
}
companion object {
private const val TAG = "FirebaseRepository"
private const val PATH_CHARACTERS = "Characters"
private const val PATH_THROWS = CHARACTERS_THROWS
}
}

View file

@ -2,15 +2,11 @@ package com.pixelized.rplexicon.ui.screens.character.factory
import com.pixelized.rplexicon.data.model.AssignedSpell
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio
import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio
import com.pixelized.rplexicon.utilitary.extentions.icon
import com.pixelized.rplexicon.utilitary.extentions.local.firstSpellSlot
import com.pixelized.rplexicon.utilitary.extentions.local.icon
import com.pixelized.rplexicon.utilitary.extentions.local.spell
import com.pixelized.rplexicon.utilitary.extentions.modifier
import javax.inject.Inject

View file

@ -7,7 +7,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.Skill

View file

@ -10,7 +10,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.business.SpellBookUseCase
import com.pixelized.rplexicon.data.model.AssignedSpell
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.Throw

View file

@ -287,9 +287,9 @@ private fun RollOverlayContent(
)
else -> ThrowsCard(
modifier = Modifier.padding(
bottom = if (enableDrawer.value) 32.dp else 0.dp
),
modifier = Modifier
.padding(bottom = if (enableDrawer.value) 32.dp else 0.dp)
.padding(all = 16.dp),
throws = it,
showDetail = showDetail,
onClick = onCard,

View file

@ -10,6 +10,7 @@ import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.business.DiceThrowUseCase
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
@ -36,6 +37,7 @@ class RollOverlayViewModel @Inject constructor(
private val rollUseCase: DiceThrowUseCase,
private val diceFactory: DiceFactory,
private val alterationFactory: AlterationFactory,
private val firebaseRepository: FirebaseRepository,
application: Application,
) : AndroidViewModel(application) {
private var sheet: CharacterSheet? = null
@ -88,13 +90,19 @@ class RollOverlayViewModel @Inject constructor(
diceThrow?.let { diceThrow ->
rollJob?.cancel()
rollJob = viewModelScope.launch {
// roll the dice ;)
val result = rollUseCase.roll(
diceThrow = diceThrow,
alterationId = _alterations.value.mapNotNull { if (it.checked) it.label else null },
)
// share the result
result?.network?.let {
firebaseRepository.sendThrow(sheet?.name, it)
}
// Start the roll animation.
_dice.value = _dice.value?.let {
it.copy(
result = "",
animationSpec = RollDiceUio.TWEEN,
rotation = it.rotation + 5f,
)

View file

@ -55,7 +55,7 @@ import java.util.UUID
@Stable
data class ThrowsCardUio(
val id: String = UUID.randomUUID().toString(),
val title: String?, // the roll title
val title: String, // the roll title
val highlight: String?, // the highlighted part of the title
@DrawableRes val dice: Int?, // the dice icon
val roll: String, // the roll + bonus string.
@ -66,7 +66,7 @@ data class ThrowsCardUio(
) {
@Stable
data class Detail(
val title: String? = null, // the detail title
val title: String = "", // the detail title
val throws: Throw? = null, // the detail of the roll if any
val result: String, // the result
)
@ -104,7 +104,6 @@ fun ThrowsCard(
Surface(
modifier = modifier
.fillMaxWidth()
.padding(all = 16.dp)
.ddBorder(inner = inner, outline = remember { CutCornerShape(size = 16.dp) })
.clip(shape = inner)
.clickable(onClick = onClick),

View file

@ -4,8 +4,9 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.network.NetworkThrow
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
@ -21,6 +22,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.label
import com.pixelized.rplexicon.ui.screens.summary.composable.common.maxLabel
import com.pixelized.rplexicon.ui.screens.summary.composable.common.none
import com.pixelized.rplexicon.ui.screens.summary.composable.common.proficiency
import com.pixelized.rplexicon.ui.screens.summary.pages.statistic.StatisticSummaryUio
import com.pixelized.rplexicon.utilitary.extentions.local.highestSpellLevel
import com.pixelized.rplexicon.utilitary.extentions.local.mastery
import com.pixelized.rplexicon.utilitary.extentions.local.passivesBonus
@ -45,7 +47,7 @@ class SummaryFactory @Inject constructor(
private val alterationRepository: AlterationRepository,
private val firebaseRepository: FirebaseRepository,
) {
fun fetchSummary(scope: CoroutineScope): SummaryUio {
fun fetchSummary(scope: CoroutineScope): StatisticSummaryUio {
val headerVisibility = mutableStateOf(false)
val statsVisibility = mutableStateOf(false)
val characteristicsVisibility = mutableStateOf(false)
@ -55,13 +57,18 @@ class SummaryFactory @Inject constructor(
val spellsVisibility = mutableStateOf(false)
val header = ClassHeaderSummaryUio(
c1 = mutableStateOf(ClassHeaderSummaryUio.Header()),
c2 = mutableStateOf(ClassHeaderSummaryUio.Header()),
c3 = mutableStateOf(ClassHeaderSummaryUio.Header()),
c4 = mutableStateOf(ClassHeaderSummaryUio.Header()),
c5 = mutableStateOf(ClassHeaderSummaryUio.Header()),
clazz1 = mutableStateOf(ClassHeaderSummaryUio.Header()),
dice1 = mutableStateOf(ClassHeaderSummaryUio.Dice()),
clazz2 = mutableStateOf(ClassHeaderSummaryUio.Header()),
dice2 = mutableStateOf(ClassHeaderSummaryUio.Dice()),
clazz3 = mutableStateOf(ClassHeaderSummaryUio.Header()),
dice3 = mutableStateOf(ClassHeaderSummaryUio.Dice()),
clazz4 = mutableStateOf(ClassHeaderSummaryUio.Header()),
dice4 = mutableStateOf(ClassHeaderSummaryUio.Dice()),
clazz5 = mutableStateOf(ClassHeaderSummaryUio.Header()),
dice5 = mutableStateOf(ClassHeaderSummaryUio.Dice()),
)
val stats = AttributesSummaryUio(
val attributes = AttributesSummaryUio(
hp = SummaryRowUio(label = R.string.character_sheet_title_hp),
ac = SummaryRowUio(label = R.string.character_sheet_title_ca),
dc = SummaryRowUio(label = R.string.character_sheet_title_dc),
@ -143,24 +150,12 @@ class SummaryFactory @Inject constructor(
maxSpellSlot.intValue = highestSpellSlot
}
// extension function to root a character to a column property.
fun ClassHeaderSummaryUio.get(sheet: CharacterSheet?) = when (sheet) {
sheets[characters.getOrNull(0)] -> c1
sheets[characters.getOrNull(1)] -> c2
sheets[characters.getOrNull(2)] -> c3
sheets[characters.getOrNull(3)] -> c4
sheets[characters.getOrNull(4)] -> c5
else -> null
}
// extension functions to root a character to a column property.
fun SummaryRowUio.get(sheet: CharacterSheet?) =
get(index = characters.indexOf(sheet?.name))
fun SummaryRowUio.get(sheet: CharacterSheet?) = when (sheet) {
sheets[characters.getOrNull(0)] -> c1
sheets[characters.getOrNull(1)] -> c2
sheets[characters.getOrNull(2)] -> c3
sheets[characters.getOrNull(3)] -> c4
sheets[characters.getOrNull(4)] -> c5
else -> null
}
fun ClassHeaderSummaryUio.getClass(sheet: CharacterSheet?) =
classes.getOrNull(index = characters.indexOf(sheet?.name))
// Update the header
sheets.values.forEach { sheet ->
@ -169,7 +164,7 @@ class SummaryFactory @Inject constructor(
icon = sheet.characterClass.firstOrNull()?.icon ?: R.drawable.ic_d20_24,
)
withContext(Dispatchers.Main) {
header.get(sheet)?.value = clazz
header.getClass(sheet)?.value = clazz
}
}
withContext(Dispatchers.Main) {
@ -206,11 +201,11 @@ class SummaryFactory @Inject constructor(
label = "${sheet.speed}m",
)
withContext(Dispatchers.Main) {
stats.hp.get(sheet)?.value = hitPoint
stats.ac.get(sheet)?.value = armorClass
stats.dc.get(sheet)?.value = dC
stats.initiative.get(sheet)?.value = initiative
stats.speed.get(sheet)?.value = speed
attributes.hp.get(sheet)?.value = hitPoint
attributes.ac.get(sheet)?.value = armorClass
attributes.dc.get(sheet)?.value = dC
attributes.initiative.get(sheet)?.value = initiative
attributes.speed.get(sheet)?.value = speed
}
}
withContext(Dispatchers.Main) {
@ -609,11 +604,36 @@ class SummaryFactory @Inject constructor(
}
}
return SummaryUio(
scope.launch(Dispatchers.IO) {
val data = HeaderStatStruct()
characterSheetRepository.data
.combine(firebaseRepository.getThrows()) { sheets, fire ->
data.sheets = sheets
data.fires = fire.details
}
.collect {
val (sheets, throws) = data
val characters = sheets.keys.sorted()
fun ClassHeaderSummaryUio.getDice(name: String?) =
dices.getOrNull(index = characters.indexOf(name))
throws.forEach { entry ->
header.getDice(name = entry.key)?.value = ClassHeaderSummaryUio.Dice(
timestamp = entry.value.timestamp,
result = entry.value.result,
isCriticalSuccess = entry.value.isCriticalSuccess ?: false,
isCriticalFailure = entry.value.isCriticalFailure ?: false,
)
}
}
}
return StatisticSummaryUio(
headerVisibility = headerVisibility,
header = header,
statsVisibility = statsVisibility,
stats = stats,
attributes = attributes,
characteristicsVisibility = characteristicsVisibility,
characteristics = characteristics,
savingThrowsVisibility = savingThrowsVisibility,
@ -634,4 +654,12 @@ class SummaryFactory @Inject constructor(
operator fun component1() = sheets
operator fun component2() = fires
}
private class HeaderStatStruct {
lateinit var sheets: Map<String, CharacterSheet>
lateinit var fires: Map<String, NetworkThrow>
operator fun component1() = sheets
operator fun component2() = fires
}
}

View file

@ -1,22 +1,18 @@
package com.pixelized.rplexicon.ui.screens.summary
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -24,73 +20,33 @@ import androidx.compose.material3.Scaffold
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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.KeepOnScreen
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet
import com.pixelized.rplexicon.ui.screens.summary.composable.AttributesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.AttributesSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.CharacteristicsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.CharacteristicsSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.ClassHeaderSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.ClassHeaderSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.PassivesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.PassivesSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.ProficiencySummary
import com.pixelized.rplexicon.ui.screens.summary.composable.ProficiencySummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.SavingThrowsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.SavingThrowsSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.SpellSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.SpellSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberCharacteristicsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberClassHeaderSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberPassivesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberProficienciesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberSavingThrowsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberSpellsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberStatsSummary
import com.pixelized.rplexicon.ui.screens.summary.pages.statistic.StatisticSummary
import com.pixelized.rplexicon.ui.screens.summary.pages.statistic.StatisticSummaryPreview
import com.pixelized.rplexicon.ui.screens.summary.pages.statistic.StatisticViewModel
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
data class SummaryUio(
val headerVisibility: State<Boolean>,
val header: ClassHeaderSummaryUio,
val statsVisibility: State<Boolean>,
val stats: AttributesSummaryUio,
val characteristicsVisibility: State<Boolean>,
val characteristics: CharacteristicsSummaryUio,
val savingThrowsVisibility: State<Boolean>,
val savingThrows: SavingThrowsSummaryUio,
val proficienciesVisibility: State<Boolean>,
val proficiencies: ProficiencySummaryUio,
val passivesVisibility: State<Boolean>,
val passives: PassivesSummaryUio,
val spellsVisibility: State<Boolean>,
val spells: SpellSummaryUio,
)
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
fun SummaryScreen(
viewModel: SummaryViewModel = hiltViewModel(),
statisticsViewModel: StatisticViewModel = hiltViewModel()
) {
val screen = LocalScreenNavHost.current
val refresh = rememberPullRefreshState(
refreshing = false,
onRefresh = { },
)
Surface(
modifier = Modifier.fillMaxSize(),
@ -98,27 +54,33 @@ fun SummaryScreen(
SummaryContent(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
summary = viewModel.summary,
.statusBarsPadding(),
refreshState = refresh,
onBack = {
screen.popBackStack()
},
onHeader = {
screen.navigateToCharacterSheet(name = it.label)
}
statistics = {
StatisticSummary(
summary = statisticsViewModel.summary,
onClass = {
screen.navigateToCharacterSheet(name = it.label)
},
onDice = { },
)
},
)
KeepOnScreen()
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun SummaryContent(
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
summary: SummaryUio,
refreshState: PullRefreshState,
onBack: () -> Unit,
onHeader: (ClassHeaderSummaryUio.Header) -> Unit,
statistics: @Composable () -> Unit,
) {
Scaffold(
modifier = modifier,
@ -139,7 +101,7 @@ private fun SummaryContent(
)
}
Text(
text = stringResource(id = R.string.character_sheet_summary_title),
text = stringResource(id = R.string.summary_title),
style = MaterialTheme.typography.titleLarge,
)
}
@ -148,140 +110,26 @@ private fun SummaryContent(
) { paddingValues ->
Column(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(paddingValues = paddingValues)
.padding(horizontal = 16.dp),
.fillMaxSize()
.pullRefresh(refreshState)
.padding(paddingValues = paddingValues),
) {
AnimatedVisibility(
modifier = Modifier
.zIndex(zIndex = 1f)
.fillMaxWidth()
.offset { IntOffset(x = 0, y = scrollState.value) }
.background(brush = rememberHeaderBackgroundGradient())
.padding(bottom = 16.dp, end = 3.dp),
visible = summary.headerVisibility.value,
enter = fadeIn(),
) {
ClassHeaderSummary(
header = summary.header,
onClick = onHeader,
)
}
Column(
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
AnimatedVisibility(
visible = summary.statsVisibility.value,
enter = enterTransition(),
) {
AttributesSummary(
attributes = summary.stats,
)
}
AnimatedVisibility(
visible = summary.characteristicsVisibility.value,
enter = enterTransition(),
) {
CharacteristicsSummary(
characteristics = summary.characteristics,
)
}
AnimatedVisibility(
visible = summary.savingThrowsVisibility.value,
enter = enterTransition(),
) {
SavingThrowsSummary(
savingThrows = summary.savingThrows,
)
}
AnimatedVisibility(
visible = summary.proficienciesVisibility.value,
enter = enterTransition(),
) {
ProficiencySummary(
proficiencies = summary.proficiencies,
)
}
AnimatedVisibility(
visible = summary.passivesVisibility.value,
enter = enterTransition(),
) {
PassivesSummary(
passives = summary.passives,
)
}
AnimatedVisibility(
visible = summary.spellsVisibility.value,
enter = enterTransition(),
) {
SpellSummary(
spells = summary.spells,
)
}
}
statistics()
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun enterTransition(
density: Density = LocalDensity.current,
): EnterTransition {
return fadeIn() + slideInVertically { with(density) { 24.dp.roundToPx() } }
}
@Composable
private fun rememberHeaderBackgroundGradient(): Brush {
val colorScheme = MaterialTheme.colorScheme
return remember {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = 1.0f),
colorScheme.surface.copy(alpha = 1.0f),
colorScheme.surface.copy(alpha = 0.5f),
colorScheme.surface.copy(alpha = 0.0f),
)
)
}
}
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, heightDp = 1680)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, heightDp = 1680)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun SummaryPreview() {
LexiconTheme {
Surface {
val header = rememberClassHeaderSummary()
val stats = rememberStatsSummary()
val characteristics = rememberCharacteristicsSummary()
val savingThrows = rememberSavingThrowsSummary()
val proficiencies = rememberProficienciesSummary()
val passives = rememberPassivesSummary()
val spells = rememberSpellsSummary()
SummaryContent(
modifier = Modifier.fillMaxSize(),
summary = remember {
SummaryUio(
headerVisibility = mutableStateOf(true),
header = header,
statsVisibility = mutableStateOf(true),
stats = stats,
characteristicsVisibility = mutableStateOf(true),
characteristics = characteristics,
savingThrowsVisibility = mutableStateOf(true),
savingThrows = savingThrows,
proficienciesVisibility = mutableStateOf(true),
proficiencies = proficiencies,
passivesVisibility = mutableStateOf(true),
passives = passives,
spellsVisibility = mutableStateOf(true),
spells = spells,
)
},
refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }),
onBack = { },
onHeader = { },
statistics = { StatisticSummaryPreview() },
)
}
}

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberStatsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberAttributesSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder
@ -78,7 +78,7 @@ private fun StatsSummaryPreview() {
LexiconTheme {
Surface {
AttributesSummary(
attributes = rememberStatsSummary()
attributes = rememberAttributesSummary()
)
}
}

View file

@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberCharacteristicsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberCharacteristicsSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder

View file

@ -2,106 +2,210 @@ package com.pixelized.rplexicon.ui.screens.summary.composable
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberClassHeaderSummary
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberClassHeaderSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class ClassHeaderSummaryUio(
val c1: MutableState<Header>,
val c2: MutableState<Header>,
val c3: MutableState<Header>,
val c4: MutableState<Header>,
val c5: MutableState<Header>,
val clazz1: MutableState<Header>,
val dice1: MutableState<Dice>,
val clazz2: MutableState<Header>,
val dice2: MutableState<Dice>,
val clazz3: MutableState<Header>,
val dice3: MutableState<Dice>,
val clazz4: MutableState<Header>,
val dice4: MutableState<Dice>,
val clazz5: MutableState<Header>,
val dice5: MutableState<Dice>,
) {
val classes = arrayListOf(clazz1, clazz2, clazz3, clazz4, clazz5)
val dices = arrayListOf(dice1, dice2, dice3, dice4, dice5)
@Stable
data class Header(
val label: String = "",
@DrawableRes val icon: Int = R.drawable.ic_d20_24,
)
@Stable
data class Dice(
val timestamp: Long = 0L,
@DrawableRes val icon: Int = R.drawable.ic_d20_24,
val isCriticalSuccess: Boolean = false,
val isCriticalFailure: Boolean = false,
val result: String = "",
)
}
@Composable
fun ClassHeaderSummary(
modifier: Modifier = Modifier,
header: ClassHeaderSummaryUio,
onClick: (ClassHeaderSummaryUio.Header) -> Unit
onClass: (ClassHeaderSummaryUio.Header) -> Unit,
onDice: (ClassHeaderSummaryUio.Dice) -> Unit,
) {
Row(
Column(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(space = 1.dp, alignment = Alignment.End),
verticalAlignment = Alignment.CenterVertically,
horizontalAlignment = Alignment.End,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(space = 1.dp),
verticalAlignment = Alignment.CenterVertically,
) {
header.classes.forEach {
IconButton(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
onClick = { onClass(it.value) },
) {
Icon(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
painter = painterResource(id = it.value.icon),
contentDescription = null,
)
}
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(space = 1.dp, alignment = Alignment.End),
verticalAlignment = Alignment.CenterVertically,
) {
header.dices.forEach {
ClassHeaderDice(
dice = it,
onDice = onDice,
)
}
}
}
}
@Composable
private fun ClassHeaderDice(
modifier: Modifier = Modifier,
dice: State<ClassHeaderSummaryUio.Dice>,
onDice: (ClassHeaderSummaryUio.Dice) -> Unit
) {
val typography = MaterialTheme.typography
val colorScheme = MaterialTheme.colorScheme
val style = remember {
typography.headlineLarge.copy(
shadow = Shadow(
color = colorScheme.surface,
offset = Offset.Zero,
blurRadius = 10f,
),
)
}
val textColor = animateColorAsState(
targetValue = when {
dice.value.isCriticalSuccess -> MaterialTheme.colorScheme.primary
dice.value.isCriticalFailure -> Color.Red
else -> MaterialTheme.colorScheme.onSurface
},
animationSpec = tween(
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
),
label = "ClassHeaderDiceColorAnimation"
)
val iconColor = remember {
derivedStateOf {
textColor.value.copy(alpha = 0.5f)
}
}
Box(
modifier = modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
contentAlignment = Alignment.Center,
) {
IconButton(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
onClick = { onClick(header.c1.value) },
onClick = { onDice(dice.value) },
) {
Icon(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
painter = painterResource(id = header.c1.value.icon),
modifier = Modifier.size(MaterialTheme.lexicon.dimens.summary.cell),
painter = painterResource(id = dice.value.icon),
tint = iconColor.value,
contentDescription = null,
)
}
IconButton(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
onClick = { onClick(header.c2.value) },
AnimatedContent(
targetState = dice.value,
label = "ClassHeaderDiceValueAnimation",
transitionSpec = labelAnimationSpec()
) {
Icon(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
painter = painterResource(id = header.c2.value.icon),
contentDescription = null,
)
}
IconButton(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
onClick = { onClick(header.c3.value) },
) {
Icon(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
painter = painterResource(id = header.c3.value.icon),
contentDescription = null,
)
}
IconButton(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
onClick = { onClick(header.c4.value) },
) {
Icon(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
painter = painterResource(id = header.c4.value.icon),
contentDescription = null,
)
}
IconButton(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
onClick = { onClick(header.c5.value) },
) {
Icon(
modifier = Modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
painter = painterResource(id = header.c5.value.icon),
contentDescription = null,
Text(
modifier = modifier.width(MaterialTheme.lexicon.dimens.summary.cell.width),
style = style,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = textColor.value,
text = it.result
)
}
}
}
@Stable
private fun labelAnimationSpec(): AnimatedContentTransitionScope<*>.() -> ContentTransform = {
val slideIn = slideInVertically(
animationSpec = tween(
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
)
) { it / 2 }
val fadeIn = fadeIn(
animationSpec = tween(
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
)
)
val slideOut = slideOutVertically(
animationSpec = tween()
) { -it / 2 }
val fadeOut = fadeOut(
animationSpec = tween()
)
slideIn + fadeIn togetherWith slideOut + fadeOut using SizeTransform(clip = false)
}
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -111,7 +215,8 @@ private fun ClassHeaderSummaryPreview() {
ClassHeaderSummary(
modifier = Modifier.fillMaxWidth(),
header = rememberClassHeaderSummary(),
onClick = { }
onClass = { },
onDice = { },
)
}
}

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberPassivesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberPassivesSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberProficienciesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberProficienciesSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder

View file

@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberSavingThrowsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberSavingThrowsSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder

View file

@ -21,7 +21,7 @@ import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRow
import com.pixelized.rplexicon.ui.screens.summary.composable.common.SummaryRowUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.rememberSpellsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberSpellsSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.ddBorder

View file

@ -35,7 +35,18 @@ data class SummaryRowUio(
val c3: MutableState<SummaryCellUio> = mutableStateOf(none()),
val c4: MutableState<SummaryCellUio> = mutableStateOf(none()),
val c5: MutableState<SummaryCellUio> = mutableStateOf(none()),
)
) {
operator fun get(index: Int): MutableState<SummaryCellUio>? {
return when (index) {
0 -> c1
1 -> c2
2 -> c3
3 -> c4
4 -> c5
else -> null
}
}
}
@Composable
fun SummaryRow(

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@ -11,7 +11,7 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.common.label
@Composable
@Stable
fun rememberStatsSummary(): AttributesSummaryUio {
fun rememberAttributesSummary(): AttributesSummaryUio {
return remember {
AttributesSummaryUio(
hp = SummaryRowUio(

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@ -12,36 +12,53 @@ import com.pixelized.rplexicon.ui.screens.summary.composable.ClassHeaderSummaryU
fun rememberClassHeaderSummary(): ClassHeaderSummaryUio {
return remember {
ClassHeaderSummaryUio(
c1 = mutableStateOf(
clazz1 = mutableStateOf(
ClassHeaderSummaryUio.Header(
label = "Brulkai",
icon = R.drawable.ic_class_barbarian_24,
)
),
c2 = mutableStateOf(
dice1 = mutableStateOf(
ClassHeaderSummaryUio.Dice(
result = "20",
)
),
clazz2 = mutableStateOf(
ClassHeaderSummaryUio.Header(
label = "Léandre",
icon = R.drawable.ic_class_cleric_24,
)
),
c3 = mutableStateOf(
dice2 = mutableStateOf(
ClassHeaderSummaryUio.Dice()
),
clazz3 = mutableStateOf(
ClassHeaderSummaryUio.Header(
label = "Nélia",
icon = R.drawable.ic_class_ranger_24,
)
),
c4 = mutableStateOf(
dice3 = mutableStateOf(
ClassHeaderSummaryUio.Dice()
),
clazz4 = mutableStateOf(
ClassHeaderSummaryUio.Header(
label = "Tigrane",
icon = R.drawable.ic_class_warlock_24,
)
),
c5 = mutableStateOf(
dice4 = mutableStateOf(
ClassHeaderSummaryUio.Dice()
),
clazz5 = mutableStateOf(
ClassHeaderSummaryUio.Header(
label = "Unathana",
icon = R.drawable.ic_class_bard_24,
)
),
dice5 = mutableStateOf(
ClassHeaderSummaryUio.Dice()
),
)
}
}

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable

View file

@ -0,0 +1,38 @@
package com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.pixelized.rplexicon.ui.screens.summary.pages.statistic.StatisticSummaryUio
@Composable
@Stable
fun rememberStatisticSummary(): StatisticSummaryUio {
val header = rememberClassHeaderSummary()
val attributes = rememberAttributesSummary()
val characteristics = rememberCharacteristicsSummary()
val savingThrows = rememberSavingThrowsSummary()
val proficiencies = rememberProficienciesSummary()
val passives = rememberPassivesSummary()
val spells = rememberSpellsSummary()
return remember {
StatisticSummaryUio(
headerVisibility = mutableStateOf(true),
header = header,
statsVisibility = mutableStateOf(true),
attributes = attributes,
characteristicsVisibility = mutableStateOf(true),
characteristics = characteristics,
savingThrowsVisibility = mutableStateOf(true),
savingThrows = savingThrows,
proficienciesVisibility = mutableStateOf(true),
proficiencies = proficiencies,
passivesVisibility = mutableStateOf(true),
passives = passives,
spellsVisibility = mutableStateOf(true),
spells = spells,
)
}
}

View file

@ -0,0 +1,189 @@
package com.pixelized.rplexicon.ui.screens.summary.pages.statistic
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.pixelized.rplexicon.ui.screens.summary.composable.AttributesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.AttributesSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.CharacteristicsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.CharacteristicsSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.ClassHeaderSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.ClassHeaderSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.PassivesSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.PassivesSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.ProficiencySummary
import com.pixelized.rplexicon.ui.screens.summary.composable.ProficiencySummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.SavingThrowsSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.SavingThrowsSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.SpellSummary
import com.pixelized.rplexicon.ui.screens.summary.composable.SpellSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.preview.statistic.rememberStatisticSummary
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
data class StatisticSummaryUio(
val headerVisibility: State<Boolean>,
val header: ClassHeaderSummaryUio,
val statsVisibility: State<Boolean>,
val attributes: AttributesSummaryUio,
val characteristicsVisibility: State<Boolean>,
val characteristics: CharacteristicsSummaryUio,
val savingThrowsVisibility: State<Boolean>,
val savingThrows: SavingThrowsSummaryUio,
val proficienciesVisibility: State<Boolean>,
val proficiencies: ProficiencySummaryUio,
val passivesVisibility: State<Boolean>,
val passives: PassivesSummaryUio,
val spellsVisibility: State<Boolean>,
val spells: SpellSummaryUio,
)
@Composable
fun StatisticSummary(
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
summary: StatisticSummaryUio,
onClass: (ClassHeaderSummaryUio.Header) -> Unit,
onDice: (ClassHeaderSummaryUio.Dice) -> Unit,
) {
Column(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(horizontal = 16.dp)
.then(other = modifier),
) {
AnimatedVisibility(
modifier = Modifier
.zIndex(zIndex = 1f)
.fillMaxWidth()
.offset { IntOffset(x = 0, y = scrollState.value) }
.background(brush = rememberHeaderBackgroundGradient())
.padding(bottom = 16.dp, end = 3.dp),
visible = summary.headerVisibility.value,
enter = fadeIn(),
) {
ClassHeaderSummary(
header = summary.header,
onClass = onClass,
onDice = onDice,
)
}
Column(
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
) {
AnimatedVisibility(
visible = summary.statsVisibility.value,
enter = enterTransition(),
) {
AttributesSummary(
attributes = summary.attributes,
)
}
AnimatedVisibility(
visible = summary.characteristicsVisibility.value,
enter = enterTransition(),
) {
CharacteristicsSummary(
characteristics = summary.characteristics,
)
}
AnimatedVisibility(
visible = summary.savingThrowsVisibility.value,
enter = enterTransition(),
) {
SavingThrowsSummary(
savingThrows = summary.savingThrows,
)
}
AnimatedVisibility(
visible = summary.proficienciesVisibility.value,
enter = enterTransition(),
) {
ProficiencySummary(
proficiencies = summary.proficiencies,
)
}
AnimatedVisibility(
visible = summary.passivesVisibility.value,
enter = enterTransition(),
) {
PassivesSummary(
passives = summary.passives,
)
}
AnimatedVisibility(
visible = summary.spellsVisibility.value,
enter = enterTransition(),
) {
SpellSummary(
spells = summary.spells,
)
}
}
}
}
@Composable
private fun enterTransition(
density: Density = LocalDensity.current,
): EnterTransition {
return fadeIn() + slideInVertically { with(density) { 24.dp.roundToPx() } }
}
@Composable
private fun rememberHeaderBackgroundGradient(): Brush {
val colorScheme = MaterialTheme.colorScheme
return remember {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = 1.0f),
colorScheme.surface.copy(alpha = 1.0f),
colorScheme.surface.copy(alpha = 0.75f),
colorScheme.surface.copy(alpha = 0.0f),
)
)
}
}
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, heightDp = 1680)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, heightDp = 1680)
fun StatisticSummaryPreview() {
LexiconTheme {
Surface {
StatisticSummary(
modifier = Modifier.fillMaxSize(),
summary = rememberStatisticSummary(),
onClass = { },
onDice = { },
)
}
}
}

View file

@ -1,12 +1,13 @@
package com.pixelized.rplexicon.ui.screens.summary
package com.pixelized.rplexicon.ui.screens.summary.pages.statistic
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.ui.screens.summary.SummaryFactory
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class SummaryViewModel @Inject constructor(
class StatisticViewModel @Inject constructor(
factory: SummaryFactory,
) : ViewModel() {
val summary = factory.fetchSummary(viewModelScope)

View file

@ -1,7 +1,7 @@
package com.pixelized.rplexicon.utilitary.extentions.local
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.CharacterSheetFire
import com.pixelized.rplexicon.data.network.CharacterSheetFire
fun CharacterSheet.spell(level: Int): Int? = when (level) {
1 -> spell1

View file

@ -86,7 +86,6 @@
<string name="map_destination">Destinations :</string>
<string name="map_illustrations">Illustrations :</string>
<string name="character_sheet_summary_title">Détails des personnages</string>
<string name="character_sheet_refresh_label">Rafraichir</string>
<string name="character_sheet_tab_proficiency">Talents</string>
<string name="character_sheet_tab_inventory">Inventaire</string>

View file

@ -86,7 +86,6 @@
<string name="map_destination">Destinations:</string>
<string name="map_illustrations">Illustrations:</string>
<string name="character_sheet_summary_title">Characters sheets details</string>
<string name="character_sheet_refresh_label">Refresh</string>
<string name="character_sheet_tab_proficiency">Proficiencies</string>
<string name="character_sheet_tab_inventory">Inventory</string>
@ -216,4 +215,9 @@
<string name="character_sheet_equipment_main_hand">Main hand</string>
<string name="character_sheet_equipment_off_hand">Off hand</string>
<string name="character_sheet_equipment_empty_description">This equipment does not have any description</string>
<string name="summary_title">Game Master</string>
<string name="summary_title_tab_statistics">Statistic</string>
<string name="summary_title_tab_combats">Combat</string>
<string name="summary_title_tab_rolls">Rolls</string>
</resources>