Add alteration status to the portrait

This commit is contained in:
Thomas Andres Gomez 2025-05-08 13:43:30 +02:00
parent 2c04559bb7
commit e81b66e725
23 changed files with 2719 additions and 210 deletions

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@ import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkFactory import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory

View file

@ -1,6 +1,5 @@
package com.pixelized.desktop.lwa.repository.alteration package com.pixelized.desktop.lwa.repository.alteration
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository import com.pixelized.desktop.lwa.repository.inventory.InventoryRepository
import com.pixelized.desktop.lwa.repository.item.ItemRepository import com.pixelized.desktop.lwa.repository.item.ItemRepository
@ -23,24 +22,23 @@ class AlterationRepository(
private val alterationStore: AlterationStore, private val alterationStore: AlterationStore,
private val inventoryRepository: InventoryRepository, private val inventoryRepository: InventoryRepository,
private val itemRepository: ItemRepository, private val itemRepository: ItemRepository,
campaignRepository: CampaignRepository, private val characterRepository: CharacterSheetRepository,
characterRepository: CharacterSheetRepository,
) { ) {
private val scope = CoroutineScope(Dispatchers.IO + Job()) private val scope = CoroutineScope(Dispatchers.IO + Job())
val alterationFlow get() = alterationStore.alterationsFlow val alterationFlow get() = alterationStore.alterationsFlow
/** /**
* This flow transform the campaign instance (player + npc) into a * This flow build a Map of CharacterSheetId to a list of AlterationId.
* Map<CharacterSheetId, List<AlterationId>> from the CharacterSheet Alteration * Map<CharacterSheetId, List<AlterationId>> from the CharacterSheet Alterations.
* It is used by an other flow to build the FieldAlteration cache. * It is used by an other flow to build the FieldAlteration cache.
*/ */
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val campaignCharacterAlterationFlow: Flow<Map<String, List<String>>> = private val charactersAlterationFlow: Flow<Map<String, List<String>>> =
campaignRepository.campaignFlow() characterRepository.characterSheetPreviewFlow()
.flatMapLatest { campaign -> .flatMapLatest { preview ->
val characters = campaign.instances.map { val characters = preview.map {
characterRepository.characterDetailFlow(characterSheetId = it) characterRepository.characterDetailFlow(characterSheetId = it.characterSheetId)
} }
combine(characters) { sheets: Array<CharacterSheet?> -> combine(characters) { sheets: Array<CharacterSheet?> ->
sheets sheets
@ -51,20 +49,20 @@ class AlterationRepository(
} }
/** /**
* This flow transform the campaign instance (player + npc) into a * This flow build a Map of CharacterSheetId to a list of AlterationId.
* Map<CharacterSheetId, List<AlterationId>> from the character inventory items Alteration * Map<CharacterSheetId, List<AlterationId>> from the character inventory items Alterations.
* It is used by an other flow to build the FieldAlteration cache. * It is used by an other flow to build the FieldAlteration cache.
*/ */
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val campaignCharacterInventoryAlterationFlow: Flow<Map<String, List<String>>> = private val inventoriesAlterationFlow: Flow<Map<String, List<String>>> =
campaignRepository.campaignFlow() characterRepository.characterSheetPreviewFlow()
.flatMapLatest { campaign -> .flatMapLatest { previews ->
val equippedItems = campaign.instances.map { characterSheetId -> val equippedItems = previews.map {
combine( combine(
inventoryRepository.equippedItemsFlow(characterSheetId = characterSheetId), inventoryRepository.equippedItemsFlow(characterSheetId = it.characterSheetId),
itemRepository.itemFlow(), itemRepository.itemFlow(),
) { equipments, items -> ) { equipments, items ->
characterSheetId to equipments.flatMap { equipment -> it.characterSheetId to equipments.flatMap { equipment ->
items[equipment.itemId]?.alterations ?: emptyList() items[equipment.itemId]?.alterations ?: emptyList()
} }
} }
@ -76,18 +74,60 @@ class AlterationRepository(
} }
} }
private val activeAlterationMapFlow: StateFlow<Map<String, Map<String, List<FieldAlteration>>>> = /**
* This flow build a Map of CharacterSheetId to a list of AlterationId.
* Map<CharacterSheetId, List<AlterationId>> from the character sheet + inventory items Alterations.
* It is used by an other flow to build the FieldAlteration cache.
*/
private val activeAlterationIdsFlow: StateFlow<Map<String, List<String>>> = combine(
charactersAlterationFlow,
inventoriesAlterationFlow,
) { characters, inventories ->
val characterSheetIds = characters.keys + inventories.keys
characterSheetIds.associateWith {
characters.getOrElse(it) { emptyList() } + inventories.getOrElse(it) { emptyList() }
}
}.stateIn(
scope = scope,
started = SharingStarted.Eagerly,
initialValue = emptyMap(),
)
/**
* This flow build a Map of CharacterSheetId to a list of Alteration.
* Map<CharacterSheetId, List<Alteration>> from the character sheet + inventory items Alterations.
*/
private val activeAlterationsFlow: StateFlow<Map<String, List<Alteration>>> =
combine( combine(
characterRepository.characterSheetPreviewFlow(),
alterationStore.alterationsFlow, alterationStore.alterationsFlow,
campaignCharacterAlterationFlow, activeAlterationIdsFlow,
campaignCharacterInventoryAlterationFlow, ) { preview, alterations, activeAlterationIds ->
) { alterations, characters, inventories -> preview.map { it.characterSheetId }.associateWith { characterSheetId ->
val characterSheetIds = characters.keys + inventories.keys activeAlterationIds
characterSheetIds.associateWith { .getOrElse(characterSheetId) { emptyList() }
.mapNotNull { alterations[it] }
}
}.stateIn(
scope = scope,
started = SharingStarted.Eagerly,
initialValue = emptyMap(),
)
/**
* This flow build a Map of CharacterSheetId to a Map of AlterationId and FieldAlteration.
* Map<CharacterSheetId, Map<AlterationId, Alteration>> from the character sheet + inventory items Alterations.
*/
private val activeFieldAlterationsFlow: StateFlow<Map<String, Map<String, List<FieldAlteration>>>> =
combine(
characterRepository.characterSheetPreviewFlow(),
alterationStore.alterationsFlow,
activeAlterationIdsFlow,
) { previews, alterations, activeAlterationIds ->
previews.map { it.characterSheetId }.associateWith {
transformToAlterationFieldMap( transformToAlterationFieldMap(
alterations = alterations, alterations = alterations,
actives = characters.getOrElse(it) { emptyList() } + actives = activeAlterationIds.getOrElse(it) { emptyList() },
inventories.getOrElse(it) { emptyList() }
) )
} }
}.stateIn( }.stateIn(
@ -106,16 +146,28 @@ class AlterationRepository(
return alterationFlow.value[alterationId] return alterationFlow.value[alterationId]
} }
fun fieldAlterations( fun activeFieldAlterations(
characterSheetId: String, characterSheetId: String,
): Map<String, List<FieldAlteration>> { ): Map<String, List<FieldAlteration>> {
return activeAlterationMapFlow.value[characterSheetId] ?: emptyMap() return activeFieldAlterationsFlow.value[characterSheetId] ?: emptyMap()
} }
fun fieldAlterationsFlow( fun activeFieldAlterationsFlow(
characterSheetId: String, characterSheetId: String,
): Flow<Map<String, List<FieldAlteration>>> { ): Flow<Map<String, List<FieldAlteration>>> {
return activeAlterationMapFlow.map { it[characterSheetId] ?: emptyMap() } return activeFieldAlterationsFlow.map { it[characterSheetId] ?: emptyMap() }
}
fun activeAlterations(
characterSheetId: String,
): List<Alteration> {
return activeAlterationsFlow.value[characterSheetId] ?: emptyList()
}
fun activeAlterationsFlow(
characterSheetId: String,
): Flow<List<Alteration>> {
return activeAlterationsFlow.map { it[characterSheetId] ?: emptyList() }
} }
@Throws @Throws

View file

@ -32,7 +32,7 @@ class CharacterSheetCharacteristicDialogFactory(
if (characterSheet == null) return null if (characterSheet == null) return null
val alterations: Map<String, List<FieldAlteration>> = alterationRepository.fieldAlterations( val alterations: Map<String, List<FieldAlteration>> = alterationRepository.activeFieldAlterations(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )

View file

@ -49,7 +49,7 @@ class CharacterSheetCharacteristicDialogViewModel(
if (characterSheet == null) return if (characterSheet == null) return
val alterations = alterationRepository.fieldAlterations( val alterations = alterationRepository.activeFieldAlterations(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )

View file

@ -29,7 +29,7 @@ class CharacterSheetDiminishedDialogFactory(
if (characterSheet == null) return null if (characterSheet == null) return null
val alterations: Map<String, List<FieldAlteration>> = alterationRepository.fieldAlterations( val alterations: Map<String, List<FieldAlteration>> = alterationRepository.activeFieldAlterations(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )

View file

@ -103,7 +103,7 @@ class RollViewModel(
if (characterSheet == null) return if (characterSheet == null) return
val alterations = alterationRepository.fieldAlterations( val alterations = alterationRepository.activeFieldAlterations(
characterSheetId = roll.characterSheetId, characterSheetId = roll.characterSheetId,
) )

View file

@ -1,41 +0,0 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
class CharacterRibbonFactory(
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
) {
fun convertToPlayerPortraitUio(
characterSheet: CharacterSheet?,
alterations: Map<String, List<FieldAlteration>>,
hideOverruled: Boolean,
enableCharacterSheet: Boolean,
enableCharacterStats: Boolean,
): CharacterPortraitUio? {
if (characterSheet == null) return null
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
characterSheet = characterSheet,
alterations = alterations,
)
return CharacterPortraitUio(
characterSheetId = characterSheet.id,
portrait = alteredCharacterSheet.thumbnail,
name = alteredCharacterSheet.name,
levelUp = alteredCharacterSheet.shouldLevelUp,
hideOverruled = hideOverruled,
enableDetail = enableCharacterSheet,
stats = takeIf { enableCharacterStats }?.let {
CharacterPortraitUio.StatsDetail(
hp = alteredCharacterSheet.maxHp - alteredCharacterSheet.damage,
maxHp = alteredCharacterSheet.maxHp,
pp = alteredCharacterSheet.maxPp - alteredCharacterSheet.fatigue,
maxPp = alteredCharacterSheet.maxPp,
)
},
)
}
}

View file

@ -51,7 +51,7 @@ class CharacterDetailHeaderFactory(
): StateFlow<CharacterDetailHeaderUio?> { ): StateFlow<CharacterDetailHeaderUio?> {
return combine( return combine(
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId), characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId), alterationRepository.activeFieldAlterationsFlow(characterSheetId = characterSheetId),
settingRepository.settingsFlow() settingRepository.settingsFlow()
) { characterSheet, alterations, settings -> ) { characterSheet, alterations, settings ->
convertToCharacterDetailHeaderUio( convertToCharacterDetailHeaderUio(

View file

@ -48,7 +48,7 @@ class CharacterDetailSheetFactory(
): StateFlow<CharacterDetailSheetUio?> { ): StateFlow<CharacterDetailSheetUio?> {
return combine( return combine(
characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId), characterSheetRepository.characterDetailFlow(characterSheetId = characterSheetId),
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId), alterationRepository.activeFieldAlterationsFlow(characterSheetId = characterSheetId),
) { characterSheet, alterations -> ) { characterSheet, alterations ->
convertToCharacterDetailSheetUio( convertToCharacterDetailSheetUio(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,

View file

@ -0,0 +1,67 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlterationUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortraitUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio
import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory
import com.pixelized.shared.lwa.model.alteration.Alteration
import com.pixelized.shared.lwa.model.alteration.FieldAlteration
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
class CharacterRibbonFactory(
private val alteredCharacterSheetFactory: AlteredCharacterSheetFactory,
) {
fun convertToPlayerPortraitUio(
characterSheet: CharacterSheet?,
alterations: List<Alteration>,
fieldAlterations: Map<String, List<FieldAlteration>>,
hideOverruled: Boolean,
enableCharacterSheet: Boolean,
enableCharacterStats: Boolean,
): CharacterRibbonUio? {
if (characterSheet == null) return null
val alteredCharacterSheet = alteredCharacterSheetFactory.sheet(
characterSheet = characterSheet,
alterations = fieldAlterations,
)
val status = alterations.map { alteration ->
CharacterRibbonAlterationUio(
icon = "https://bg3.wiki/w/images/2/2d/Map_Tutorial_Map_Icon.png",
tooltips = BasicTooltipUio(
title = alteration.metadata.name,
description = alteration.metadata.description,
),
)
}.fold(
initial = mutableListOf<MutableList<CharacterRibbonAlterationUio>>(),
operation = { acc, item ->
if (acc.isEmpty() || acc.last().size == 5) acc.add(mutableListOf())
acc.last().add(item)
acc
}
)
return CharacterRibbonUio(
characterSheetId = characterSheet.id,
hideOverruled = hideOverruled,
portrait = CharacterRibbonPortraitUio(
portrait = alteredCharacterSheet.thumbnail,
name = alteredCharacterSheet.name,
levelUp = alteredCharacterSheet.shouldLevelUp,
enableDetail = enableCharacterSheet,
stats = takeIf { enableCharacterStats }?.let {
CharacterRibbonPortraitUio.StatsDetail(
hp = alteredCharacterSheet.maxHp - alteredCharacterSheet.damage,
maxHp = alteredCharacterSheet.maxHp,
pp = alteredCharacterSheet.maxPp - alteredCharacterSheet.fatigue,
maxPp = alteredCharacterSheet.maxPp,
)
},
),
status = status,
)
}
}

View file

@ -1,11 +1,13 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
@ -14,10 +16,11 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRollUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRollUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.protocol.websocket.RollEvent.Critical
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -38,7 +41,7 @@ abstract class CharacterRibbonViewModel(
private val ribbonFactory: CharacterRibbonFactory, private val ribbonFactory: CharacterRibbonFactory,
) : ViewModel() { ) : ViewModel() {
private val rolls = hashMapOf<String, MutableState<CharacterPortraitRollUio?>>() private val rolls = hashMapOf<String, MutableState<CharacterRibbonRollUio?>>()
abstract fun fetch( abstract fun fetch(
campaign: Campaign, campaign: Campaign,
@ -66,7 +69,7 @@ abstract class CharacterRibbonViewModel(
* Then sort the result. * Then sort the result.
*/ */
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
val characters: StateFlow<List<CharacterPortraitUio>> = combine( val characters: StateFlow<List<CharacterRibbonUio>> = combine(
settingsRepository.settingsFlow(), settingsRepository.settingsFlow(),
campaignRepository.campaignFlow(), campaignRepository.campaignFlow(),
) { settings, campaign -> campaign to settings } ) { settings, campaign -> campaign to settings }
@ -76,15 +79,17 @@ abstract class CharacterRibbonViewModel(
val characterSheetIds = fetch(campaign, settings) val characterSheetIds = fetch(campaign, settings)
when (characterSheetIds.isEmpty()) { when (characterSheetIds.isEmpty()) {
true -> flowOf(emptyList()) true -> flowOf(emptyList())
else -> combine<CharacterPortraitUio?, List<CharacterPortraitUio>>( else -> combine<CharacterRibbonUio?, List<CharacterRibbonUio>>(
flows = characterSheetIds.map { characterSheetId -> flows = characterSheetIds.map { characterSheetId ->
combine( combine(
characterRepository.characterDetailFlow(characterSheetId = characterSheetId), characterRepository.characterDetailFlow(characterSheetId = characterSheetId),
alterationRepository.fieldAlterationsFlow(characterSheetId = characterSheetId), alterationRepository.activeAlterationsFlow(characterSheetId = characterSheetId),
) { sheet, alterations -> alterationRepository.activeFieldAlterationsFlow(characterSheetId = characterSheetId),
) { sheet, alterations, fieldAlterations ->
ribbonFactory.convertToPlayerPortraitUio( ribbonFactory.convertToPlayerPortraitUio(
characterSheet = sheet, characterSheet = sheet,
alterations = alterations, alterations = alterations,
fieldAlterations = fieldAlterations,
hideOverruled = hideOverruled, hideOverruled = hideOverruled,
enableCharacterSheet = enableCharacterSheet(settings = settings), enableCharacterSheet = enableCharacterSheet(settings = settings),
enableCharacterStats = enableCharacterStats(settings = settings), enableCharacterStats = enableCharacterStats(settings = settings),
@ -93,7 +98,7 @@ abstract class CharacterRibbonViewModel(
}, },
transform = { headers -> transform = { headers ->
headers.mapNotNull { it } headers.mapNotNull { it }
.sortedWith(compareBy(Collator.getInstance()) { it.name }) .sortedWith(compareBy(Collator.getInstance()) { it.portrait.name })
.toList() .toList()
} }
) )
@ -113,8 +118,11 @@ abstract class CharacterRibbonViewModel(
@Stable @Stable
fun roll( fun roll(
characterSheetId: String, characterSheetId: String,
): State<CharacterPortraitRollUio?> { ): State<CharacterRibbonRollUio?> {
val state = rolls.getOrPut(characterSheetId) { mutableStateOf(null) } val colorScheme = MaterialTheme.lwa.colorScheme
val state = remember(characterSheetId) {
rolls.getOrPut(characterSheetId) { mutableStateOf(null) }
}
LaunchedEffect(characterSheetId) { LaunchedEffect(characterSheetId) {
combine( combine(
@ -122,10 +130,17 @@ abstract class CharacterRibbonViewModel(
rollHistoryRepository.rolls(), rollHistoryRepository.rolls(),
) { settings, roll -> ) { settings, roll ->
if (settings.portrait.dynamicDice && characterSheetId == roll.characterSheetId) { if (settings.portrait.dynamicDice && characterSheetId == roll.characterSheetId) {
state.value = CharacterPortraitRollUio( state.value = CharacterRibbonRollUio(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
value = roll.rollValue, value = roll.rollValue,
label = roll.resultLabel?.split(" ")?.joinToString(separator = "\n") { it } tint = when (roll.critical) {
Critical.CRITICAL_SUCCESS -> colorScheme.portrait.criticalSuccess
Critical.SPECIAL_SUCCESS -> colorScheme.portrait.spacialSuccess
Critical.SUCCESS -> colorScheme.portrait.success
Critical.FAILURE -> colorScheme.portrait.failure
Critical.CRITICAL_FAILURE -> colorScheme.portrait.criticalFailure
null -> colorScheme.portrait.default
},
) )
} }
}.launchIn(this) }.launchIn(this)

View file

@ -0,0 +1,39 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.EaseOutCirc
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlin.math.max
import kotlin.math.min
@Composable
fun BloodOverlay(
modifier: Modifier = Modifier,
bloodColor: Color = MaterialTheme.lwa.colorScheme.portrait.blood,
maxHp: Float,
hp: Float,
) {
val animatedRatio = animateFloatAsState(
targetValue = min(maxHp, max(0f, (maxHp - hp) / maxHp)),
animationSpec = tween(durationMillis = 350, easing = EaseOutCirc)
)
val animatedColor = animateColorAsState(
targetValue = bloodColor.copy(alpha = ((maxHp - hp) / maxHp) / 4f + .25f)
)
Box(
modifier = modifier
.fillMaxWidth()
.fillMaxHeight(fraction = animatedRatio.value)
.background(color = animatedColor.value)
)
}

View file

@ -0,0 +1,94 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipPlacement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import coil3.PlatformContext
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipLayout
import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio
import com.pixelized.desktop.lwa.ui.theme.lwa
@Stable
data class CharacterRibbonAlterationUio(
val icon: String,
val tooltips: BasicTooltipUio?,
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CharacterRibbonAlteration(
modifier: Modifier = Modifier,
size: DpSize = MaterialTheme.lwa.size.portrait.minimized,
direction: LayoutDirection,
status: List<List<CharacterRibbonAlterationUio>>,
) {
val currentDirection: LayoutDirection = LocalLayoutDirection.current
CompositionLocalProvider(
LocalLayoutDirection provides direction
) {
Row(
modifier = Modifier
.size(size = size)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
CompositionLocalProvider(
LocalLayoutDirection provides currentDirection
) {
status.forEach { columns ->
Column(
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
) {
columns.forEach {
BasicTooltipLayout(
delayMillis = 0,
tooltip = it.tooltips,
tooltipPlacement = remember(currentDirection) {
TooltipPlacement.ComponentRect(
anchor = when(direction) {
LayoutDirection.Ltr -> Alignment.TopStart
LayoutDirection.Rtl -> Alignment.TopEnd
},
alignment = when(direction) {
LayoutDirection.Ltr -> Alignment.BottomEnd
LayoutDirection.Rtl -> Alignment.BottomStart
},
)
},
content = {
AsyncImage(
modifier = Modifier.size(24.dp),
model = ImageRequest.Builder(context = PlatformContext.INSTANCE)
.data(data = it.icon)
.size(size = 48)
.build(),
filterQuality = FilterQuality.High,
contentDescription = null,
)
}
)
}
}
}
}
}
}
}

View file

@ -1,4 +1,4 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@ -37,7 +37,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerButton import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@ -54,12 +53,10 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@Stable @Stable
data class CharacterPortraitUio( data class CharacterRibbonPortraitUio(
val characterSheetId: String,
val portrait: String?, val portrait: String?,
val name: String, val name: String,
val levelUp: Boolean, val levelUp: Boolean,
val hideOverruled: Boolean,
val enableDetail: Boolean, val enableDetail: Boolean,
val stats: StatsDetail?, val stats: StatsDetail?,
) { ) {
@ -74,32 +71,31 @@ data class CharacterPortraitUio(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun CharacterPortrait( fun CharacterRibbonPortrait(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
size: DpSize = MaterialTheme.lwa.size.portrait.minimized, size: DpSize = MaterialTheme.lwa.size.portrait.minimized,
levelUpOffset: Dp = 9.dp, levelUpOffset: Dp = 9.dp,
character: CharacterPortraitUio, character: CharacterRibbonPortraitUio,
onCharacterLeftClick: (characterSheetId: String) -> Unit, onCharacterLeftClick: () -> Unit,
onCharacterRightClick: (characterSheetId: String) -> Unit, onCharacterRightClick: () -> Unit,
onLevelUp: (characterSheetId: String) -> Unit, onLevelUp: () -> Unit,
) { ) {
val colorScheme = MaterialTheme.lwa.colorScheme val colorScheme = MaterialTheme.lwa.colorScheme
Box( Box(
modifier = modifier modifier = modifier
.graphicsLayer { if (character.hideOverruled) this.alpha = 0.3f }
.size(size = size) .size(size = size)
.clip(shape = MaterialTheme.lwa.shapes.portrait) .clip(shape = MaterialTheme.lwa.shapes.portrait)
.background(color = colorScheme.elevated.base1dp) .background(color = colorScheme.elevated.base1dp)
.onClick( .onClick(
matcher = PointerMatcher.mouse(PointerButton.Primary), matcher = PointerMatcher.mouse(PointerButton.Primary),
enabled = character.enableDetail, enabled = character.enableDetail,
onClick = { onCharacterLeftClick(character.characterSheetId) } onClick = onCharacterLeftClick,
) )
.onClick( .onClick(
matcher = PointerMatcher.mouse(PointerButton.Secondary), matcher = PointerMatcher.mouse(PointerButton.Secondary),
enabled = character.enableDetail, enabled = character.enableDetail,
onClick = { onCharacterRightClick(character.characterSheetId) } onClick = onCharacterRightClick,
), ),
) { ) {
AnimatedContent( AnimatedContent(
@ -125,7 +121,7 @@ fun CharacterPortrait(
exit = fadeOut(), exit = fadeOut(),
) { ) {
IconButton( IconButton(
onClick = { onLevelUp(character.characterSheetId) }, onClick = onLevelUp,
) { ) {
ArrowShape( ArrowShape(
color = MaterialTheme.lwa.colorScheme.portrait.levelUp, color = MaterialTheme.lwa.colorScheme.portrait.levelUp,
@ -194,25 +190,3 @@ fun CharacterPortrait(
} }
} }
} }
@Composable
private fun BloodOverlay(
modifier: Modifier = Modifier,
bloodColor: Color = MaterialTheme.lwa.colorScheme.portrait.blood,
maxHp: Float,
hp: Float,
) {
val animatedRatio = animateFloatAsState(
targetValue = min(maxHp, max(0f, (maxHp - hp) / maxHp)),
animationSpec = tween(durationMillis = 350, easing = EaseOutCirc)
)
val animatedColor = animateColorAsState(
targetValue = bloodColor.copy(alpha = ((maxHp - hp) / maxHp) / 4f + .25f)
)
Box(
modifier = modifier
.fillMaxWidth()
.fillMaxHeight(fraction = animatedRatio.value)
.background(color = animatedColor.value)
)
}

View file

@ -1,7 +1,8 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform import androidx.compose.animation.SizeTransform
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.Spring
@ -9,16 +10,11 @@ import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width
import androidx.compose.foundation.onClick
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
@ -28,48 +24,43 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.shared.lwa.model.campaign.Campaign
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@Stable @Stable
data class CharacterPortraitRollUio( data class CharacterRibbonRollUio(
val characterSheetId: String, val characterSheetId: String,
val value: Int?, val value: Int?,
val label: String?, val tint: Color?,
) )
@Stable @Stable
data class CharacterPortraitRollAnimation( data class CharacterRibbonRollAnimation(
val alpha: Animatable<Float, AnimationVector1D> = Animatable(0f),
val rotation: Animatable<Float, AnimationVector1D> = Animatable(0f), val rotation: Animatable<Float, AnimationVector1D> = Animatable(0f),
val scale: Animatable<Float, AnimationVector1D> = Animatable(1f), val scale: Animatable<Float, AnimationVector1D> = Animatable(1f),
) )
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun CharacterPortraitRoll( fun CharacterRibbonRoll(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
size: DpSize = MaterialTheme.lwa.size.portrait.minimized, size: DpSize = MaterialTheme.lwa.size.portrait.minimized,
value: CharacterPortraitRollUio?, value: CharacterRibbonRollUio?,
onLeftClick: (CharacterPortraitRollUio) -> Unit,
onRightClick: (CharacterPortraitRollUio) -> Unit,
) { ) {
AnimatedContent( AnimatedContent(
modifier = modifier modifier = modifier
.size(size = size) .width(width = size.width)
.aspectRatio(ratio = 1f)
.graphicsLayer { clip = false }, .graphicsLayer { clip = false },
targetState = value, targetState = value,
transitionSpec = { transitionSpec = {
@ -79,6 +70,7 @@ fun CharacterPortraitRoll(
} }
) { ) {
val animation = diceIconAnimation(key = it ?: Unit) val animation = diceIconAnimation(key = it ?: Unit)
val color = animateColorAsState(targetValue = it?.tint ?: Color.Transparent)
Box( Box(
modifier = Modifier.graphicsLayer { modifier = Modifier.graphicsLayer {
@ -91,23 +83,13 @@ fun CharacterPortraitRoll(
Icon( Icon(
modifier = Modifier modifier = Modifier
.graphicsLayer { .graphicsLayer {
this.alpha = 0.8f
this.rotationZ = animation.rotation.value this.rotationZ = animation.rotation.value
} }
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(1f) .aspectRatio(1f)
.padding(all = 8.dp) .padding(all = 16.dp),
.clip(shape = CircleShape)
.onClick(
matcher = PointerMatcher.mouse(PointerButton.Secondary),
onClick = { onRightClick(it) },
)
.clickable {
onLeftClick(it)
}
.padding(all = 8.dp),
painter = painterResource(Res.drawable.ic_d20_24dp), painter = painterResource(Res.drawable.ic_d20_24dp),
tint = MaterialTheme.colors.primary, tint = color.value,
contentDescription = null, contentDescription = null,
) )
Text( Text(
@ -123,21 +105,15 @@ fun CharacterPortraitRoll(
color = MaterialTheme.colors.onSurface, color = MaterialTheme.colors.onSurface,
text = it.value.toString() text = it.value.toString()
) )
Text(
modifier = Modifier.padding(top = 84.dp),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.caption,
text = it.label ?: "",
)
} }
} }
} }
} }
@Composable @Composable
private fun diceIconAnimation(key: Any = Unit): CharacterPortraitRollAnimation { private fun diceIconAnimation(key: Any = Unit): CharacterRibbonRollAnimation {
val animation = remember(key) { val animation = remember(key) {
CharacterPortraitRollAnimation() CharacterRibbonRollAnimation()
} }
LaunchedEffect(key) { LaunchedEffect(key) {
launch { launch {

View file

@ -0,0 +1,11 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common
import androidx.compose.runtime.Stable
@Stable
class CharacterRibbonUio(
val characterSheetId: String,
val hideOverruled: Boolean,
val portrait: CharacterRibbonPortraitUio,
val status: List<List<CharacterRibbonAlterationUio>>,
)

View file

@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -8,9 +9,12 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortrait import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRoll import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
@ -34,23 +38,26 @@ fun NpcRibbon(
key = { it.characterSheetId }, key = { it.characterSheetId },
) { ) {
Row( Row(
modifier = Modifier.animateItem(), modifier = Modifier
.animateItem()
.graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) { ) {
CharacterPortraitRoll( CharacterRibbonAlteration(
value = viewModel.roll(characterSheetId = it.characterSheetId).value, status = it.status,
onRightClick = { direction = LayoutDirection.Rtl,
viewModel.onPortraitRollRightClick(characterSheetId = it.characterSheetId)
},
onLeftClick = {
},
)
CharacterPortrait(
character = it,
onCharacterLeftClick = onCharacterLeftClick,
onCharacterRightClick = onCharacterRightClick,
onLevelUp = onLevelUp,
) )
Box {
CharacterRibbonPortrait(
character = it.portrait,
onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
onLevelUp = { onLevelUp(it.characterSheetId) },
)
CharacterRibbonRoll(
value = viewModel.roll(characterSheetId = it.characterSheetId).value,
)
}
} }
} }
} }

View file

@ -6,7 +6,7 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel
import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign
@ -36,11 +36,11 @@ class NpcRibbonViewModel(
return !campaign.options.showNpcs && settings.isGameMaster == true return !campaign.options.showNpcs && settings.isGameMaster == true
} }
override fun enableCharacterSheet(settings: Settings) : Boolean { override fun enableCharacterSheet(settings: Settings): Boolean {
return settings.isGameMaster ?: false return settings.isGameMaster ?: false
} }
override fun enableCharacterStats(settings: Settings) : Boolean { override fun enableCharacterStats(settings: Settings): Boolean {
return settings.isGameMaster ?: false return settings.isGameMaster ?: false
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@ -8,9 +9,12 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortrait import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterPortraitRoll import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
@ -34,22 +38,25 @@ fun PlayerRibbon(
key = { it.characterSheetId }, key = { it.characterSheetId },
) { ) {
Row( Row(
modifier = Modifier.animateItem(), modifier = Modifier
.animateItem()
.graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f },
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) { ) {
CharacterPortrait( Box {
character = it, CharacterRibbonPortrait(
onCharacterLeftClick = onCharacterLeftClick, character = it.portrait,
onCharacterRightClick = onCharacterRightClick, onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) },
onLevelUp = onLevelUp, onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) },
) onLevelUp = { onLevelUp(it.characterSheetId) },
CharacterPortraitRoll( )
value = viewModel.roll(characterSheetId = it.characterSheetId).value, CharacterRibbonRoll(
onRightClick = { value = viewModel.roll(characterSheetId = it.characterSheetId).value,
viewModel.onPortraitRollRightClick(characterSheetId = it.characterSheetId) )
}, }
onLeftClick = { CharacterRibbonAlteration(
status = it.status,
}, direction = LayoutDirection.Ltr,
) )
} }
} }

View file

@ -6,7 +6,7 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel
import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign

View file

@ -40,7 +40,7 @@ class CharacterSheetViewModel(
val sheetFlow = combine( val sheetFlow = combine(
characterRepository.characterDetailFlow(characterSheetId = argument.characterSheetId), characterRepository.characterDetailFlow(characterSheetId = argument.characterSheetId),
alteration.fieldAlterationsFlow(characterSheetId = argument.characterSheetId), alteration.activeFieldAlterationsFlow(characterSheetId = argument.characterSheetId),
transform = { characterSheet, alterations -> transform = { characterSheet, alterations ->
factory.convertToUio( factory.convertToUio(
characterSheetId = argument.characterSheetId, characterSheetId = argument.characterSheetId,

View file

@ -32,6 +32,12 @@ data class LwaColors(
data class Portrait( data class Portrait(
val levelUp: Color, val levelUp: Color,
val blood: Color, val blood: Color,
val criticalSuccess: Color,
val spacialSuccess: Color,
val success: Color,
val failure: Color,
val criticalFailure: Color,
val default: Color,
) )
@Stable @Stable
@ -84,15 +90,21 @@ fun darkLwaColorTheme(
portrait: LwaColors.Portrait = LwaColors.Portrait( portrait: LwaColors.Portrait = LwaColors.Portrait(
levelUp = LwaColorPalette.Gold, levelUp = LwaColorPalette.Gold,
blood = LwaColorPalette.Blood, blood = LwaColorPalette.Blood,
),
chat: LwaColors.Chat = LwaColors.Chat(
timestamp = base.secondary,
text = base.onSurface.copy(alpha = 0.7f),
criticalSuccess = LwaColorPalette.Teal400, criticalSuccess = LwaColorPalette.Teal400,
spacialSuccess = LwaColorPalette.Green400, spacialSuccess = LwaColorPalette.Green400,
success = LwaColorPalette.LightGreen400, success = LwaColorPalette.LightGreen400,
failure = LwaColorPalette.Orange400, failure = LwaColorPalette.Orange400,
criticalFailure = LwaColorPalette.Red400, criticalFailure = LwaColorPalette.Red400,
default = base.primary,
),
chat: LwaColors.Chat = LwaColors.Chat(
timestamp = base.secondary,
text = base.onSurface.copy(alpha = 0.7f),
criticalSuccess = portrait.criticalSuccess,
spacialSuccess = portrait.spacialSuccess,
success = portrait.success,
failure = portrait.failure,
criticalFailure = portrait.criticalFailure,
), ),
): LwaColors = LwaColors( ): LwaColors = LwaColors(
base = base, base = base,