Add alteration status to the portrait
This commit is contained in:
parent
2c04559bb7
commit
e81b66e725
23 changed files with 2719 additions and 210 deletions
2296
composeApp/hs_err_pid10276.log
Normal file
2296
composeApp/hs_err_pid10276.log
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -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>>,
|
||||||
|
)
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue