Add the edit & create character to the gamemaster screen.

This commit is contained in:
Thomas Andres Gomez 2025-03-16 17:47:38 +01:00
parent 662e270f3f
commit 2056348ec0
30 changed files with 420 additions and 143 deletions

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M360,570q-21,0 -35.5,-14.5T310,520q0,-21 14.5,-35.5T360,470q21,0 35.5,14.5T410,520q0,21 -14.5,35.5T360,570ZM600,570q-21,0 -35.5,-14.5T550,520q0,-21 14.5,-35.5T600,470q21,0 35.5,14.5T650,520q0,21 -14.5,35.5T600,570ZM480,800q134,0 227,-93t93,-227q0,-24 -3,-46.5T786,390q-21,5 -42,7.5t-44,2.5q-91,0 -172,-39T390,252q-32,78 -91.5,135.5T160,474v6q0,134 93,227t227,93ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM426,165q42,70 114,112.5T700,320q14,0 27,-1.5t27,-3.5q-42,-70 -114,-112.5T480,160q-14,0 -27,1.5t-27,3.5ZM177,379q51,-29 89,-75t57,-103q-51,29 -89,75t-57,103ZM426,165ZM323,201Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m814,884 l-88,-88q-51,39 -113,61.5T482,880q-82,0 -155,-31.5t-127.5,-86Q145,708 113.5,635T82,480q0,-69 22.5,-131.5T166,235l-88,-87 56,-56 736,736 -56,56ZM831,675 L772,616q14,-32 22,-66t8,-70q0,-24 -3,-46.5T788,390q-21,5 -42,7.5t-44,2.5q-56,0 -106.5,-14.5T500,344L286,130q44,-24 93,-37t103,-13q83,0 155.5,31.5t127,85.5q54.5,54 86,127T882,480q0,53 -12.5,101.5T831,675ZM590,240ZM179,379q21,-12 39,-26.5t35,-31.5l-30,-29q-14,20 -25,41.5T179,379ZM426,165q42,70 114,112.5T700,320q14,0 27,-1.5t27,-3.5q-42,-70 -114,-112.5T480,160q-14,0 -27,1.5t-27,3.5ZM216,335ZM362,570q-21,0 -35.5,-14.5T312,520q0,-21 14.5,-35.5T362,470q21,0 35.5,14.5T412,520q0,21 -14.5,35.5T362,570ZM482,800q53,0 100,-15.5t86,-44.5L309,379q-30,32 -67.5,56T162,474v6q0,133 93.5,226.5T482,800Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M40,800v-112q0,-34 17.5,-62.5T104,582q62,-31 126,-46.5T360,520q66,0 130,15.5T616,582q29,15 46.5,43.5T680,688v112L40,800ZM760,800v-120q0,-44 -24.5,-84.5T666,526q51,6 96,20.5t84,35.5q36,20 55,44.5t19,53.5v120L760,800ZM360,480q-66,0 -113,-47t-47,-113q0,-66 47,-113t113,-47q66,0 113,47t47,113q0,66 -47,113t-113,47ZM760,320q0,66 -47,113t-113,47q-11,0 -28,-2.5t-28,-5.5q27,-32 41.5,-71t14.5,-81q0,-42 -14.5,-81T544,168q14,-5 28,-6.5t28,-1.5q66,0 113,47t47,113ZM120,720h480v-32q0,-11 -5.5,-20T580,654q-54,-27 -109,-40.5T360,600q-56,0 -111,13.5T140,654q-9,5 -14.5,14t-5.5,20v32ZM360,400q33,0 56.5,-23.5T440,320q0,-33 -23.5,-56.5T360,240q-33,0 -56.5,23.5T280,320q0,33 23.5,56.5T360,400ZM360,720ZM360,320Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M819,932 L680,793v7L40,800v-112q0,-34 17.5,-62.5T104,582q62,-31 126,-46.5T360,520q12,0 24.5,0.5T409,522l-42,-42h-7q-66,0 -113,-47t-47,-113v-7L27,140l57,-57L876,875l-57,57ZM666,526q51,6 96,20.5t84,35.5q36,20 55,44.5t19,53.5v120h-5L755,640q-9,-33 -31.5,-62.5T666,526ZM360,600q-56,0 -111,13.5T140,654q-9,5 -14.5,14t-5.5,20v32h480v-7l-87,-87q-38,-13 -76.5,-19.5T360,600ZM562,447q19,-28 28.5,-60t9.5,-67q0,-42 -14.5,-81T544,168q14,-5 28,-6.5t28,-1.5q66,0 113,47t47,113q0,66 -49.5,113T595,480l-33,-33ZM504,389 L440,325v-5q0,-33 -23.5,-56.5T360,240h-5l-64,-64q16,-8 33,-12t36,-4q66,0 113,47t47,113q0,19 -4,36t-12,33ZM365,720ZM398,282Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h560q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L200,840ZM200,760h560v-560L200,200v560ZM240,680h480L570,480 450,640l-90,-120 -120,160ZM200,760v-560,560ZM340,400q25,0 42.5,-17.5T400,340q0,-25 -17.5,-42.5T340,280q-25,0 -42.5,17.5T280,340q0,25 17.5,42.5T340,400Z"
android:fillColor="#5f6368"/>
</vector>

View file

@ -39,6 +39,11 @@ class DataSyncViewModel(
.onEach { campaignRepository.update() } .onEach { campaignRepository.update() }
.launchIn(this) .launchIn(this)
networkRepository.status
.filter { status -> status == NetworkRepository.Status.CONNECTED }
.onEach { characterRepository.updateCharacterPreviews() }
.launchIn(this)
networkRepository.status networkRepository.status
.filter { status -> status == NetworkRepository.Status.CONNECTED } .filter { status -> status == NetworkRepository.Status.CONNECTED }
.combine(campaignRepository.campaignFlow) { _, campaign: Campaign -> campaign } .combine(campaignRepository.campaignFlow) { _, campaign: Campaign -> campaign }

View file

@ -16,8 +16,13 @@ class CharacterSheetRepository(
private val scope = CoroutineScope(Dispatchers.IO + Job()) private val scope = CoroutineScope(Dispatchers.IO + Job())
val characterSheetPreviewFlow get() = store.previewFlow val characterSheetPreviewFlow get() = store.previewFlow
val characterDetailFlow get() = store.detailFlow val characterDetailFlow get() = store.detailFlow
suspend fun updateCharacterPreviews() {
store.updateCharacterPreviews()
}
fun characterPreview(characterId: String?): CharacterSheetPreview? { fun characterPreview(characterId: String?): CharacterSheetPreview? {
return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId } return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId }
} }

View file

@ -32,7 +32,7 @@ class CharacterSheetStore(
val scope = CoroutineScope(Dispatchers.IO + Job()) val scope = CoroutineScope(Dispatchers.IO + Job())
// initial data loading. // initial data loading.
scope.launch { scope.launch {
_previewFlow.value = charactersPreview() updateCharacterPreviews()
} }
// data update through WebSocket. // data update through WebSocket.
scope.launch { scope.launch {
@ -42,6 +42,10 @@ class CharacterSheetStore(
// region Rest // region Rest
suspend fun updateCharacterPreviews() {
_previewFlow.value = charactersPreview()
}
suspend fun charactersPreview(): List<CharacterSheetPreview> { suspend fun charactersPreview(): List<CharacterSheetPreview> {
val request = client.characters() val request = client.characters()
val data = request.map { val data = request.map {

View file

@ -21,6 +21,7 @@ class SettingsFactory(
autoHideDelay = settings.autoHideDelay, autoHideDelay = settings.autoHideDelay,
autoShowChat = settings.autoShowChat, autoShowChat = settings.autoShowChat,
autoScrollChat = settings.autoScrollChat, autoScrollChat = settings.autoScrollChat,
isAdmin = settings.isAdmin,
isGM = settings.isGM, isGM = settings.isGM,
) )
} }
@ -46,6 +47,7 @@ class SettingsFactory(
autoHideDelay = json.autoHideDelay ?: default.autoHideDelay, autoHideDelay = json.autoHideDelay ?: default.autoHideDelay,
autoShowChat = json.autoShowChat ?: default.autoShowChat, autoShowChat = json.autoShowChat ?: default.autoShowChat,
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat, autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
isAdmin = json.isAdmin ?: default.isAdmin,
isGM = json.isGM ?: default.isGM, isGM = json.isGM ?: default.isGM,
) )
} }

View file

@ -9,6 +9,7 @@ data class Settings(
val autoHideDelay: Int, val autoHideDelay: Int,
val autoShowChat: Boolean, val autoShowChat: Boolean,
val autoScrollChat: Boolean, val autoScrollChat: Boolean,
val isAdmin: Boolean,
val isGM: Boolean, val isGM: Boolean,
) { ) {
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}" val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"

View file

@ -13,4 +13,5 @@ data class SettingsJsonV1(
val autoShowChat: Boolean?, val autoShowChat: Boolean?,
val autoScrollChat: Boolean?, val autoScrollChat: Boolean?,
val isGM: Boolean?, val isGM: Boolean?,
val isAdmin: Boolean?,
) : SettingsJson ) : SettingsJson

View file

@ -17,7 +17,7 @@ class CharacterSheetEditWindow(
fun WindowController.navigateToCharacterSheetEdit( fun WindowController.navigateToCharacterSheetEdit(
characterId: String?, characterId: String?,
title: String, title: String = "",
) { ) {
showWindow( showWindow(
window = CharacterSheetEditWindow( window = CharacterSheetEditWindow(
@ -25,7 +25,7 @@ fun WindowController.navigateToCharacterSheetEdit(
title = title, title = title,
size = DpSize( size = DpSize(
width = 600.dp, width = 600.dp,
height = maxWindowHeight, height = maxWindowHeight - 32.dp,
), ),
) )
) )

View file

@ -18,7 +18,7 @@ class CharacterSheetWindow(
fun WindowController.navigateToCharacterSheet( fun WindowController.navigateToCharacterSheet(
characterId: Campaign.CharacterInstance.Id, characterId: Campaign.CharacterInstance.Id,
title: String, title: String = "Feuille de personnage",
) { ) {
showWindow( showWindow(
window = CharacterSheetWindow( window = CharacterSheetWindow(
@ -26,7 +26,7 @@ fun WindowController.navigateToCharacterSheet(
title = title, title = title,
size = DpSize( size = DpSize(
width = 400.dp + 64.dp, width = 400.dp + 64.dp,
height = maxWindowHeight, height = maxWindowHeight - 32.dp,
), ),
) )
) )

View file

@ -22,7 +22,7 @@ fun WindowController.navigateToRollHistory(
title = title, title = title,
size = DpSize( size = DpSize(
width = 400.dp + 64.dp, width = 400.dp + 64.dp,
height = maxWindowHeight, height = maxWindowHeight - 32.dp,
) )
) )
) )

View file

@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
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 coil3.compose.AsyncImage import coil3.compose.AsyncImage
@ -60,6 +61,7 @@ object CharacterPortraitDefault {
@Stable @Stable
data class CharacterPortraitUio( data class CharacterPortraitUio(
val id: Campaign.CharacterInstance.Id, val id: Campaign.CharacterInstance.Id,
val idLabel: String?,
val portrait: String?, val portrait: String?,
val name: String, val name: String,
val levelUp: Boolean, val levelUp: Boolean,
@ -79,6 +81,7 @@ data class CharacterPortraitUio(
fun CharacterPortrait( fun CharacterPortrait(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
size: DpSize = CharacterPortraitDefault.size, size: DpSize = CharacterPortraitDefault.size,
levelUpOffset: Dp = 9.dp,
character: CharacterPortraitUio, character: CharacterPortraitUio,
onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit, onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit,
onLevelUp: (id: Campaign.CharacterInstance.Id) -> Unit, onLevelUp: (id: Campaign.CharacterInstance.Id) -> Unit,
@ -106,8 +109,18 @@ fun CharacterPortrait(
) )
} }
character.idLabel?.let { label ->
Text(
modifier = Modifier.padding(start = 4.dp, top = 3.dp),
style = MaterialTheme.lwa.typography.portrait.idLabel,
text = label,
)
}
AnimatedVisibility( AnimatedVisibility(
modifier = Modifier.offset(x = (-8).dp, y = (-8).dp), modifier = Modifier
.align(alignment = Alignment.TopEnd)
.offset(x = levelUpOffset, y = -levelUpOffset),
visible = character.levelUp, visible = character.levelUp,
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(), exit = fadeOut(),
@ -149,14 +162,12 @@ fun CharacterPortrait(
) )
Text( Text(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.caption, style = MaterialTheme.lwa.typography.portrait.value,
fontWeight = FontWeight.Bold,
text = "${stats.hp}", text = "${stats.hp}",
) )
Text( Text(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.caption, style = MaterialTheme.lwa.typography.portrait.max,
fontWeight = FontWeight.Light,
text = "/${stats.maxHp}", text = "/${stats.maxHp}",
) )
} }
@ -171,14 +182,12 @@ fun CharacterPortrait(
) )
Text( Text(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.caption, style = MaterialTheme.lwa.typography.portrait.value,
fontWeight = FontWeight.Bold,
text = "${stats.pp}", text = "${stats.pp}",
) )
Text( Text(
modifier = Modifier.alignByBaseline(), modifier = Modifier.alignByBaseline(),
fontWeight = FontWeight.Light, style = MaterialTheme.lwa.typography.portrait.max,
style = MaterialTheme.typography.caption,
text = "/${stats.maxPp}", text = "/${stats.maxPp}",
) )
} }

View file

@ -15,8 +15,9 @@ class CharacterRibbonFactory(
alterations: Map<String, List<FieldAlteration>>, alterations: Map<String, List<FieldAlteration>>,
characterInstanceId: Campaign.CharacterInstance.Id, characterInstanceId: Campaign.CharacterInstance.Id,
characterInstance: Campaign.CharacterInstance, characterInstance: Campaign.CharacterInstance,
enableDetail: Boolean, enableCharacterId: Boolean,
displayCharacterStats: Boolean, enableCharacterSheet: Boolean,
enableCharacterStats: Boolean,
): CharacterPortraitUio? { ): CharacterPortraitUio? {
if (characterSheet == null) return null if (characterSheet == null) return null
@ -27,11 +28,12 @@ class CharacterRibbonFactory(
return CharacterPortraitUio( return CharacterPortraitUio(
id = characterInstanceId, id = characterInstanceId,
idLabel = takeIf { enableCharacterId }?.let { characterInstanceId.instanceId.toString() },
portrait = alteredCharacterSheet.thumbnail, portrait = alteredCharacterSheet.thumbnail,
name = alteredCharacterSheet.name, name = alteredCharacterSheet.name,
levelUp = alteredCharacterSheet.shouldLevelUp, levelUp = alteredCharacterSheet.shouldLevelUp,
enableDetail = enableDetail, enableDetail = enableCharacterSheet,
stats = takeIf { displayCharacterStats }?.let { stats = takeIf { enableCharacterStats }?.let {
CharacterPortraitUio.StatsDetail( CharacterPortraitUio.StatsDetail(
hp = alteredCharacterSheet.maxHp - characterInstance.damage, hp = alteredCharacterSheet.maxHp - characterInstance.damage,
maxHp = alteredCharacterSheet.maxHp, maxHp = alteredCharacterSheet.maxHp,

View file

@ -41,6 +41,9 @@ abstract class CharacterRibbonViewModel(
abstract val Campaign.data: Map<CharacterInstance.Id, CharacterInstance> abstract val Campaign.data: Map<CharacterInstance.Id, CharacterInstance>
abstract val enableCharacterSheet: Boolean
abstract val enableCharacterStats: Boolean
/** /**
* This flow is a tad complex so there is an explanation of wtf it's about : * This flow is a tad complex so there is an explanation of wtf it's about :
* On a campaign update it go through every element of the abstract [data] map and either: * On a campaign update it go through every element of the abstract [data] map and either:
@ -58,14 +61,16 @@ abstract class CharacterRibbonViewModel(
combine( combine(
characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId), characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId),
alterationRepository.alterationsFlow(characterInstanceId = entry.key), alterationRepository.alterationsFlow(characterInstanceId = entry.key),
) { sheet, alterations -> settingsRepository.settingsFlow(),
) { sheet, alterations, settings ->
ribbonFactory.convertToPlayerPortraitUio( ribbonFactory.convertToPlayerPortraitUio(
characterSheet = sheet, characterSheet = sheet,
alterations = alterations, alterations = alterations,
characterInstanceId = entry.key, characterInstanceId = entry.key,
characterInstance = entry.value, characterInstance = entry.value,
enableDetail = settingsRepository.settings().isGM, enableCharacterId = settings.isGM,
displayCharacterStats = settingsRepository.settings().isGM, enableCharacterSheet = enableCharacterSheet || settings.isGM,
enableCharacterStats = enableCharacterStats || settings.isGM,
) )
} }
}, },

View file

@ -34,7 +34,9 @@ fun NpcRibbon(
items = characters.value, items = characters.value,
key = { it.id }, key = { it.id },
) { ) {
Row { Row(
modifier = Modifier.animateItem(),
) {
CharacterPortraitRoll( CharacterPortraitRoll(
size = CharacterPortraitDefault.size, size = CharacterPortraitDefault.size,
value = viewModel.roll(characterId = it.id).value, value = viewModel.roll(characterId = it.id).value,

View file

@ -25,4 +25,7 @@ class NpcRibbonViewModel(
ribbonFactory = ribbonFactory, ribbonFactory = ribbonFactory,
) { ) {
override val Campaign.data get() = npcs override val Campaign.data get() = npcs
override val enableCharacterSheet = false
override val enableCharacterStats = false
} }

View file

@ -33,7 +33,9 @@ fun PlayerRibbon(
items = characters.value, items = characters.value,
key = { it.id }, key = { it.id },
) { ) {
Row { Row(
modifier = Modifier.animateItem(),
) {
CharacterPortrait( CharacterPortrait(
character = it, character = it,
onCharacter = onCharacter, onCharacter = onCharacter,

View file

@ -25,4 +25,7 @@ class PlayerRibbonViewModel(
ribbonFactory = ribbonFactory, ribbonFactory = ribbonFactory,
) { ) {
override val Campaign.data get() = characters override val Campaign.data get() = characters
override val enableCharacterSheet = true
override val enableCharacterStats = true
} }

View file

@ -5,7 +5,6 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon import androidx.compose.material.Icon
@ -22,7 +21,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -57,15 +55,15 @@ fun CampaignToolbar(
val title = viewModel.title.collectAsState() val title = viewModel.title.collectAsState()
val status = viewModel.status.collectAsState() val status = viewModel.status.collectAsState()
val isGM = viewModel.isGM.collectAsState() val isAdmin = viewModel.isAdmin.collectAsState()
CampaignToolbarContent( CampaignToolbarContent(
title = title, title = title,
status = status, status = status,
isGM = isGM, isAdmin = isAdmin,
isNetworkMenuOpen = isNetworkMenuOpen, isNetworkMenuOpen = isNetworkMenuOpen,
isOverflowMenuOpen = isOverflowMenuOpen, isOverflowMenuOpen = isOverflowMenuOpen,
onGM = { onAdmin = {
windows.navigateToGameMasterWindow() windows.navigateToGameMasterWindow()
}, },
onNetwork = { onNetwork = {
@ -96,10 +94,10 @@ private fun CampaignToolbarContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: State<String>, title: State<String>,
status: State<NetworkRepository.Status>, status: State<NetworkRepository.Status>,
isGM: State<Boolean>, isAdmin: State<Boolean>,
isNetworkMenuOpen: State<Boolean>, isNetworkMenuOpen: State<Boolean>,
isOverflowMenuOpen: State<Boolean>, isOverflowMenuOpen: State<Boolean>,
onGM: () -> Unit, onAdmin: () -> Unit,
onNetwork: () -> Unit, onNetwork: () -> Unit,
onOverflow: () -> Unit, onOverflow: () -> Unit,
onRollHistory: () -> Unit, onRollHistory: () -> Unit,
@ -116,17 +114,16 @@ private fun CampaignToolbarContent(
}, },
actions = { actions = {
AnimatedVisibility( AnimatedVisibility(
visible = isGM.value, visible = isAdmin.value,
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(), exit = fadeOut(),
) { ) {
TextButton( TextButton(
modifier = Modifier.size(size = 48.dp).clip(shape = CircleShape), onClick = onAdmin,
onClick = onGM,
) { ) {
Text( Text(
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
text = "GM", text = "Admin",
) )
} }
} }

View file

@ -25,8 +25,8 @@ class CampaignToolbarViewModel(
initialValue = "", initialValue = "",
) )
val isGM = settingsRepository.settingsFlow() val isAdmin = settingsRepository.settingsFlow()
.map { it.isGM } .map { it.isAdmin }
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Lazily, started = SharingStarted.Lazily,

View file

@ -1,7 +1,7 @@
package com.pixelized.desktop.lwa.ui.screen.gamemaster package com.pixelized.desktop.lwa.ui.screen.gamemaster
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio.Action
class GameMasterActionUseCase( class GameMasterActionUseCase(
private val campaignRepository: CampaignRepository, private val campaignRepository: CampaignRepository,

View file

@ -1,8 +1,8 @@
package com.pixelized.desktop.lwa.ui.screen.gamemaster package com.pixelized.desktop.lwa.ui.screen.gamemaster
import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio.Action
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
@ -18,7 +18,7 @@ class GameMasterFactory {
characters: List<CharacterSheetPreview>, characters: List<CharacterSheetPreview>,
filter: String, filter: String,
tags: Map<GMTagUio.TagId, Boolean>, tags: Map<GMTagUio.TagId, Boolean>,
): List<GMCharacterPreviewUio> { ): List<GMCharacterUio> {
val normalizedFilter = Normalizer.normalize(filter, Normalizer.Form.NFD) val normalizedFilter = Normalizer.normalize(filter, Normalizer.Form.NFD)
return characters.mapNotNull { return characters.mapNotNull {
@ -36,7 +36,7 @@ class GameMasterFactory {
character: CharacterSheetPreview, character: CharacterSheetPreview,
filter: String, filter: String,
tags: Map<GMTagUio.TagId, Boolean>, tags: Map<GMTagUio.TagId, Boolean>,
): GMCharacterPreviewUio? { ): GMCharacterUio? {
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any // get the characterInstanceId from the player list corresponding to this CharacterSheet if any
val characterInstanceId: Campaign.CharacterInstance.Id? = val characterInstanceId: Campaign.CharacterInstance.Id? =
campaign.characters.keys.firstOrNull { campaign.characters.keys.firstOrNull {
@ -106,9 +106,10 @@ class GameMasterFactory {
) )
} }
// return the cell UIO. // return the cell UIO.
return GMCharacterPreviewUio( return GMCharacterUio(
characterSheetId = character.characterSheetId, characterSheetId = character.characterSheetId,
name = character.name, level = character.level, name = character.name,
level = character.level,
tags = previewTagsList, tags = previewTagsList,
actions = actions, actions = actions,
) )

View file

@ -3,12 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -19,30 +20,40 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreview import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacter
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTag import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTag
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
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.character_sheet_edit__create__title
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@ -50,6 +61,9 @@ import org.koin.compose.viewmodel.koinViewModel
fun GameMasterScreen( fun GameMasterScreen(
viewModel: GameMasterViewModel = koinViewModel(), viewModel: GameMasterViewModel = koinViewModel(),
) { ) {
val windows = LocalWindowController.current
val scope = rememberCoroutineScope()
val characters = viewModel.characters.collectAsState() val characters = viewModel.characters.collectAsState()
val tags = viewModel.tags.collectAsState() val tags = viewModel.tags.collectAsState()
@ -59,10 +73,27 @@ fun GameMasterScreen(
GameMasterContent( GameMasterContent(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
filter = viewModel.filter, filter = viewModel.filter,
onGameMaster = viewModel::onGameMaster,
tags = tags, tags = tags,
characters = characters, characters = characters,
onTag = viewModel::onTag, onTag = viewModel::onTag,
onCharacterAction = viewModel::onCharacterAction, onCharacterAction = viewModel::onCharacterAction,
onCharacterSheetEdit = { characterSheetId ->
scope.launch {
windows.navigateToCharacterSheetEdit(
characterId = characterSheetId,
title = getString(Res.string.character_sheet_edit__edit__title),
)
}
},
onCharacterSheetCreate = {
scope.launch {
windows.navigateToCharacterSheetEdit(
characterId = null,
title = getString(Res.string.character_sheet_edit__create__title),
)
}
},
) )
} }
} }
@ -73,9 +104,12 @@ private fun GameMasterContent(
filterChipsState: LazyListState = rememberLazyListState(), filterChipsState: LazyListState = rememberLazyListState(),
filter: LwaTextFieldUio, filter: LwaTextFieldUio,
tags: State<List<GMTagUio>>, tags: State<List<GMTagUio>>,
characters: State<List<GMCharacterPreviewUio>>, characters: State<List<GMCharacterUio>>,
onGameMaster: () -> Unit,
onTag: (GMTagUio.TagId) -> Unit, onTag: (GMTagUio.TagId) -> Unit,
onCharacterAction: (String, GMCharacterPreviewUio.Action) -> Unit, onCharacterAction: (String, GMCharacterUio.Action) -> Unit,
onCharacterSheetEdit: (String) -> Unit,
onCharacterSheetCreate: () -> Unit,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -87,6 +121,13 @@ private fun GameMasterContent(
Text( Text(
text = "", text = "",
) )
},
actions = {
TextButton(
onClick = onGameMaster,
) {
Text(text = "GameMaster")
}
} }
) )
}, },
@ -94,6 +135,10 @@ private fun GameMasterContent(
Column( Column(
modifier = Modifier.padding(paddingValues = paddingValues) modifier = Modifier.padding(paddingValues = paddingValues)
) { ) {
Surface(
elevation = 1.dp,
) {
Column {
LwaTextField( LwaTextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
field = filter, field = filter,
@ -139,20 +184,56 @@ private fun GameMasterContent(
) )
} }
} }
}
}
Box(
modifier = Modifier.fillMaxWidth().weight(1f),
) {
LazyColumn( LazyColumn(
modifier = Modifier.weight(1f), modifier = Modifier.matchParentSize(),
contentPadding = remember { PaddingValues(all = 8.dp) }, contentPadding = remember {
PaddingValues(
start = 8.dp,
top = 8.dp,
end = 8.dp,
bottom = 8.dp + 48.dp + 8.dp,
)
},
verticalArrangement = Arrangement.spacedBy(space = 8.dp), verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) { ) {
items( items(
items = characters.value, items = characters.value,
key = { it.characterSheetId },
) { character -> ) { character ->
GMCharacterPreview( GMCharacter(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.animateItem(),
character = character, character = character,
onEdit = {
onCharacterSheetEdit(character.characterSheetId)
},
onAction = { action -> onAction = { action ->
onCharacterAction(character.characterSheetId, action) onCharacterAction(character.characterSheetId, action)
},
)
} }
}
IconButton(
modifier = Modifier
.align(alignment = Alignment.BottomEnd)
.padding(all = 8.dp)
.background(
color = MaterialTheme.lwa.colorScheme.base.primary,
shape = CircleShape,
),
onClick = onCharacterSheetCreate,
) {
Icon(
imageVector = Icons.Default.Add,
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
contentDescription = null,
) )
} }
} }

View file

@ -4,8 +4,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository 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.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio.TagId import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio.TagId
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -20,9 +21,10 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character_t
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
class GameMasterViewModel( class GameMasterViewModel(
private val campaignRepository: CampaignRepository, campaignRepository: CampaignRepository,
private val characterSheetRepository: CharacterSheetRepository, characterSheetRepository: CharacterSheetRepository,
private val gameMasterFactory: GameMasterFactory, private val settingsRepository: SettingsRepository,
private val factory: GameMasterFactory,
private val useCase: GameMasterActionUseCase, private val useCase: GameMasterActionUseCase,
) : ViewModel() { ) : ViewModel() {
@ -63,16 +65,25 @@ class GameMasterViewModel(
characterSheetRepository.characterSheetPreviewFlow, characterSheetRepository.characterSheetPreviewFlow,
filter.valueFlow, filter.valueFlow,
_tags, _tags,
gameMasterFactory::convertToGMCharacterPreviewUio, factory::convertToGMCharacterPreviewUio,
).stateIn( ).stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = emptyList(), initialValue = emptyList(),
) )
fun onGameMaster() {
val settings = settingsRepository.settings()
settingsRepository.update(
settings = settings.copy(
isGM = settings.isGM.not(),
)
)
}
fun onCharacterAction( fun onCharacterAction(
characterSheetId: String, characterSheetId: String,
action: GMCharacterPreviewUio.Action, action: GMCharacterUio.Action,
) { ) {
viewModelScope.launch { viewModelScope.launch {
try { try {

View file

@ -16,6 +16,7 @@ import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
@ -27,7 +28,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio.Action
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__add_to_group import lwacharactersheet.composeapp.generated.resources.game_master__character_action__add_to_group
@ -36,10 +37,17 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character_a
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__remove_from_group import lwacharactersheet.composeapp.generated.resources.game_master__character_action__remove_from_group
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__remove_from_npc import lwacharactersheet.composeapp.generated.resources.game_master__character_action__remove_from_npc
import lwacharactersheet.composeapp.generated.resources.game_master__character_level__label import lwacharactersheet.composeapp.generated.resources.game_master__character_level__label
import lwacharactersheet.composeapp.generated.resources.ic_face_24dp
import lwacharactersheet.composeapp.generated.resources.ic_face_retouching_off_24dp
import lwacharactersheet.composeapp.generated.resources.ic_group_24dp
import lwacharactersheet.composeapp.generated.resources.ic_group_off_24dp
import lwacharactersheet.composeapp.generated.resources.ic_imagesmode_24dp
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@Stable @Stable
data class GMCharacterPreviewUio( data class GMCharacterUio(
val characterSheetId: String, val characterSheetId: String,
val name: String, val name: String,
val level: Int, val level: Int,
@ -47,21 +55,33 @@ data class GMCharacterPreviewUio(
val actions: List<Action>, val actions: List<Action>,
) { ) {
@Stable @Stable
sealed class Action { sealed class Action(
val icon: DrawableResource,
) {
@Stable @Stable
data object DisplayPortrait : Action() data object DisplayPortrait : Action(
icon = Res.drawable.ic_imagesmode_24dp,
)
@Stable @Stable
data object AddToGroup : Action() data object AddToGroup : Action(
icon = Res.drawable.ic_group_24dp,
)
@Stable @Stable
data class RemoveFromGroup(val instanceId: Int) : Action() data class RemoveFromGroup(val instanceId: Int) : Action(
icon = Res.drawable.ic_group_off_24dp,
)
@Stable @Stable
data object AddToNpc : Action() data object AddToNpc : Action(
icon = Res.drawable.ic_face_24dp,
)
@Stable @Stable
data class RemoveFromNpc(val instanceId: Int) : Action() data class RemoveFromNpc(val instanceId: Int) : Action(
icon = Res.drawable.ic_face_retouching_off_24dp,
)
} }
} }
@ -70,10 +90,11 @@ object GMCharacterPreviewDefault {
} }
@Composable @Composable
fun GMCharacterPreview( fun GMCharacter(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
padding: PaddingValues = GMCharacterPreviewDefault.padding, padding: PaddingValues = GMCharacterPreviewDefault.padding,
character: GMCharacterPreviewUio, character: GMCharacterUio,
onEdit: () -> Unit,
onAction: (Action) -> Unit, onAction: (Action) -> Unit,
) { ) {
val layoutDirection = LocalLayoutDirection.current val layoutDirection = LocalLayoutDirection.current
@ -108,20 +129,22 @@ fun GMCharacterPreview(
), ),
) )
} }
OverflowActionMenu( MenuActions(
character = character, character = character,
onEdit = onEdit,
onAction = onAction, onAction = onAction,
) )
} }
Row( Row(
modifier = Modifier modifier = Modifier
.padding(paddingValues = padding) .padding(paddingValues = padding)
.padding(bottom = 16.dp), .padding(bottom = 8.dp),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp), horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) { ) {
character.tags.forEach { tag -> character.tags.forEach { tag ->
GMTag( GMTag(
style = MaterialTheme.lwa.typography.base.caption, style = MaterialTheme.lwa.typography.base.caption,
elevation = 4.dp,
tag = tag, tag = tag,
) )
} }
@ -130,10 +153,36 @@ fun GMCharacterPreview(
} }
} }
@Composable
private fun MenuActions(
modifier: Modifier = Modifier,
character: GMCharacterUio,
onEdit: () -> Unit,
onAction: (Action) -> Unit,
) {
Row(
modifier = modifier,
) {
IconButton(
onClick = onEdit
) {
Icon(
imageVector = Icons.Default.Edit,
tint = MaterialTheme.lwa.colorScheme.base.primary,
contentDescription = null,
)
}
OverflowActionMenu(
character = character,
onAction = onAction,
)
}
}
@Composable @Composable
private fun OverflowActionMenu( private fun OverflowActionMenu(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
character: GMCharacterPreviewUio, character: GMCharacterUio,
onAction: (Action) -> Unit, onAction: (Action) -> Unit,
) { ) {
val overflowMenu = remember(character) { val overflowMenu = remember(character) {
@ -165,6 +214,15 @@ private fun OverflowActionMenu(
onAction(action) onAction(action)
}, },
) { ) {
Row(
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(action.icon),
tint = MaterialTheme.lwa.colorScheme.base.primary,
contentDescription = null,
)
Text( Text(
style = MaterialTheme.lwa.typography.base.body1, style = MaterialTheme.lwa.typography.base.body1,
color = MaterialTheme.lwa.colorScheme.base.primary, color = MaterialTheme.lwa.colorScheme.base.primary,
@ -194,6 +252,7 @@ private fun OverflowActionMenu(
) )
} }
} }
}
}, },
) )
} }

View file

@ -4,12 +4,16 @@ import androidx.compose.material.Typography
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
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.sp import androidx.compose.ui.unit.sp
import com.pixelized.desktop.lwa.ui.theme.color.LwaColorPalette
import com.pixelized.desktop.lwa.ui.theme.color.LwaColors import com.pixelized.desktop.lwa.ui.theme.color.LwaColors
import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography.Settings import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography.Settings
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
@ -22,6 +26,7 @@ data class LwaTypography(
val base: Typography, val base: Typography,
val chat: Chat, val chat: Chat,
val settings: Settings, val settings: Settings,
val portrait: Portrait,
) { ) {
@Stable @Stable
data class Chat( data class Chat(
@ -36,6 +41,13 @@ data class LwaTypography(
val input: TextStyle, val input: TextStyle,
val description: TextStyle, val description: TextStyle,
) )
@Stable
data class Portrait(
val idLabel: TextStyle,
val value: TextStyle,
val max: TextStyle,
)
} }
@Composable @Composable
@ -90,6 +102,32 @@ fun lwaTypography(
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
color = colors.base.onSurface.copy(alpha = 0.7f), color = colors.base.onSurface.copy(alpha = 0.7f),
), ),
),
portrait = LwaTypography.Portrait(
idLabel = base.caption.copy(
fontWeight = FontWeight.SemiBold,
shadow = Shadow(
color = Color.Black,
offset = Offset(x = 1f, y = 1f),
blurRadius = 2f,
),
),
value = base.caption.copy(
fontWeight = FontWeight.Bold,
shadow = Shadow(
color = Color.Black,
offset = Offset(x = 1f, y = 1f),
blurRadius = 2f,
),
),
max = base.caption.copy(
fontWeight = FontWeight.Light,
shadow = Shadow(
color = Color.Black,
offset = Offset(x = 1f, y = 1f),
blurRadius = 2f,
),
),
) )
) )
} }

View file

@ -13,6 +13,7 @@ class SettingsUseCase {
autoHideDelay = 8, autoHideDelay = 8,
autoShowChat = true, autoShowChat = true,
autoScrollChat = true, autoScrollChat = true,
isAdmin = false,
isGM = false, isGM = false,
) )