Add the edit & create character to the gamemaster screen.
This commit is contained in:
parent
662e270f3f
commit
2056348ec0
30 changed files with 420 additions and 143 deletions
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -39,6 +39,11 @@ class DataSyncViewModel(
|
|||
.onEach { campaignRepository.update() }
|
||||
.launchIn(this)
|
||||
|
||||
networkRepository.status
|
||||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||
.onEach { characterRepository.updateCharacterPreviews() }
|
||||
.launchIn(this)
|
||||
|
||||
networkRepository.status
|
||||
.filter { status -> status == NetworkRepository.Status.CONNECTED }
|
||||
.combine(campaignRepository.campaignFlow) { _, campaign: Campaign -> campaign }
|
||||
|
|
|
|||
|
|
@ -16,8 +16,13 @@ class CharacterSheetRepository(
|
|||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
val characterSheetPreviewFlow get() = store.previewFlow
|
||||
|
||||
val characterDetailFlow get() = store.detailFlow
|
||||
|
||||
suspend fun updateCharacterPreviews() {
|
||||
store.updateCharacterPreviews()
|
||||
}
|
||||
|
||||
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
||||
return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class CharacterSheetStore(
|
|||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
// initial data loading.
|
||||
scope.launch {
|
||||
_previewFlow.value = charactersPreview()
|
||||
updateCharacterPreviews()
|
||||
}
|
||||
// data update through WebSocket.
|
||||
scope.launch {
|
||||
|
|
@ -42,6 +42,10 @@ class CharacterSheetStore(
|
|||
|
||||
// region Rest
|
||||
|
||||
suspend fun updateCharacterPreviews() {
|
||||
_previewFlow.value = charactersPreview()
|
||||
}
|
||||
|
||||
suspend fun charactersPreview(): List<CharacterSheetPreview> {
|
||||
val request = client.characters()
|
||||
val data = request.map {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class SettingsFactory(
|
|||
autoHideDelay = settings.autoHideDelay,
|
||||
autoShowChat = settings.autoShowChat,
|
||||
autoScrollChat = settings.autoScrollChat,
|
||||
isAdmin = settings.isAdmin,
|
||||
isGM = settings.isGM,
|
||||
)
|
||||
}
|
||||
|
|
@ -46,6 +47,7 @@ class SettingsFactory(
|
|||
autoHideDelay = json.autoHideDelay ?: default.autoHideDelay,
|
||||
autoShowChat = json.autoShowChat ?: default.autoShowChat,
|
||||
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
|
||||
isAdmin = json.isAdmin ?: default.isAdmin,
|
||||
isGM = json.isGM ?: default.isGM,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ data class Settings(
|
|||
val autoHideDelay: Int,
|
||||
val autoShowChat: Boolean,
|
||||
val autoScrollChat: Boolean,
|
||||
val isAdmin: Boolean,
|
||||
val isGM: Boolean,
|
||||
) {
|
||||
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@ data class SettingsJsonV1(
|
|||
val autoShowChat: Boolean?,
|
||||
val autoScrollChat: Boolean?,
|
||||
val isGM: Boolean?,
|
||||
val isAdmin: Boolean?,
|
||||
) : SettingsJson
|
||||
|
|
@ -17,7 +17,7 @@ class CharacterSheetEditWindow(
|
|||
|
||||
fun WindowController.navigateToCharacterSheetEdit(
|
||||
characterId: String?,
|
||||
title: String,
|
||||
title: String = "",
|
||||
) {
|
||||
showWindow(
|
||||
window = CharacterSheetEditWindow(
|
||||
|
|
@ -25,7 +25,7 @@ fun WindowController.navigateToCharacterSheetEdit(
|
|||
title = title,
|
||||
size = DpSize(
|
||||
width = 600.dp,
|
||||
height = maxWindowHeight,
|
||||
height = maxWindowHeight - 32.dp,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class CharacterSheetWindow(
|
|||
|
||||
fun WindowController.navigateToCharacterSheet(
|
||||
characterId: Campaign.CharacterInstance.Id,
|
||||
title: String,
|
||||
title: String = "Feuille de personnage",
|
||||
) {
|
||||
showWindow(
|
||||
window = CharacterSheetWindow(
|
||||
|
|
@ -26,7 +26,7 @@ fun WindowController.navigateToCharacterSheet(
|
|||
title = title,
|
||||
size = DpSize(
|
||||
width = 400.dp + 64.dp,
|
||||
height = maxWindowHeight,
|
||||
height = maxWindowHeight - 32.dp,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ fun WindowController.navigateToRollHistory(
|
|||
title = title,
|
||||
size = DpSize(
|
||||
width = 400.dp + 64.dp,
|
||||
height = maxWindowHeight,
|
||||
height = maxWindowHeight - 32.dp,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
|
|
@ -60,6 +61,7 @@ object CharacterPortraitDefault {
|
|||
@Stable
|
||||
data class CharacterPortraitUio(
|
||||
val id: Campaign.CharacterInstance.Id,
|
||||
val idLabel: String?,
|
||||
val portrait: String?,
|
||||
val name: String,
|
||||
val levelUp: Boolean,
|
||||
|
|
@ -79,6 +81,7 @@ data class CharacterPortraitUio(
|
|||
fun CharacterPortrait(
|
||||
modifier: Modifier = Modifier,
|
||||
size: DpSize = CharacterPortraitDefault.size,
|
||||
levelUpOffset: Dp = 9.dp,
|
||||
character: CharacterPortraitUio,
|
||||
onCharacter: (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(
|
||||
modifier = Modifier.offset(x = (-8).dp, y = (-8).dp),
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.TopEnd)
|
||||
.offset(x = levelUpOffset, y = -levelUpOffset),
|
||||
visible = character.levelUp,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
|
|
@ -149,14 +162,12 @@ fun CharacterPortrait(
|
|||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.lwa.typography.portrait.value,
|
||||
text = "${stats.hp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.lwa.typography.portrait.max,
|
||||
text = "/${stats.maxHp}",
|
||||
)
|
||||
}
|
||||
|
|
@ -171,14 +182,12 @@ fun CharacterPortrait(
|
|||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.caption,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.lwa.typography.portrait.value,
|
||||
text = "${stats.pp}",
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
fontWeight = FontWeight.Light,
|
||||
style = MaterialTheme.typography.caption,
|
||||
style = MaterialTheme.lwa.typography.portrait.max,
|
||||
text = "/${stats.maxPp}",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ class CharacterRibbonFactory(
|
|||
alterations: Map<String, List<FieldAlteration>>,
|
||||
characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
characterInstance: Campaign.CharacterInstance,
|
||||
enableDetail: Boolean,
|
||||
displayCharacterStats: Boolean,
|
||||
enableCharacterId: Boolean,
|
||||
enableCharacterSheet: Boolean,
|
||||
enableCharacterStats: Boolean,
|
||||
): CharacterPortraitUio? {
|
||||
if (characterSheet == null) return null
|
||||
|
||||
|
|
@ -27,11 +28,12 @@ class CharacterRibbonFactory(
|
|||
|
||||
return CharacterPortraitUio(
|
||||
id = characterInstanceId,
|
||||
idLabel = takeIf { enableCharacterId }?.let { characterInstanceId.instanceId.toString() },
|
||||
portrait = alteredCharacterSheet.thumbnail,
|
||||
name = alteredCharacterSheet.name,
|
||||
levelUp = alteredCharacterSheet.shouldLevelUp,
|
||||
enableDetail = enableDetail,
|
||||
stats = takeIf { displayCharacterStats }?.let {
|
||||
enableDetail = enableCharacterSheet,
|
||||
stats = takeIf { enableCharacterStats }?.let {
|
||||
CharacterPortraitUio.StatsDetail(
|
||||
hp = alteredCharacterSheet.maxHp - characterInstance.damage,
|
||||
maxHp = alteredCharacterSheet.maxHp,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ abstract class CharacterRibbonViewModel(
|
|||
|
||||
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 :
|
||||
* On a campaign update it go through every element of the abstract [data] map and either:
|
||||
|
|
@ -58,14 +61,16 @@ abstract class CharacterRibbonViewModel(
|
|||
combine(
|
||||
characterRepository.characterDetailFlow(characterSheetId = entry.key.characterSheetId),
|
||||
alterationRepository.alterationsFlow(characterInstanceId = entry.key),
|
||||
) { sheet, alterations ->
|
||||
settingsRepository.settingsFlow(),
|
||||
) { sheet, alterations, settings ->
|
||||
ribbonFactory.convertToPlayerPortraitUio(
|
||||
characterSheet = sheet,
|
||||
alterations = alterations,
|
||||
characterInstanceId = entry.key,
|
||||
characterInstance = entry.value,
|
||||
enableDetail = settingsRepository.settings().isGM,
|
||||
displayCharacterStats = settingsRepository.settings().isGM,
|
||||
enableCharacterId = settings.isGM,
|
||||
enableCharacterSheet = enableCharacterSheet || settings.isGM,
|
||||
enableCharacterStats = enableCharacterStats || settings.isGM,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ fun NpcRibbon(
|
|||
items = characters.value,
|
||||
key = { it.id },
|
||||
) {
|
||||
Row {
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
CharacterPortraitRoll(
|
||||
size = CharacterPortraitDefault.size,
|
||||
value = viewModel.roll(characterId = it.id).value,
|
||||
|
|
|
|||
|
|
@ -25,4 +25,7 @@ class NpcRibbonViewModel(
|
|||
ribbonFactory = ribbonFactory,
|
||||
) {
|
||||
override val Campaign.data get() = npcs
|
||||
|
||||
override val enableCharacterSheet = false
|
||||
override val enableCharacterStats = false
|
||||
}
|
||||
|
|
@ -33,7 +33,9 @@ fun PlayerRibbon(
|
|||
items = characters.value,
|
||||
key = { it.id },
|
||||
) {
|
||||
Row {
|
||||
Row(
|
||||
modifier = Modifier.animateItem(),
|
||||
) {
|
||||
CharacterPortrait(
|
||||
character = it,
|
||||
onCharacter = onCharacter,
|
||||
|
|
|
|||
|
|
@ -25,4 +25,7 @@ class PlayerRibbonViewModel(
|
|||
ribbonFactory = ribbonFactory,
|
||||
) {
|
||||
override val Campaign.data get() = characters
|
||||
|
||||
override val enableCharacterSheet = true
|
||||
override val enableCharacterStats = true
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.compose.animation.fadeIn
|
|||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Icon
|
||||
|
|
@ -22,7 +21,6 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -57,15 +55,15 @@ fun CampaignToolbar(
|
|||
|
||||
val title = viewModel.title.collectAsState()
|
||||
val status = viewModel.status.collectAsState()
|
||||
val isGM = viewModel.isGM.collectAsState()
|
||||
val isAdmin = viewModel.isAdmin.collectAsState()
|
||||
|
||||
CampaignToolbarContent(
|
||||
title = title,
|
||||
status = status,
|
||||
isGM = isGM,
|
||||
isAdmin = isAdmin,
|
||||
isNetworkMenuOpen = isNetworkMenuOpen,
|
||||
isOverflowMenuOpen = isOverflowMenuOpen,
|
||||
onGM = {
|
||||
onAdmin = {
|
||||
windows.navigateToGameMasterWindow()
|
||||
},
|
||||
onNetwork = {
|
||||
|
|
@ -96,10 +94,10 @@ private fun CampaignToolbarContent(
|
|||
modifier: Modifier = Modifier,
|
||||
title: State<String>,
|
||||
status: State<NetworkRepository.Status>,
|
||||
isGM: State<Boolean>,
|
||||
isAdmin: State<Boolean>,
|
||||
isNetworkMenuOpen: State<Boolean>,
|
||||
isOverflowMenuOpen: State<Boolean>,
|
||||
onGM: () -> Unit,
|
||||
onAdmin: () -> Unit,
|
||||
onNetwork: () -> Unit,
|
||||
onOverflow: () -> Unit,
|
||||
onRollHistory: () -> Unit,
|
||||
|
|
@ -116,17 +114,16 @@ private fun CampaignToolbarContent(
|
|||
},
|
||||
actions = {
|
||||
AnimatedVisibility(
|
||||
visible = isGM.value,
|
||||
visible = isAdmin.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
TextButton(
|
||||
modifier = Modifier.size(size = 48.dp).clip(shape = CircleShape),
|
||||
onClick = onGM,
|
||||
onClick = onAdmin,
|
||||
) {
|
||||
Text(
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
text = "GM",
|
||||
text = "Admin",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ class CampaignToolbarViewModel(
|
|||
initialValue = "",
|
||||
)
|
||||
|
||||
val isGM = settingsRepository.settingsFlow()
|
||||
.map { it.isGM }
|
||||
val isAdmin = settingsRepository.settingsFlow()
|
||||
.map { it.isAdmin }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
||||
|
||||
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(
|
||||
private val campaignRepository: CampaignRepository,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
||||
|
||||
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.GMCharacterPreviewUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -18,7 +18,7 @@ class GameMasterFactory {
|
|||
characters: List<CharacterSheetPreview>,
|
||||
filter: String,
|
||||
tags: Map<GMTagUio.TagId, Boolean>,
|
||||
): List<GMCharacterPreviewUio> {
|
||||
): List<GMCharacterUio> {
|
||||
val normalizedFilter = Normalizer.normalize(filter, Normalizer.Form.NFD)
|
||||
|
||||
return characters.mapNotNull {
|
||||
|
|
@ -36,7 +36,7 @@ class GameMasterFactory {
|
|||
character: CharacterSheetPreview,
|
||||
filter: String,
|
||||
tags: Map<GMTagUio.TagId, Boolean>,
|
||||
): GMCharacterPreviewUio? {
|
||||
): GMCharacterUio? {
|
||||
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
||||
val characterInstanceId: Campaign.CharacterInstance.Id? =
|
||||
campaign.characters.keys.firstOrNull {
|
||||
|
|
@ -106,9 +106,10 @@ class GameMasterFactory {
|
|||
)
|
||||
}
|
||||
// return the cell UIO.
|
||||
return GMCharacterPreviewUio(
|
||||
return GMCharacterUio(
|
||||
characterSheetId = character.characterSheetId,
|
||||
name = character.name, level = character.level,
|
||||
name = character.name,
|
||||
level = character.level,
|
||||
tags = previewTagsList,
|
||||
actions = actions,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
|||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
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.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
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.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.LwaTextFieldUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreview
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
|
||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
|
||||
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.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
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 org.jetbrains.compose.resources.getString
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
|
|
@ -50,6 +61,9 @@ import org.koin.compose.viewmodel.koinViewModel
|
|||
fun GameMasterScreen(
|
||||
viewModel: GameMasterViewModel = koinViewModel(),
|
||||
) {
|
||||
val windows = LocalWindowController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
val tags = viewModel.tags.collectAsState()
|
||||
|
||||
|
|
@ -59,10 +73,27 @@ fun GameMasterScreen(
|
|||
GameMasterContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
filter = viewModel.filter,
|
||||
onGameMaster = viewModel::onGameMaster,
|
||||
tags = tags,
|
||||
characters = characters,
|
||||
onTag = viewModel::onTag,
|
||||
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(),
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagUio>>,
|
||||
characters: State<List<GMCharacterPreviewUio>>,
|
||||
characters: State<List<GMCharacterUio>>,
|
||||
onGameMaster: () -> Unit,
|
||||
onTag: (GMTagUio.TagId) -> Unit,
|
||||
onCharacterAction: (String, GMCharacterPreviewUio.Action) -> Unit,
|
||||
onCharacterAction: (String, GMCharacterUio.Action) -> Unit,
|
||||
onCharacterSheetEdit: (String) -> Unit,
|
||||
onCharacterSheetCreate: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -87,6 +121,13 @@ private fun GameMasterContent(
|
|||
Text(
|
||||
text = "",
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
TextButton(
|
||||
onClick = onGameMaster,
|
||||
) {
|
||||
Text(text = "GameMaster")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
|
@ -94,65 +135,105 @@ private fun GameMasterContent(
|
|||
Column(
|
||||
modifier = Modifier.padding(paddingValues = paddingValues)
|
||||
) {
|
||||
LwaTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = filter,
|
||||
trailingIcon = {
|
||||
val value = filter.valueFlow.collectAsState()
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
Surface(
|
||||
elevation = 1.dp,
|
||||
) {
|
||||
Column {
|
||||
LwaTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = filter,
|
||||
trailingIcon = {
|
||||
val value = filter.valueFlow.collectAsState()
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { filter.onValueChange.invoke("") },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_cancel_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
filterChipsState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = filterChipsState,
|
||||
contentPadding = remember { PaddingValues(all = 8.dp) },
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { filter.onValueChange.invoke("") },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_cancel_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
items(
|
||||
items = tags.value,
|
||||
) { tag ->
|
||||
GMTag(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
tag = tag,
|
||||
onTag = { onTag(tag.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
filterChipsState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = filterChipsState,
|
||||
contentPadding = remember { PaddingValues(all = 8.dp) },
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
items(
|
||||
items = tags.value,
|
||||
) { tag ->
|
||||
GMTag(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
tag = tag,
|
||||
onTag = { onTag(tag.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentPadding = remember { PaddingValues(all = 8.dp) },
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
) {
|
||||
items(
|
||||
items = characters.value,
|
||||
) { character ->
|
||||
GMCharacterPreview(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
character = character,
|
||||
onAction = { action ->
|
||||
onCharacterAction(character.characterSheetId, action)
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
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),
|
||||
) {
|
||||
items(
|
||||
items = characters.value,
|
||||
key = { it.characterSheetId },
|
||||
) { character ->
|
||||
GMCharacter(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateItem(),
|
||||
character = character,
|
||||
onEdit = {
|
||||
onCharacterSheetEdit(character.characterSheetId)
|
||||
},
|
||||
onAction = { 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||
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.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.TagId
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -20,9 +21,10 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character_t
|
|||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class GameMasterViewModel(
|
||||
private val campaignRepository: CampaignRepository,
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val gameMasterFactory: GameMasterFactory,
|
||||
campaignRepository: CampaignRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val factory: GameMasterFactory,
|
||||
private val useCase: GameMasterActionUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -63,16 +65,25 @@ class GameMasterViewModel(
|
|||
characterSheetRepository.characterSheetPreviewFlow,
|
||||
filter.valueFlow,
|
||||
_tags,
|
||||
gameMasterFactory::convertToGMCharacterPreviewUio,
|
||||
factory::convertToGMCharacterPreviewUio,
|
||||
).stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
fun onGameMaster() {
|
||||
val settings = settingsRepository.settings()
|
||||
settingsRepository.update(
|
||||
settings = settings.copy(
|
||||
isGM = settings.isGM.not(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun onCharacterAction(
|
||||
characterSheetId: String,
|
||||
action: GMCharacterPreviewUio.Action,
|
||||
action: GMCharacterUio.Action,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.material.IconButton
|
|||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.runtime.Composable
|
||||
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.unit.DpOffset
|
||||
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 lwacharactersheet.composeapp.generated.resources.Res
|
||||
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_npc
|
||||
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
|
||||
|
||||
@Stable
|
||||
data class GMCharacterPreviewUio(
|
||||
data class GMCharacterUio(
|
||||
val characterSheetId: String,
|
||||
val name: String,
|
||||
val level: Int,
|
||||
|
|
@ -47,21 +55,33 @@ data class GMCharacterPreviewUio(
|
|||
val actions: List<Action>,
|
||||
) {
|
||||
@Stable
|
||||
sealed class Action {
|
||||
sealed class Action(
|
||||
val icon: DrawableResource,
|
||||
) {
|
||||
@Stable
|
||||
data object DisplayPortrait : Action()
|
||||
data object DisplayPortrait : Action(
|
||||
icon = Res.drawable.ic_imagesmode_24dp,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data object AddToGroup : Action()
|
||||
data object AddToGroup : Action(
|
||||
icon = Res.drawable.ic_group_24dp,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class RemoveFromGroup(val instanceId: Int) : Action()
|
||||
data class RemoveFromGroup(val instanceId: Int) : Action(
|
||||
icon = Res.drawable.ic_group_off_24dp,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data object AddToNpc : Action()
|
||||
data object AddToNpc : Action(
|
||||
icon = Res.drawable.ic_face_24dp,
|
||||
)
|
||||
|
||||
@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
|
||||
fun GMCharacterPreview(
|
||||
fun GMCharacter(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = GMCharacterPreviewDefault.padding,
|
||||
character: GMCharacterPreviewUio,
|
||||
character: GMCharacterUio,
|
||||
onEdit: () -> Unit,
|
||||
onAction: (Action) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
|
|
@ -108,20 +129,22 @@ fun GMCharacterPreview(
|
|||
),
|
||||
)
|
||||
}
|
||||
OverflowActionMenu(
|
||||
MenuActions(
|
||||
character = character,
|
||||
onEdit = onEdit,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues = padding)
|
||||
.padding(bottom = 16.dp),
|
||||
.padding(bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
character.tags.forEach { tag ->
|
||||
GMTag(
|
||||
style = MaterialTheme.lwa.typography.base.caption,
|
||||
elevation = 4.dp,
|
||||
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
|
||||
private fun OverflowActionMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
character: GMCharacterPreviewUio,
|
||||
character: GMCharacterUio,
|
||||
onAction: (Action) -> Unit,
|
||||
) {
|
||||
val overflowMenu = remember(character) {
|
||||
|
|
@ -165,33 +214,43 @@ private fun OverflowActionMenu(
|
|||
onAction(action)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
text = when (action) {
|
||||
Action.DisplayPortrait -> stringResource(
|
||||
Res.string.game_master__character_action__display_portrait,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(action.icon),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.lwa.typography.base.body1,
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
text = when (action) {
|
||||
Action.DisplayPortrait -> stringResource(
|
||||
Res.string.game_master__character_action__display_portrait,
|
||||
)
|
||||
|
||||
Action.AddToGroup -> stringResource(
|
||||
Res.string.game_master__character_action__add_to_group,
|
||||
)
|
||||
Action.AddToGroup -> stringResource(
|
||||
Res.string.game_master__character_action__add_to_group,
|
||||
)
|
||||
|
||||
Action.AddToNpc -> stringResource(
|
||||
Res.string.game_master__character_action__add_to_npc,
|
||||
)
|
||||
Action.AddToNpc -> stringResource(
|
||||
Res.string.game_master__character_action__add_to_npc,
|
||||
)
|
||||
|
||||
is Action.RemoveFromGroup -> stringResource(
|
||||
Res.string.game_master__character_action__remove_from_group,
|
||||
action.instanceId,
|
||||
)
|
||||
is Action.RemoveFromGroup -> stringResource(
|
||||
Res.string.game_master__character_action__remove_from_group,
|
||||
action.instanceId,
|
||||
)
|
||||
|
||||
is Action.RemoveFromNpc -> stringResource(
|
||||
Res.string.game_master__character_action__remove_from_npc,
|
||||
action.instanceId,
|
||||
)
|
||||
}
|
||||
)
|
||||
is Action.RemoveFromNpc -> stringResource(
|
||||
Res.string.game_master__character_action__remove_from_npc,
|
||||
action.instanceId,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -4,12 +4,16 @@ import androidx.compose.material.Typography
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
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.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.typography.LwaTypography.Settings
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
|
|
@ -22,6 +26,7 @@ data class LwaTypography(
|
|||
val base: Typography,
|
||||
val chat: Chat,
|
||||
val settings: Settings,
|
||||
val portrait: Portrait,
|
||||
) {
|
||||
@Stable
|
||||
data class Chat(
|
||||
|
|
@ -36,6 +41,13 @@ data class LwaTypography(
|
|||
val input: TextStyle,
|
||||
val description: TextStyle,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class Portrait(
|
||||
val idLabel: TextStyle,
|
||||
val value: TextStyle,
|
||||
val max: TextStyle,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -90,6 +102,32 @@ fun lwaTypography(
|
|||
fontStyle = FontStyle.Italic,
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class SettingsUseCase {
|
|||
autoHideDelay = 8,
|
||||
autoShowChat = true,
|
||||
autoScrollChat = true,
|
||||
isAdmin = false,
|
||||
isGM = false,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue