Refactor the alteration system to be full flow.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-02 17:01:32 +02:00
parent 8841529b31
commit 6ea8f90903
41 changed files with 688 additions and 452 deletions

View file

@ -0,0 +1,25 @@
package com.pixelized.rplexicon.business
import com.pixelized.rplexicon.data.model.CharacterSheet
object AlterationSortUseCase {
private const val PLAYER = "Joueur"
private const val FEAT = "Don"
fun sort(sheet: CharacterSheet?): Comparator<String> {
return compareByDescending<String> {
sheet?.name == it
}.thenByDescending {
PLAYER == it
}.thenByDescending {
sheet?.characterClass?.any { clazz -> clazz.value == it }
}.thenByDescending {
sheet?.race == it
}.thenByDescending {
FEAT == it
}.thenBy {
it
}
}
}

View file

@ -3,12 +3,12 @@ package com.pixelized.rplexicon.business
import android.app.Application
import android.content.Context
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.AssignedSpell
import com.pixelized.rplexicon.data.model.CharacterSheet
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.alteration.Alteration
import com.pixelized.rplexicon.data.model.roll.Flat
import com.pixelized.rplexicon.data.model.roll.Throw
import com.pixelized.rplexicon.data.network.NetworkThrow
@ -20,7 +20,6 @@ import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.data.repository.character.SpellRepository
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
import com.pixelized.rplexicon.utilitary.extentions.icon
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import com.pixelized.rplexicon.utilitary.extentions.local.advantage
import com.pixelized.rplexicon.utilitary.extentions.local.base
import com.pixelized.rplexicon.utilitary.extentions.local.critical
@ -36,6 +35,8 @@ import com.pixelized.rplexicon.utilitary.extentions.local.sum
import com.pixelized.rplexicon.utilitary.extentions.local.toStatus
import com.pixelized.rplexicon.utilitary.extentions.masteryMultiplier
import com.pixelized.rplexicon.utilitary.extentions.modifier
import com.pixelized.rplexicon.utilitary.extentions.toLabel
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
@ -57,14 +58,15 @@ class DiceThrowUseCase @Inject constructor(
private val skillRepository: SkillRepository,
private val alterationRepository: AlterationRepository,
) {
fun roll(
suspend fun roll(
diceThrow: DiceThrow,
isThrowHidden: Boolean,
alterationId: List<String>,
): DiceThrowResult? {
val sheet = characterSheetRepository.find(name = diceThrow.character)
val alterations = alterationRepository.getAlterations(character = diceThrow.character)
.filter { alterationId.contains(it.name) }
val alterations =
alterationRepository.getAssignedAlterations(diceThrow.character).firstOrNull()
?.filter { alterationId.contains(it.name) } ?: emptyList()
return if (sheet != null) {
when (diceThrow) {
@ -393,15 +395,12 @@ class DiceThrowUseCase @Inject constructor(
character = diceThrow.character,
action = diceThrow.weapon,
)
val objectAlterations = alterationRepository.getAlterations(
objects = action?.alterations,
)
actionThrow(
character = sheet,
action = diceThrow.weapon,
diceThrow = action?.hit,
title = { getString(R.string.dice_roll_attack_hit_title, it) },
alterations = alterations + objectAlterations,
alterations = alterations,
ability = Property.PHYSICAL_MELEE_ATTACK,
)
}
@ -411,15 +410,12 @@ class DiceThrowUseCase @Inject constructor(
character = diceThrow.character,
action = diceThrow.weapon,
)
val objectAlterations = alterationRepository.getAlterations(
objects = action?.alterations,
)
actionThrow(
character = sheet,
action = diceThrow.weapon,
diceThrow = action?.damage,
title = { getString(R.string.dice_roll_attack_damage_title, it) },
alterations = alterations + objectAlterations,
alterations = alterations,
ability = Property.PHYSICAL_MELEE_DAMAGE,
)
}
@ -429,15 +425,12 @@ class DiceThrowUseCase @Inject constructor(
character = diceThrow.character,
action = diceThrow.weapon,
)
val objectAlterations = alterationRepository.getAlterations(
objects = action?.alterations,
)
actionThrow(
character = sheet,
action = diceThrow.weapon,
diceThrow = action?.hit,
title = { getString(R.string.dice_roll_attack_hit_title, it) },
alterations = alterations + objectAlterations,
alterations = alterations,
ability = Property.PHYSICAL_RANGE_ATTACK,
)
}
@ -447,15 +440,12 @@ class DiceThrowUseCase @Inject constructor(
character = diceThrow.character,
action = diceThrow.weapon,
)
val objectAlterations = alterationRepository.getAlterations(
objects = action?.alterations,
)
actionThrow(
character = sheet,
action = diceThrow.weapon,
diceThrow = action?.damage,
title = { getString(R.string.dice_roll_attack_damage_title, it) },
alterations = alterations + objectAlterations,
alterations = alterations,
ability = Property.PHYSICAL_RANGE_DAMAGE,
)
}
@ -750,11 +740,13 @@ class DiceThrowUseCase @Inject constructor(
// compute the amount of main dice to throw.
val amount = if (status.isCritical) {
diceThrow?.dice?.count?.times(2)?.let {
if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isSavageAttacks)
it.plus(1) else it
if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isSavageAttacks) it.plus(
1
) else it
}?.let {
if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isBrutalCritical)
it.plus(1) else it
if (ability == Property.PHYSICAL_MELEE_DAMAGE && status.isBrutalCritical) it.plus(
1
) else it
}
} else {
diceThrow?.dice?.count

View file

@ -318,7 +318,7 @@ class SearchUseCase @Inject constructor(
return spells.filter {
criterion.map { criteria ->
val schoolName = context.getString(it.school.label)
val itemDescription = descriptionRepository.find(name = it.name)
val itemDescription = descriptionRepository.getDescription(name = it.name)
val name = it.name.contains(criteria, true)
val translated = itemDescription?.original?.contains(criteria, true) == true
val school = schoolName.contains(criteria, true)
@ -333,7 +333,7 @@ class SearchUseCase @Inject constructor(
}.all { it }
}.map { item ->
val school = context.getString(item.school.label)
val itemDescription = descriptionRepository.find(name = item.name)
val itemDescription = descriptionRepository.getDescription(name = item.name)
SearchItemUio.SpellSearchItemUio(
id = item.name,

View file

@ -57,6 +57,7 @@ data class CharacterSheet(
val others: List<String>, // others masteries
) {
val isWarlock: Boolean get() = characterClass.contains(Class.WARLOCK)
val resource: String? get() = characterClass.firstNotNullOfOrNull { it.resource }
enum class Class(
val value: String,
@ -82,7 +83,12 @@ data class CharacterSheet(
label = R.string.character_sheet_title_conduit,
icon = R.drawable.ic_class_cleric_24
),
DRUID(value = "Druide", icon = R.drawable.ic_class_druid_24),
DRUID(
value = "Druide",
resource = "Forme sauvage",
label = R.string.character_sheet_title_wild_shape,
icon = R.drawable.ic_class_druid_24,
),
FIGHTER(value = "Guerrier", icon = R.drawable.ic_class_fighter_24),
MONK(value = "Moine", icon = R.drawable.ic_class_monk_24),
PALADIN(value = "Paladin", icon = R.drawable.ic_class_paladin_24),

View file

@ -1,6 +1,7 @@
package com.pixelized.rplexicon.data.model
package com.pixelized.rplexicon.data.model.alteration
import android.net.Uri
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.roll.Dice
import com.pixelized.rplexicon.data.model.roll.Flat

View file

@ -0,0 +1,19 @@
package com.pixelized.rplexicon.data.model.alteration
abstract class AlterationStatus {
data class Key(
val character: String,
val alteration: String,
)
data class Value(
val value: Boolean,
) {
fun switch(): Value = Value(value = !value)
}
companion object {
fun entry(character: String, alteration: String, value: Boolean): Pair<Key, Value> =
Key(character = character, alteration = alteration) to Value(value = value)
}
}

View file

@ -1,7 +1,7 @@
package com.pixelized.rplexicon.data.parser.alteration
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.parser.column

View file

@ -8,6 +8,7 @@ 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.alteration.AlterationStatus
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.network.CharacterSheetFireMap
import com.pixelized.rplexicon.data.network.NetworkThrow
@ -20,13 +21,13 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import javax.inject.Singleton
@ -34,7 +35,6 @@ import javax.inject.Singleton
class FirebaseRepository @Inject constructor(
application: Application,
) {
private val externalScope: CoroutineScope = CoroutineScope(Dispatchers.Default + Job())
private val database = Firebase.database(
url = application.getString(R.string.firebase_realtime_database),
)
@ -42,84 +42,105 @@ class FirebaseRepository @Inject constructor(
private val _error = MutableSharedFlow<Exception>()
val error: SharedFlow<Exception> get() = _error
val status: StateFlow<Map<String, Boolean>>
private val characterFireSheet: StateFlow<Map<String, CharacterSheetFire>> = 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(CharacterSheetFireMap::class.java)
} catch (exception: Exception) {
Log.e(TAG, "Failed to parse value.", exception)
_error.tryEmit(exception)
null
}
if (value != null) {
trySend(value.characters)
}
}
override fun onCancelled(error: DatabaseError) {
Log.e(TAG, "Failed to read value.", error.toException())
cancel()
}
})
awaitClose {
reference.removeEventListener(listener)
}
}.stateIn(
scope = CoroutineScope(Dispatchers.Default + Job()),
started = SharingStarted.Lazily,
initialValue = emptyMap()
)
private val networkThrows = 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)
}
}.stateIn(
scope = CoroutineScope(Dispatchers.Default + Job()),
started = SharingStarted.Lazily,
initialValue = NetworkThrowMap(),
)
private val alterationStatus: StateFlow<Map<AlterationStatus.Key, AlterationStatus.Value>> =
characterFireSheet.map { fireSheets: Map<String, CharacterSheetFire> ->
fireSheets.flatMap { fireCharacter: Map.Entry<String, CharacterSheetFire> ->
fireCharacter.value.alterations.map { alteration ->
AlterationStatus.entry(
character = fireCharacter.key,
alteration = alteration.key,
value = alteration.value,
)
}
}.toMap()
}.stateIn(
scope = CoroutineScope(Dispatchers.Default + Job()),
started = SharingStarted.Lazily,
initialValue = emptyMap()
)
init {
Firebase.database.setPersistenceEnabled(true)
status = MutableStateFlow(emptyMap())
externalScope.launch {
getCharacter().map {
it.characters.flatMap { character ->
character.value.alterations.map { alteration ->
(character.key + alteration.key) to alteration.value
}
}.toMap()
}.collectLatest {
status.value = it
}
}
}
fun getCharacter(): Flow<CharacterSheetFireMap> {
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(CharacterSheetFireMap::class.java)
} catch (exception: Exception) {
Log.e(TAG, "Failed to parse value.", exception)
_error.tryEmit(exception)
null
}
if (value != null) {
trySend(value)
}
}
fun getCharacters(): Flow<Map<String, CharacterSheetFire>> =
characterFireSheet
override fun onCancelled(error: DatabaseError) {
Log.e(TAG, "Failed to read value.", error.toException())
cancel()
}
})
awaitClose {
reference.removeEventListener(listener)
}
}
}
fun getCharacter(character: String): Flow<CharacterSheetFire> =
characterFireSheet.mapNotNull { it[character] }
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) {
Log.e(TAG, "Failed to parse value.", exception)
_error.tryEmit(exception)
null
}
if (value != null) {
trySend(value)
}
}
fun getThrows(): Flow<NetworkThrowMap> =
networkThrows
override fun onCancelled(error: DatabaseError) {
Log.e(TAG, "Failed to read value.", error.toException())
cancel()
}
})
awaitClose {
reference.removeEventListener(listener)
}
}
}
fun getAlterationStatus(): Flow<Map<AlterationStatus.Key, AlterationStatus.Value>> =
alterationStatus
fun getAlterationStatusSnapshot(key: AlterationStatus.Key): AlterationStatus.Value =
alterationStatus.value[key] ?: AlterationStatus.Value(false)
fun setCharacterHitPoint(character: String, value: Int, extra: Int) {
val reference = database.getReference(
@ -159,45 +180,11 @@ class FirebaseRepository @Inject constructor(
reference.setValue(value)
}
fun getStatus(character: String, status: String): Boolean {
return this.status.value[character + status] ?: false
}
fun setStatus(character: String, status: String, value: Boolean) {
fun setAlterationStatus(key: AlterationStatus.Key, value: AlterationStatus.Value) {
val reference = database.getReference(
"$PATH_CHARACTERS/$character/${CharacterSheetFire.ALTERATIONS}/$status"
"$PATH_CHARACTERS/${key.character}/${CharacterSheetFire.ALTERATIONS}/${key.alteration}"
)
reference.setValue(value)
}
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)
}
}
reference.setValue(value.value)
}
fun sendThrow(character: String?, throws: NetworkThrow) {

View file

@ -0,0 +1,56 @@
package com.pixelized.rplexicon.data.repository.character
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.alteration.AlterationStatus
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ActiveAlterationRepository @Inject constructor(
alterationRepository: AlterationRepository,
firebaseRepository: FirebaseRepository,
) {
private val activeAlterations: StateFlow<Map<String, List<Alteration>>> =
alterationRepository.getAlterations()
.combine(firebaseRepository.getAlterationStatus()) { activeAlterations, status ->
activeAlterations.mapValues { entry ->
entry.value.filter { alteration ->
val key = AlterationStatus.Key(entry.key, alteration.name)
status[key] == ACTIVE
}
}
}
.stateIn(
scope = CoroutineScope(Dispatchers.Default + Job()),
started = SharingStarted.Lazily,
initialValue = emptyMap()
)
/**
* Get a list of Active [Alteration] for all characters.
* @return a list of alterations.
*/
fun getActiveAlterations(): Flow<Map<String, List<Alteration>>> =
activeAlterations
/**
* Get a list of Active [Alteration] for a character
* @return a list of alterations.
*/
fun getActiveAssignedAlterations(character: String?): Flow<List<Alteration>> =
activeAlterations.map { it[character] ?: emptyList() }
companion object {
val ACTIVE = AlterationStatus.Value(value = true)
}
}

View file

@ -1,17 +1,17 @@
package com.pixelized.rplexicon.data.repository.character
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.parser.alteration.AlterationParser
import com.pixelized.rplexicon.data.repository.CharacterBinder
import com.pixelized.rplexicon.data.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.utilitary.Update
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton
@ -19,11 +19,8 @@ import javax.inject.Singleton
class AlterationRepository @Inject constructor(
private val googleRepository: GoogleSheetServiceRepository,
private val alterationParser: AlterationParser,
private val fireRepository: FirebaseRepository,
) {
private val _assignedAlterations = MutableStateFlow<Map<String, List<Alteration>>>(emptyMap())
val assignedAlterations: Flow<Map<String, List<Alteration>>> =
combine(_assignedAlterations, fireRepository.status) { alt, _ -> alt }
private val assignedAlterations = MutableStateFlow<Map<String, List<Alteration>>>(emptyMap())
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
@ -32,18 +29,27 @@ class AlterationRepository @Inject constructor(
* get all [Alteration] for a character
* @return a list of alterations.
*/
fun getAlterations(character: String): List<Alteration> {
return _assignedAlterations.value[character] ?: emptyList()
fun getAlterations(): Flow<Map<String, List<Alteration>>> {
return assignedAlterations
}
/**
* get all [Alteration] for a list of objects
* @returna list of alterations.
* get an alteration for a character that have [alteration] as namer.
* @return a nullable [Alteration]
*/
fun getAlterations(objects: List<String>?): List<Alteration> {
return objects?.flatMap { objectAlteration ->
_assignedAlterations.value[objectAlteration] ?: emptyList()
} ?: emptyList()
suspend fun getAssignedAlterationSnapshot(character: String?, alteration: String?): Alteration? {
return assignedAlterations.firstOrNull()?.get(character)
?.firstOrNull { it.name == alteration }
}
/**
* get all [Alteration] for a character
* @return a list of alterations.
*/
fun getAssignedAlterations(character: String): Flow<List<Alteration>> {
return assignedAlterations.map { alterations ->
alterations[character] ?: emptyList()
}
}
/**
@ -52,39 +58,15 @@ class AlterationRepository @Inject constructor(
* @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) }
fun getAssignedAlterations(
character: String,
vararg properties: Property
): Flow<List<Alteration>> {
return getAssignedAlterations(character = character).map { alterations ->
alterations.filter { it.status.keys.any { key -> properties.contains(key) } }
}
}
/**
* get a list of Active [Alteration] for a character
* @return a list of alterations.
*/
fun getActiveAlterations(character: String): List<Alteration> {
return _assignedAlterations.value[character]?.filter { alteration ->
fireRepository.status.value[character + alteration.name] == true
} ?: emptyList()
}
/**
* 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 (fireRepository.status.value[character + alteration.name] == true) {
alteration.status.forEach {
status.getOrPut(it.key) { mutableListOf() }.add(it.value)
}
}
}
return status
}
/**
* Update the alteration sheet.
*/
@ -93,29 +75,8 @@ class AlterationRepository @Inject constructor(
googleRepository.fetch { sheet ->
val request = sheet.get(CharacterBinder.ID, CharacterBinder.ALTERATION)
val data = alterationParser.parse(sheet = request.execute(), characterSheets = sheets)
_assignedAlterations.emit(data)
assignedAlterations.tryEmit(data)
lastSuccessFullUpdate = Update.currentTime()
}
}
companion object {
fun sort(sheet: CharacterSheet?): Comparator<String> {
return compareByDescending<String> {
sheet?.name == it
}.thenByDescending {
PLAYER == it
}.thenByDescending {
sheet?.characterClass?.any { clazz -> clazz.value == it }
}.thenByDescending {
sheet?.race == it
}.thenByDescending {
FEAT == it
}.thenBy {
it
}
}
private const val PLAYER = "Joueur"
private const val FEAT = "Don"
}
}

View file

@ -22,7 +22,7 @@ class DescriptionRepository @Inject constructor(
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun find(name: String?): Description? = _data.value[name]
fun getDescription(name: String?): Description? = _data.value[name]
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchDescription() {

View file

@ -151,16 +151,20 @@ fun CharacterSheetScreen(
scope.launch { pagerState.animateScrollToPage(it) }
},
onInitiative = {
overlay.prepareRoll(diceThrow = headerViewModel.initiativeRoll())
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = headerViewModel.initiativeRoll())
overlay.showOverlay()
}
},
onHitPoint = headerViewModel::toggleHitPointDialog,
onResource = {
headerViewModel.showSkillEditDialog(item = it)
},
onDeathRoll = {
overlay.prepareRoll(diceThrow = headerViewModel.onDeathThrow())
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = headerViewModel.onDeathThrow())
overlay.showOverlay()
}
},
onDeathSuccess = headerViewModel::onDeathSuccess,
onDeathFailure = headerViewModel::onDeathFailure,
@ -203,9 +207,11 @@ fun CharacterSheetScreen(
modifier = Modifier.navigationBarsPadding(),
spells = spellsViewModel.preparedSpellLevel,
onLevel = { spell, level ->
scope.launch { sheetState.hide() }
overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(spell, level))
overlay.showOverlay()
scope.launch {
sheetState.hide()
overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(spell, level))
overlay.showOverlay()
}
},
)
},

View file

@ -3,15 +3,24 @@ 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 android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
@ -36,6 +45,7 @@ data class AlterationItemUio(
val source: String,
val subLabel: String?,
val checked: Boolean,
val override: Boolean,
)
@Composable
@ -46,49 +56,63 @@ fun AlterationItem(
onInfo: (id: String) -> Unit,
onClick: (id: String) -> Unit,
) {
Row(
modifier = Modifier
.clickable { onInfo(alteration.label) }
.heightIn(min = 52.dp)
.padding(paddingValues = padding)
.then(other = modifier),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
alteration.icon?.let { uri ->
AsyncImage(
modifier = Modifier.size(size = 36.dp),
model = uri,
contentDescription = null,
)
}
Column(
modifier = Modifier.weight(weight = 1f),
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
Box(modifier = Modifier.height(IntrinsicSize.Min)) {
Row(
modifier = Modifier
.clickable { onInfo(alteration.label) }
.heightIn(min = 52.dp)
.padding(paddingValues = padding)
.then(other = modifier),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
Text(
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = alteration.label,
)
alteration.subLabel?.let {
Text(
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = it,
alteration.icon?.let { uri ->
AsyncImage(
modifier = Modifier.size(size = 36.dp),
model = uri,
contentDescription = null,
)
}
Column(
modifier = Modifier.weight(weight = 1f),
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
) {
Text(
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = alteration.label,
)
alteration.subLabel?.let {
Text(
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = it,
)
}
}
Switch(
enabled = true,
checked = alteration.checked,
onCheckedChange = { onClick(alteration.label) },
)
}
AnimatedVisibility(
visible = alteration.override,
enter = fadeIn(),
exit = fadeOut(),
) {
Box(
modifier = Modifier
.fillMaxHeight()
.width(width = 2.dp)
.background(color = MaterialTheme.colorScheme.primary)
)
}
Switch(
enabled = true,
checked = alteration.checked,
onCheckedChange = { onClick(alteration.label) },
)
}
}
@ -118,6 +142,7 @@ private class RollAlterationPreviewProvider : PreviewParameterProvider<Alteratio
subLabel = "Bless",
source = "Clerc",
checked = false,
override = false,
),
AlterationItemUio(
icon = null,
@ -125,6 +150,7 @@ private class RollAlterationPreviewProvider : PreviewParameterProvider<Alteratio
subLabel = "Guidance",
source = "Clerc",
checked = true,
override = true,
),
)
}

View file

@ -1,7 +1,7 @@
package com.pixelized.rplexicon.ui.screens.character.factory
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.Attack
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property

View file

@ -18,10 +18,10 @@ class CharacterSheetHeaderUioFactory @Inject constructor(
sheetHeaderData: HeaderViewModel.SheetHeaderData?,
fireHeaderData: HeaderViewModel.FireHeaderData?,
): CharacterSheetHeaderUio {
val characterClass = sheetHeaderData?.characterClass
val classWithResource = sheetHeaderData?.characterClass?.firstOrNull { it.resource != null }
val favoriteSkill = skillRepository.find(
character = character,
skill = sheetHeaderData?.characterClass?.resource
skill = classWithResource?.resource
)
return CharacterSheetHeaderUio(
initiative = LabelPointUio(
@ -44,10 +44,10 @@ class CharacterSheetHeaderUioFactory @Inject constructor(
value = "$it",
)
},
resource = if (characterClass?.label != null && favoriteSkill?.amount != null) {
resource = if (classWithResource?.label != null && favoriteSkill?.amount != null) {
ResourcePointUio(
id = favoriteSkill.name,
label = characterClass.label,
label = classWithResource.label,
value = fireHeaderData?.resource ?: 0,
max = favoriteSkill.amount,
)

View file

@ -1,7 +1,7 @@
package com.pixelized.rplexicon.ui.screens.character.factory
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio

View file

@ -24,7 +24,7 @@ class SkillFactoryUioFactory @Inject constructor(
): List<SkillItemUio> {
// Filter out passive skills, theses are display in the Proficiency Page.
return skills.map { skill ->
val description = descriptionRepository.find(name = skill.name)
val description = descriptionRepository.getDescription(name = skill.name)
val effectModifier = skill.effect?.modifier?.sumOf {
when (it) {
Property.LEVEL -> character?.level?.base ?: 0

View file

@ -78,7 +78,7 @@ class SpellUioFactory @Inject constructor(
icon = assignedSpell.spell.icon,
school = assignedSpell.spell.school,
name = assignedSpell.spell.name,
translated = descriptionRepository.find(name = assignedSpell.spell.name)?.original,
translated = descriptionRepository.getDescription(name = assignedSpell.spell.name)?.original,
castingTime = assignedSpell.spell.castingTime,
range = assignedSpell.spell.range,
duration = assignedSpell.spell.duration,

View file

@ -68,14 +68,18 @@ fun ActionPage(
spells = spellsViewModel.spells,
onAttackHit = { id ->
attacksViewModel.onHitRoll(id)?.let {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
}
}
},
onAttackDamage = { id ->
attacksViewModel.onDamageRoll(id)?.let {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
}
}
},
onObject = {
@ -83,16 +87,20 @@ fun ActionPage(
},
onUseObject = {
objectsViewModel.onUse(it.name)?.let { throws ->
overlay.prepareRoll(diceThrow = throws)
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = throws)
overlay.showOverlay()
}
}
},
onSkillCount = {
skillViewModel.showSkillEditDialog(item = it)
},
onSkillThrow = {
overlay.prepareRoll(diceThrow = skillViewModel.onSkillRoll(it.label))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = skillViewModel.onSkillRoll(it.label))
overlay.showOverlay()
}
},
onSkillInfo = {
skillViewModel.showSkillDetailDialog(item = it.label)
@ -104,20 +112,26 @@ fun ActionPage(
screen.navigateToSpellDetail(spell = spell)
},
onSpellHit = { id ->
overlay.prepareRoll(diceThrow = spellsViewModel.onSpellHitRoll(id))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = spellsViewModel.onSpellHitRoll(id))
overlay.showOverlay()
}
},
onSpellDamage = { id ->
overlay.prepareRoll(diceThrow = spellsViewModel.onSpellDamageRoll(id))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = spellsViewModel.onSpellDamageRoll(id))
overlay.showOverlay()
}
},
onCast = {
if (spellsViewModel.shouldDisplaySpellLevelChooser(it)) {
spellsViewModel.prepareSpellCast(it)
scope.launch { sheetState.show() }
} else {
overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(it))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(it))
overlay.showOverlay()
}
}
},
)

View file

@ -6,17 +6,18 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.Attack
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.repository.character.ActionRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.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.toStatus
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
@ -29,7 +30,7 @@ class AttacksViewModel @Inject constructor(
application: Application,
savedStateHandle: SavedStateHandle,
private val characterRepository: CharacterSheetRepository,
private val alterationRepository: AlterationRepository,
private val activeAlterationRepository: ActiveAlterationRepository,
private val actionRepository: ActionRepository,
attackFactory: AttackUioFactory,
) : AndroidViewModel(application) {
@ -39,26 +40,23 @@ class AttacksViewModel @Inject constructor(
val attacks: State<List<AttackUio>> get() = _attacks
init {
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.Default) {
val struct = ActionStruct()
characterRepository.data
.combine(actionRepository.data) { characters, actions ->
ActionData(
character = characters[character],
actions = actions[character] ?: emptyList()
)
struct.character = characters[character]
struct.actions = actions[character] ?: emptyList()
}
.combine(alterationRepository.assignedAlterations) { data, _ ->
data.also {
it.alterations = alterationRepository.getActiveAlterationsStatus(character)
}
.combine(activeAlterationRepository.getActiveAssignedAlterations(character = character)) { _, alterations ->
struct.alterations = alterations.toStatus()
}
.collect { data ->
val characterSheet = data.character
if (characterSheet != null) {
val attacks = data.actions.map { action ->
.collect {
val (sheet, actions, alterations) = struct
if (sheet != null) {
val attacks = actions.map { action ->
attackFactory.convert(
characterSheet = characterSheet,
alterations = data.alterations,
characterSheet = sheet,
alterations = alterations,
attack = action,
)
}
@ -108,10 +106,14 @@ class AttacksViewModel @Inject constructor(
}
}
private data class ActionData(
var character: CharacterSheet?,
var actions: List<Attack>,
private class ActionStruct(
var character: CharacterSheet? = null,
) {
lateinit var actions: List<Attack>
lateinit var alterations: Map<Property, List<Alteration.Status>>
operator fun component1() = character
operator fun component2() = actions
operator fun component3() = alterations
}
}

View file

@ -10,10 +10,12 @@ import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.ui.composable.edit.HpPointDialogUio
import com.pixelized.rplexicon.ui.composable.edit.SkillEditDialogUio
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
@ -21,6 +23,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.Charact
import com.pixelized.rplexicon.ui.screens.character.composable.character.ResourcePointUio
import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetHeaderUioFactory
import com.pixelized.rplexicon.utilitary.extentions.local.sum
import com.pixelized.rplexicon.utilitary.extentions.local.toStatus
import com.pixelized.rplexicon.utilitary.extentions.modifier
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@ -34,15 +37,13 @@ class HeaderViewModel @Inject constructor(
private val headerFactory: CharacterSheetHeaderUioFactory,
private val firebaseRepository: FirebaseRepository,
characterRepository: CharacterSheetRepository,
alterationRepository: AlterationRepository,
skillRepository: SkillRepository,
activeAlterationRepository: ActiveAlterationRepository,
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(
character = character,
@ -59,18 +60,19 @@ class HeaderViewModel @Inject constructor(
init {
viewModelScope.launch {
launch(context = Dispatchers.IO) {
launch(context = Dispatchers.Default) {
val struct = HeaderStruct()
characterRepository.data
.combine(skillRepository.skills) { sheets, _ -> sheets[character] }
.combine(alterationRepository.assignedAlterations) { sheet, _ -> sheet }
.collect { sheet ->
.combine(activeAlterationRepository.getActiveAssignedAlterations(character = character)) { sheets, alterations ->
struct.sheet = sheets[character]
struct.alterationsStatus = alterations.toStatus()
}
.collect {
val (sheet, status) = struct
if (sheet != null) {
val status = alterationRepository.getActiveAlterationsStatus(
character = sheet.name,
)
val data = SheetHeaderData(
hpMax = sheet.hitPoint + status[Property.HIT_POINT].sum,
characterClass = sheet.characterClass.firstOrNull(),
characterClass = sheet.characterClass,
initiative = (sheet.dexterity + status[Property.DEXTERITY].sum).modifier + status[Property.INITIATIVE].sum,
ca = sheet.armorClass + status[Property.ARMOR_CLASS].sum,
dc = sheet.dC,
@ -83,14 +85,19 @@ class HeaderViewModel @Inject constructor(
}
}
}
launch(context = Dispatchers.IO) {
launch(context = Dispatchers.Default) {
val struct = HeaderValueStruct()
characterRepository.data
.combine(firebaseRepository.getCharacter(character = character)) { sheets, fire -> sheets[character] to fire }
.combine(firebaseRepository.getCharacter(character = character)) { sheets, fire ->
struct.sheet = sheets[character]
struct.fire = fire
}
.collect {
val (sheet, fire) = it
val (sheet, fire) = struct
val data = FireHeaderData(
hp = fire.hitPoint?.value ?: 1,
resource = fire.skills[sheet?.characterClass?.firstOrNull()?.resource],
resource = fire.skills[sheet?.resource],
extra = fire.hitPoint?.additional ?: 0,
deathSuccess = fire.death?.success ?: 0,
deathFailure = fire.death?.failure ?: 0,
@ -172,10 +179,24 @@ class HeaderViewModel @Inject constructor(
fun initiativeRoll(): DiceThrow.Initiative = DiceThrow.Initiative(character)
data class HeaderStruct(
var sheet: CharacterSheet? = null,
var alterationsStatus: Map<Property, List<Alteration.Status>> = emptyMap(),
)
class HeaderValueStruct(
var sheet: CharacterSheet? = null
) {
lateinit var fire: CharacterSheetFire
operator fun component1() = sheet
operator fun component2() = fire
}
@Stable
data class SheetHeaderData(
val hpMax: Int,
val characterClass: CharacterSheet.Class?,
val characterClass: List<CharacterSheet.Class>,
val initiative: Int,
val ca: Int,
val dc: Int?,

View file

@ -31,14 +31,14 @@ class ObjectsViewModel @Inject constructor(
val dialog: State<SkillDetailUio?> get() = _dialog
init {
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.Default) {
objectsRepository.data.collect { objects ->
val data = objects[character]?.map {
ObjectItemUio(
prefix = it.prefix,
icon = it.icon,
name = it.name,
original = descriptionRepository.find(name = it.name)?.original,
original = descriptionRepository.getDescription(name = it.name)?.original,
effect = it.effect,
)
} ?: emptyList()

View file

@ -48,7 +48,7 @@ class SkillsViewModel @Inject constructor(
init {
viewModelScope.launch {
launch(Dispatchers.IO) {
launch(Dispatchers.Default) {
val data = SkillStruct()
sheetRepository.data
.combine(skillRepository.skills) { sheets, skills ->
@ -81,7 +81,7 @@ class SkillsViewModel @Inject constructor(
fun showSkillDetailDialog(item: String) {
_skillDetailDialog.value = descriptionRepository
.find(name = item)
.getDescription(name = item)
?.let { description ->
SkillDetailUio(
name = item,

View file

@ -61,7 +61,7 @@ class SpellsViewModel @Inject constructor(
val preparedSpellLevel: State<SpellChooserUio?> get() = _preparedSpellLevel
init {
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(Dispatchers.Default) {
characterRepository.data
.combine(spellRepository.spells) { sheets, spells ->
character = sheets[characterName]

View file

@ -42,7 +42,9 @@ fun AlterationPage(
modifier = Modifier.fillMaxSize(),
groups = viewModel.alterations,
onAlterationInfo = {
viewModel.showAlterationDetail(id = it)
scope.launch {
viewModel.showAlterationDetail(id = it)
}
},
onAlterationClick = {
scope.launch {

View file

@ -7,7 +7,13 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.business.AlterationSortUseCase
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Description
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.alteration.AlterationStatus
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
@ -25,6 +31,7 @@ import javax.inject.Inject
class AlterationViewModel @Inject constructor(
private val characterSheetRepository: CharacterSheetRepository,
private val alterationRepository: AlterationRepository,
private val activeAlterationRepository: ActiveAlterationRepository,
private val firebaseRepository: FirebaseRepository,
private val descriptionRepository: DescriptionRepository,
private val factory: AlterationFactory,
@ -41,32 +48,38 @@ class AlterationViewModel @Inject constructor(
init {
viewModelScope.launch {
launch(Dispatchers.IO) {
launch(Dispatchers.Default) {
val data = AlterationStruct()
descriptionRepository.data
.combine(characterSheetRepository.data) { _, sheet -> sheet }
.combine(alterationRepository.assignedAlterations) { sheet, alterations ->
sheet[character] to alterations
.combine(characterSheetRepository.data) { descriptions, sheet ->
data.sheet = sheet[character]
data.descriptions = descriptions
}
.collect { entry ->
val (sheet, alterationMaps) = entry
val alterations = alterationMaps[character] ?: emptyList()
val data = alterations
.combine(alterationRepository.getAssignedAlterations(character)) { _, alterations ->
data.alterations = alterations
}
.combine(activeAlterationRepository.getActiveAssignedAlterations(character)) { _, actives ->
data.actives = actives.associate { it.name to true }
}
.collect {
val (sheet, descriptions, alterations, actives) = data
val uio = alterations
.groupBy { alteration -> alteration.source }
.toSortedMap(AlterationRepository.sort(sheet = sheet))
.toSortedMap(AlterationSortUseCase.sort(sheet = sheet))
.map {
AlterationGroupUio(
name = it.key,
alterations = factory.convert(
character = character,
alterations = it.value
alterations = it.value,
description = descriptions,
actives = actives,
).sortedBy { alteration ->
alteration.label
}
)
}
withContext(Dispatchers.Main) {
_alterations.value = data
_alterations.value = uio
}
}
}
@ -74,16 +87,22 @@ class AlterationViewModel @Inject constructor(
}
fun toggleAlteration(alteration: String) {
val status = firebaseRepository.getStatus(character = character, status = alteration)
firebaseRepository.setStatus(character = character, status = alteration, status.not())
val key = AlterationStatus.Key(character = character, alteration = alteration)
firebaseRepository.setAlterationStatus(
key = key,
value = firebaseRepository.getAlterationStatusSnapshot(key = key).switch(),
)
}
fun showAlterationDetail(id: String) {
val alteration = alterationRepository.getAlterations(character = character)
.firstOrNull { it.name == id }
val description = descriptionRepository.find(name = alteration?.name)
suspend fun showAlterationDetail(id: String) {
val alteration = alterationRepository.getAssignedAlterationSnapshot(
character = character,
alteration = id,
)
if (alteration != null) {
val description = descriptionRepository.getDescription(
name = alteration.name,
)
_alterationDetail.value = AlterationDetailUio(
name = id,
original = description?.original,
@ -98,4 +117,11 @@ class AlterationViewModel @Inject constructor(
fun hideAlterationDetail() {
_alterationDetail.value = null
}
private data class AlterationStruct(
var sheet: CharacterSheet? = null,
var descriptions: Map<String, Description> = emptyMap(),
var alterations: List<Alteration> = emptyList(),
var actives: Map<String, Boolean> = emptyMap(),
)
}

View file

@ -88,7 +88,7 @@ class InventoryViewModel @Inject constructor(
}
fun showSkillDetailDialog(item: String) {
val description = descriptionRepository.find(name = item)
val description = descriptionRepository.getDescription(name = item)
if (description != null) {
_dialog.value = SkillDetailUio(

View file

@ -22,6 +22,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
@ -55,6 +56,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberC
import com.pixelized.rplexicon.ui.screens.character.pages.actions.HandleSkillDetailDialog
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.modifier.ddBorder
import kotlinx.coroutines.launch
@Stable
data class CharacterSheetUio(
@ -71,6 +73,7 @@ data class CharacterSheetUio(
fun ProficiencyPage(
viewModel: ProficiencyViewModel = hiltViewModel(),
) {
val scope = rememberCoroutineScope()
val overlay = LocalRollOverlay.current
viewModel.sheet.value?.let { sheet ->
@ -79,19 +82,25 @@ fun ProficiencyPage(
sheet = sheet,
passives = viewModel.skills,
onStats = { stat ->
overlay.prepareRoll(diceThrow = viewModel.statRoll(stat.id))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = viewModel.statRoll(stat.id))
overlay.showOverlay()
}
},
onProficiencies = { proficiency ->
overlay.prepareRoll(diceThrow = viewModel.proficiencyRoll(proficiency.id))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = viewModel.proficiencyRoll(proficiency.id))
overlay.showOverlay()
}
},
onSkillCount = {
viewModel.showSkillEditDialog(item = it)
},
onSkillThrow = {
overlay.prepareRoll(diceThrow = viewModel.onSkillRoll(it.label))
overlay.showOverlay()
scope.launch {
overlay.prepareRoll(diceThrow = viewModel.onSkillRoll(it.label))
overlay.showOverlay()
}
},
onSkillInfo = {
viewModel.showSkillDetailDialog(item = it.label)

View file

@ -8,10 +8,12 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.data.model.CharacterSheet
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.alteration.Alteration
import com.pixelized.rplexicon.data.network.CharacterSheetFire
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
@ -23,6 +25,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio
import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetUioFactory
import com.pixelized.rplexicon.ui.screens.character.factory.SkillFactoryUioFactory
import com.pixelized.rplexicon.ui.screens.character.pages.actions.SkillDetailUio
import com.pixelized.rplexicon.utilitary.extentions.local.toStatus
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
@ -34,7 +37,7 @@ import javax.inject.Inject
class ProficiencyViewModel @Inject constructor(
private val characterRepository: CharacterSheetRepository,
private val firebaseRepository: FirebaseRepository,
private val alterationRepository: AlterationRepository,
private val activeAlterationRepository: ActiveAlterationRepository,
private val characterSheetFactory: CharacterSheetUioFactory,
private val skillRepository: SkillRepository,
private val descriptionRepository: DescriptionRepository,
@ -58,14 +61,16 @@ class ProficiencyViewModel @Inject constructor(
init {
viewModelScope.launch {
launch(Dispatchers.IO) {
launch(Dispatchers.Default) {
val data = SheetStruct()
characterRepository.data
.combine(alterationRepository.assignedAlterations) { sheets, _ -> sheets }
.combine(activeAlterationRepository.getActiveAssignedAlterations(character = character)) { sheets, alterations ->
data.sheet = sheets[character]
data.alterationStatus = alterations.toStatus()
}
.collect {
val characterSheet = it[character]
val (characterSheet, alterations) = data
if (characterSheet != null) {
val alterations =
alterationRepository.getActiveAlterationsStatus(character)
val sheet = characterSheetFactory.convert(
sheet = characterSheet,
status = alterations,
@ -81,7 +86,7 @@ class ProficiencyViewModel @Inject constructor(
}
}
launch(Dispatchers.IO) {
launch(Dispatchers.Default) {
val data = SkillStruct()
characterRepository.data
.combine(skillRepository.skills) { sheets, skills ->
@ -150,7 +155,7 @@ class ProficiencyViewModel @Inject constructor(
fun showSkillDetailDialog(item: String) {
_skillDetailDialog.value = descriptionRepository
.find(name = item)
.getDescription(name = item)
?.let { description ->
SkillDetailUio(
name = item,
@ -183,6 +188,11 @@ class ProficiencyViewModel @Inject constructor(
hideSkillEditDialog()
}
private data class SheetStruct(
var sheet: CharacterSheet? = null,
var alterationStatus: Map<Property, List<Alteration.Status>> = emptyMap(),
)
private data class SkillStruct(
var sheet: CharacterSheet? = null,
var fire: CharacterSheetFire? = null,

View file

@ -16,13 +16,11 @@ 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.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
@ -123,10 +121,10 @@ fun RollOverlay(
viewModel.toggleCardDetail()
},
onAlterationInfo = {
viewModel.showAlterationDetail(it)
scope.launch { viewModel.showAlterationDetail(it) }
},
onAlteration = {
viewModel.onAlteration(it)
scope.launch { viewModel.onAlteration(it) }
},
onThrowVisibilityChange = {
viewModel.onThrowVisibilityChange(it)
@ -472,7 +470,7 @@ private fun animation(density: Density): ContentTransform {
@Stable
interface BlurredRollOverlayHostState : BlurredOverlayHostState {
fun prepareRoll(diceThrow: DiceThrow)
suspend fun prepareRoll(diceThrow: DiceThrow)
}
@Stable
@ -482,7 +480,7 @@ private class BlurredRollOverlayHostStateImpl(
) : BlurredRollOverlayHostState {
override var isOverlayVisible by rollOverlayVisibilityState
override fun prepareRoll(diceThrow: DiceThrow) {
override suspend fun prepareRoll(diceThrow: DiceThrow) {
viewModel.prepareRoll(diceThrow)
}

View file

@ -1,16 +1,21 @@
package com.pixelized.rplexicon.ui.screens.rolls
import android.app.Application
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.business.AlterationSortUseCase
import com.pixelized.rplexicon.business.DiceThrowUseCase
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Description
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.alteration.AlterationStatus
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.repository.character.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
@ -25,15 +30,21 @@ import com.pixelized.rplexicon.ui.screens.rolls.factory.DiceFactory
import com.pixelized.rplexicon.utilitary.extentions.context
import com.pixelized.rplexicon.utilitary.extentions.switch
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class RollOverlayViewModel @Inject constructor(
private val characterSheetRepository: CharacterSheetRepository,
private val alterationRepository: AlterationRepository,
private val activeAlterationRepository: ActiveAlterationRepository,
private val descriptionRepository: DescriptionRepository,
private val rollUseCase: DiceThrowUseCase,
private val diceFactory: DiceFactory,
@ -43,21 +54,24 @@ class RollOverlayViewModel @Inject constructor(
application: Application,
) : AndroidViewModel(application) {
private var sheet: CharacterSheet? = null
private var diceThrow: DiceThrow? = null
private var rollJob: Job? = null
private var alterationJob: Job? = null
private lateinit var diceThrow: DiceThrow
private val _alterations = mutableStateOf<List<AlterationItemUio>>(emptyList())
val alterations: State<List<AlterationGroupUio>> = derivedStateOf {
_alterations.value
.groupBy { it.source }
.toSortedMap(AlterationRepository.sort(sheet = sheet))
.map { entry ->
AlterationGroupUio(
name = entry.key,
alterations = entry.value.sortedBy { it.label }
)
}
}
private val _alterations = MutableStateFlow<List<AlterationItemUio>>(emptyList())
private val _alterationStatusOverride = MutableStateFlow<Map<String, Boolean>>(emptyMap())
val alterations: State<List<AlterationGroupUio>>
@Composable
get() = _alterations.map { data ->
data.groupBy { it.source }
.toSortedMap(AlterationSortUseCase.sort(sheet = sheet))
.map { entry ->
AlterationGroupUio(
name = entry.key,
alterations = entry.value.sortedBy { it.label }
)
}
}.collectAsState(initial = emptyList())
private val _dice = mutableStateOf<RollDiceUio?>(null)
val dice: State<RollDiceUio?> get() = _dice
@ -74,26 +88,61 @@ class RollOverlayViewModel @Inject constructor(
private val _alterationDetail = mutableStateOf<AlterationDetailUio?>(null)
val alterationDetail: State<AlterationDetailUio?> get() = _alterationDetail
fun prepareRoll(diceThrow: DiceThrow) {
suspend fun prepareRoll(diceThrow: DiceThrow) {
this.diceThrow = diceThrow
// hide the throw for other player.
_isThrowHidden.value = diceThrow is DiceThrow.DeathSavingThrow
// hide the detail throw card.
_card.value = null
sheet = characterSheetRepository.find(name = diceThrow.character)
_dice.value = diceFactory.convertDiceThrow(diceThrow)
_alterations.value = alterationFactory.convertDiceThrow(diceThrow)
}
fun onAlteration(id: String) {
_alterations.value = _alterations.value.toMutableList().also { alterations ->
val index = alterations.indexOfFirst { it.label == id }
val alteration = alterations[index].let { it.copy(checked = it.checked.not()) }
alterations.removeAt(index)
alterations.add(index, alteration)
// reset the override status
_alterationStatusOverride.value = emptyMap()
// get the character sheet.
sheet = characterSheetRepository.find(
name = diceThrow.character,
)
// build the dice UIO.
_dice.value = diceFactory.convertDiceThrow(
diceThrow = diceThrow,
)
// listen to alteration + active to build the list of applicable alteration.
alterationJob?.cancel()
alterationJob = viewModelScope.launch(Dispatchers.Default) {
val character = diceThrow.character
val struct = AlterationStruct()
descriptionRepository.data
.combine(activeAlterationRepository.getActiveAssignedAlterations(character = character)) { descriptions, actives ->
struct.descriptions = descriptions
struct.actives = actives.associate { it.name to true }
}
.combine(_alterationStatusOverride) { _, override ->
struct.override = override
}
.collect {
val (description, actives, override) = struct
val alterations = alterationFactory.convertThrowAlterationItem(
diceThrow = diceThrow,
description = description,
checked = actives,
override = override,
)
withContext(Dispatchers.Main) {
_alterations.value = alterations
}
}
}
}
fun onAlteration(id: String) {
val override = _alterationStatusOverride.value.toMutableMap().also {
it[id] = it[id]?.not() ?: firebaseRepository.getAlterationStatusSnapshot(
key = AlterationStatus.Key(character = diceThrow.character, alteration = id)
).value.not()
}
_alterationStatusOverride.value = override
}
fun roll() {
diceThrow?.let { diceThrow ->
diceThrow.let { diceThrow ->
rollJob?.cancel()
rollJob = viewModelScope.launch {
// roll the dice ;)
@ -132,12 +181,14 @@ class RollOverlayViewModel @Inject constructor(
_isThrowHidden.value = hidden
}
fun showAlterationDetail(id: String) {
val alteration = diceThrow?.character?.let { character ->
alterationRepository.getAlterations(character = character).firstOrNull { it.name == id }
}
val description = descriptionRepository.find(name = alteration?.name)
suspend fun showAlterationDetail(id: String) {
val alteration = alterationRepository.getAssignedAlterationSnapshot(
character = diceThrow.character,
alteration = id,
)
val description = descriptionRepository.getDescription(
name = alteration?.name,
)
if (alteration != null) {
_alterationDetail.value = AlterationDetailUio(
name = id,
@ -153,4 +204,10 @@ class RollOverlayViewModel @Inject constructor(
fun hideAlterationDetail() {
_alterationDetail.value = null
}
private data class AlterationStruct(
var descriptions: Map<String, Description> = emptyMap(),
var actives: Map<String, Boolean> = emptyMap(),
var override: Map<String, Boolean> = emptyMap(),
)
}

View file

@ -1,43 +1,46 @@
package com.pixelized.rplexicon.ui.screens.rolls.factory
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.Description
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.repository.authentication.FirebaseRepository
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.repository.character.ActionRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.data.repository.character.SpellRepository
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
class AlterationFactory @Inject constructor(
private val descriptionRepository: DescriptionRepository,
private val actionRepository: ActionRepository,
private val spellRepository: SpellRepository,
private val skillRepository: SkillRepository,
private val alterationRepository: AlterationRepository,
private val firebaseRepository: FirebaseRepository,
) {
fun convert(character: String, alterations: List<Alteration>): List<AlterationItemUio> {
fun convert(
alterations: List<Alteration>,
actives: Map<String, Boolean>,
description: Map<String, Description>,
): List<AlterationItemUio> {
return alterations.map {
AlterationItemUio(
icon = it.icon,
label = it.name,
source = it.source,
subLabel = descriptionRepository.find(
name = it.name,
)?.original,
checked = firebaseRepository.getStatus(
character = character,
status = it.name,
),
subLabel = description[it.name]?.original,
checked = actives[it.name] ?: false,
override = false,
)
}
}
fun convertDiceThrow(diceThrow: DiceThrow): List<AlterationItemUio> {
suspend fun convertThrowAlterationItem(
diceThrow: DiceThrow,
description: Map<String, Description>,
checked: Map<String, Boolean>,
override: Map<String, Boolean>,
): List<AlterationItemUio> {
val properties = when (diceThrow) {
is DiceThrow.Initiative -> listOf(Property.INITIATIVE, Property.DEXTERITY)
is DiceThrow.Strength -> listOf(Property.STRENGTH, Property.STRENGTH_THROW)
@ -139,20 +142,17 @@ class AlterationFactory @Inject constructor(
}
return alterationRepository
.getAlterations(character = diceThrow.character, *properties.toTypedArray())
.map {
.getAssignedAlterations(character = diceThrow.character, *properties.toTypedArray())
.firstOrNull()
?.map { alt ->
AlterationItemUio(
icon = it.icon,
label = it.name,
source = it.source,
checked = firebaseRepository.getStatus(
character = diceThrow.character,
status = it.name,
),
subLabel = descriptionRepository.find(
name = it.name,
)?.original,
icon = alt.icon,
label = alt.name,
subLabel = description[alt.name]?.original,
source = alt.source,
checked = override[alt.name] ?: checked[alt.name] ?: false,
override = override[alt.name]?.let { it != checked[alt.name] } ?: false,
)
}
} ?: emptyList()
}
}

View file

@ -21,6 +21,7 @@ fun rememberRollAlterations() = remember {
subLabel = "Rage",
source = "Barbare",
checked = false,
override = false,
),
),
),
@ -32,7 +33,8 @@ fun rememberRollAlterations() = remember {
label = "Inspiration bardique",
subLabel = "Bardic inspiration",
source = "Barde",
checked = false
checked = false,
override = true,
),
AlterationItemUio(
icon = null,
@ -40,6 +42,7 @@ fun rememberRollAlterations() = remember {
subLabel = "Bless",
source = "Barde",
checked = false,
override = true,
),
),
),
@ -51,7 +54,8 @@ fun rememberRollAlterations() = remember {
label = "Cape de protection",
subLabel = "Cape of protection",
source = "Equipement",
checked = true
checked = true,
override = false,
),
),
),

View file

@ -30,7 +30,7 @@ class SpellDetailViewModel @Inject constructor(
init {
val argument = savedStateHandle.spellDetailArgument
val description = descriptionRepository.find(
val description = descriptionRepository.getDescription(
name = argument.spell,
)
val spell = spellRepository.findSpell(

View file

@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -50,6 +51,7 @@ import com.pixelized.rplexicon.ui.screens.summary.pages.statistic.StatisticSumma
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
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -68,6 +70,7 @@ fun SummaryScreen(
val extendPassives = rememberSaveable { mutableStateOf(true) }
val extendSpells = rememberSaveable { mutableStateOf(true) }
val extendStatus = rememberSaveable { mutableStateOf(true) }
val scope = rememberCoroutineScope()
Surface(
modifier = Modifier.fillMaxSize(),
@ -101,10 +104,12 @@ fun SummaryScreen(
onSpells = { extendSpells.value = it },
onStatus = { extendStatus.value = it },
onAlteration = {
statisticsViewModel.showAlterationDetail(
name = it.id,
character = it.character,
)
scope.launch {
statisticsViewModel.showAlterationDetail(
name = it.id,
character = it.character,
)
}
}
)
},

View file

@ -17,6 +17,7 @@ import com.pixelized.rplexicon.utilitary.extentions.context
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -62,10 +63,10 @@ class StatisticViewModel @Inject constructor(
_detail.value = null
}
fun showAlterationDetail(name: String, character: String,) {
val alteration = alterationRepository.getAlterations(character = character)
.firstOrNull { it.name == name }
val description = descriptionRepository.find(name = alteration?.name)
suspend fun showAlterationDetail(name: String, character: String) {
val alteration = alterationRepository.getAssignedAlterations(character = character)
.firstOrNull()?.firstOrNull { it.name == name }
val description = descriptionRepository.getDescription(name = alteration?.name)
if (alteration != null) {
_alterationDetail.value = AlterationDetailUio(

View file

@ -3,13 +3,13 @@ package com.pixelized.rplexicon.ui.screens.summary.pages.statistic
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Property
import com.pixelized.rplexicon.data.model.alteration.Alteration
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.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.ui.screens.summary.composable.AttributesSummaryUio
import com.pixelized.rplexicon.ui.screens.summary.composable.CharacteristicsSummaryUio
@ -28,6 +28,7 @@ import com.pixelized.rplexicon.utilitary.extentions.local.highestSpellLevel
import com.pixelized.rplexicon.utilitary.extentions.local.passivesBonus
import com.pixelized.rplexicon.utilitary.extentions.local.spell
import com.pixelized.rplexicon.utilitary.extentions.local.sum
import com.pixelized.rplexicon.utilitary.extentions.local.toStatus
import com.pixelized.rplexicon.utilitary.extentions.masteryMultiplier
import com.pixelized.rplexicon.utilitary.extentions.modifier
import com.pixelized.rplexicon.utilitary.extentions.toLabel
@ -42,7 +43,7 @@ import kotlin.math.max
class SummaryFactory @Inject constructor(
private val characterSheetRepository: CharacterSheetRepository,
private val alterationRepository: AlterationRepository,
private val activeAlterationRepository: ActiveAlterationRepository,
private val firebaseRepository: FirebaseRepository,
) {
fun fetchSummary(scope: CoroutineScope): StatisticSummaryUio {
@ -141,15 +142,19 @@ class SummaryFactory @Inject constructor(
scope.launch(Dispatchers.IO) {
val data = DataStruct()
characterSheetRepository.data
.combine(alterationRepository.assignedAlterations) { sheets, _ ->
.combine(activeAlterationRepository.getActiveAlterations()) { sheets, alterations ->
data.sheets = sheets
data.alterations = alterations
}
.combine(firebaseRepository.getCharacter()) { _, fire ->
data.fires = fire.characters
.combine(firebaseRepository.getCharacters()) { _, fire ->
data.fires = fire
}
.collect {
val (sheets, fires) = data
val (sheets, alterations, fires) = data
val characters = sheets.keys.sorted()
val alterationStatus = sheets.values.associate { sheet ->
sheet.name to alterations[sheet.name]?.toStatus()
}
// Update the max spell slot card.
val highestSpellSlot = sheets.values.maxOf { it.highestSpellLevel() }
@ -184,7 +189,7 @@ class SummaryFactory @Inject constructor(
// Update the attributes
sheets.values.forEach { sheet ->
val status = alterationRepository.getActiveAlterationsStatus(sheet.name)
val status = alterationStatus[sheet.name] ?: emptyMap()
val fire = fires[sheet.name]
val dexterity = sheet.dexterity + status[Property.DEXTERITY].sum
@ -225,7 +230,7 @@ class SummaryFactory @Inject constructor(
// Update the Characteristics
sheets.values.forEach { sheet ->
val status = alterationRepository.getActiveAlterationsStatus(sheet.name)
val status = alterationStatus[sheet.name] ?: emptyMap()
// Update the Characteristics
val strengthLabel = label(
label = "${sheet.strength + status[Property.STRENGTH].sum}",
@ -261,7 +266,7 @@ class SummaryFactory @Inject constructor(
// Update the SavingThrows
sheets.values.forEach { sheet ->
val status = alterationRepository.getActiveAlterationsStatus(sheet.name)
val status = alterationStatus[sheet.name] ?: emptyMap()
val proficiency = (sheet.proficiency + status[Property.PROFICIENCY].sum)
val strength = sheet.strength + status[Property.STRENGTH].sum
@ -331,7 +336,7 @@ class SummaryFactory @Inject constructor(
// Update proficiencies
sheets.values.forEach { sheet ->
val status = alterationRepository.getActiveAlterationsStatus(sheet.name)
val status = alterationStatus[sheet.name] ?: emptyMap()
val proficiency = (sheet.proficiency + status[Property.PROFICIENCY].sum)
val strength = sheet.strength + status[Property.STRENGTH].sum
@ -494,7 +499,7 @@ class SummaryFactory @Inject constructor(
// Update passives
sheets.values.forEach { sheet ->
val status = alterationRepository.getActiveAlterationsStatus(sheet.name)
val status = alterationStatus[sheet.name] ?: emptyMap()
val proficiency = (sheet.proficiency + status[Property.PROFICIENCY].sum)
val intelligence = sheet.intelligence + status[Property.INTELLIGENCE].sum
@ -614,9 +619,8 @@ class SummaryFactory @Inject constructor(
// Update the Status
sheets.values.forEach { sheet ->
val status = alterationRepository
.getActiveAlterations(character = sheet.name)
.filterDndStatus(character = sheet.name)
val status = alterations[sheet.name]
?.filterDndStatus(character = sheet.name) ?: emptyList()
withContext(Dispatchers.Main) {
statuses.get(sheet = sheet)?.value = status
}
@ -728,10 +732,12 @@ class SummaryFactory @Inject constructor(
private class DataStruct {
lateinit var sheets: Map<String, CharacterSheet>
lateinit var alterations: Map<String, List<Alteration>>
lateinit var fires: Map<String, CharacterSheetFire>
operator fun component1() = sheets
operator fun component2() = fires
operator fun component2() = alterations
operator fun component3() = fires
}
private class HeaderStatStruct {

View file

@ -4,7 +4,7 @@ import android.net.Uri
import androidx.annotation.DrawableRes
import com.pixelized.rplexicon.BuildConfig
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.utilitary.extentions.local.amateurism
import com.pixelized.rplexicon.utilitary.extentions.local.mastery
import kotlin.math.abs

View file

@ -1,6 +1,6 @@
package com.pixelized.rplexicon.utilitary.extentions.local
import com.pixelized.rplexicon.data.model.Alteration
import com.pixelized.rplexicon.data.model.alteration.Alteration
import com.pixelized.rplexicon.data.model.Property
fun List<Alteration>.toStatus(): Map<Property, List<Alteration.Status>> {

View file

@ -112,6 +112,7 @@
<string name="character_sheet_title_rage">Rage</string>
<string name="character_sheet_title_inspiration">Inspiration</string>
<string name="character_sheet_title_conduit">Conduit Divin</string>
<string name="character_sheet_title_wild_shape">Forme sauvage</string>
<string name="character_sheet_title_initiative">Initiative</string>
<string name="character_sheet_title_proficiency_bonus">Bonus de maîtrise</string>
<string name="character_sheet_title_characteristic">Caractéristiques</string>

View file

@ -118,6 +118,7 @@
<string name="character_sheet_title_rage">Rage</string>
<string name="character_sheet_title_inspiration">Inspiration</string>
<string name="character_sheet_title_conduit">Divine conduit</string>
<string name="character_sheet_title_wild_shape">Wild shape</string>
<string name="character_sheet_title_initiative">Initiative</string>
<string name="character_sheet_title_proficiency_bonus">Proficiency bonus</string>
<string name="character_sheet_title_saving_throws">Saving Throws</string>