Add the ability to roll from the admin screen.

This commit is contained in:
Thomas Andres Gomez 2025-03-27 17:17:14 +01:00
parent 2fb0d3d4cd
commit ee4445490c
10 changed files with 278 additions and 223 deletions

View file

@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class CharacterSheetRepository( class CharacterSheetRepository(
private val store: CharacterSheetStore, private val store: CharacterSheetStore,
@ -47,6 +48,11 @@ class CharacterSheetRepository(
characterSheetId: String?, characterSheetId: String?,
): StateFlow<CharacterSheet?> { ): StateFlow<CharacterSheet?> {
val initial = store.detailFlow.value[characterSheetId] val initial = store.detailFlow.value[characterSheetId]
if (initial == null) {
scope.launch { characterDetail(characterSheetId = characterSheetId) }
}
return store.detailFlow return store.detailFlow
.map { sheets -> .map { sheets ->
sheets[characterSheetId] sheets[characterSheetId]

View file

@ -1,12 +1,5 @@
package com.pixelized.desktop.lwa.ui.composable.character.characteristic package com.pixelized.desktop.lwa.ui.composable.character.characteristic
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -38,6 +31,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
@ -67,35 +61,22 @@ fun CharacterSheetCharacteristicDialog(
onConfirm: (CharacterSheetCharacteristicDialogUio) -> Unit, onConfirm: (CharacterSheetCharacteristicDialogUio) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
AnimatedContent( dialog.value?.let {
modifier = Modifier.fillMaxSize(), Dialog(
targetState = dialog.value, onDismissRequest = onDismissRequest,
transitionSpec = { content = {
val enter = fadeIn() + slideInVertically { 32 } CharacterSheetCharacteristicContent(
val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
Box(
modifier = Modifier.fillMaxSize()
) {
when (it) {
null -> Box(
modifier = Modifier,
)
else -> Dialog(
dialog = it, dialog = it,
onConfirm = onConfirm, onConfirm = onConfirm,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) )
} }
} )
} }
} }
@Composable @Composable
private fun Dialog( private fun CharacterSheetCharacteristicContent(
dialog: CharacterSheetCharacteristicDialogUio, dialog: CharacterSheetCharacteristicDialogUio,
onConfirm: (CharacterSheetCharacteristicDialogUio) -> Unit, onConfirm: (CharacterSheetCharacteristicDialogUio) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,

View file

@ -21,7 +21,7 @@ fun WindowController.navigateToGameMasterWindow(
window = GameMasterWindow( window = GameMasterWindow(
title = title, title = title,
size = DpSize( size = DpSize(
width = 124.dp * 4 + 64.dp, width = 124.dp * 4 + 108.dp,
height = maxWindowHeight - 32.dp, height = maxWindowHeight - 32.dp,
) )
) )

View file

@ -16,7 +16,6 @@ import kotlinx.coroutines.flow.stateIn
class CharacterDetailViewModel( class CharacterDetailViewModel(
private val characterSheetRepository: CharacterSheetRepository, private val characterSheetRepository: CharacterSheetRepository,
private val campaignRepository: CampaignRepository,
private val alterationRepository: AlterationRepository, private val alterationRepository: AlterationRepository,
private val characterDetailFactory: CharacterDetailFactory, private val characterDetailFactory: CharacterDetailFactory,
private val network: NetworkRepository, private val network: NetworkRepository,

View file

@ -31,9 +31,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
@ -51,7 +48,7 @@ import org.jetbrains.compose.resources.stringResource
data class CharacterSheetCopyDialogUio( data class CharacterSheetCopyDialogUio(
val label: String, val label: String,
val value: LwaTextFieldUio, val value: LwaTextFieldUio,
val validate: () -> Boolean val validate: () -> Boolean,
) )
@Composable @Composable
@ -165,7 +162,11 @@ private fun Dialog(
) )
} }
TextButton( TextButton(
onClick = { if(dialog.validate()) {onConfirm(dialog)} }, onClick = {
if (dialog.validate()) {
onConfirm(dialog)
}
},
) { ) {
Text( Text(
text = stringResource(Res.string.dialog__confirm_action) text = stringResource(Res.string.dialog__confirm_action)

View file

@ -1,12 +1,5 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -26,14 +19,11 @@ import androidx.compose.runtime.State
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.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__description import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__description
import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__title import lwacharactersheet.composeapp.generated.resources.character_sheet__delete_dialog__title
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
@ -52,35 +42,22 @@ fun CharacterSheetDeleteConfirmationDialog(
onConfirm: (CharacterSheetDeleteConfirmationDialogUio) -> Unit, onConfirm: (CharacterSheetDeleteConfirmationDialogUio) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
AnimatedContent( dialog.value?.let {
modifier = Modifier.fillMaxSize(), Dialog(
targetState = dialog.value, onDismissRequest = onDismissRequest,
transitionSpec = { content = {
val enter = fadeIn() + slideInVertically { 32 } CharacterSheetDeleteConfirmationContent(
val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
Box(
modifier = Modifier.fillMaxSize()
) {
when (it) {
null -> Box(
modifier = Modifier,
)
else -> Dialog(
dialog = it, dialog = it,
onConfirm = onConfirm, onConfirm = onConfirm,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) )
} }
} )
} }
} }
@Composable @Composable
private fun Dialog( private fun CharacterSheetDeleteConfirmationContent(
dialog: CharacterSheetDeleteConfirmationDialogUio, dialog: CharacterSheetDeleteConfirmationDialogUio,
onConfirm: (CharacterSheetDeleteConfirmationDialogUio) -> Unit, onConfirm: (CharacterSheetDeleteConfirmationDialogUio) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,

View file

@ -1,12 +1,5 @@
package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog package com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -38,9 +31,9 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape
import com.pixelized.shared.lwa.model.campaign.Campaign
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action
import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action
@ -60,35 +53,22 @@ fun DiminishedStatDialog(
onConfirm: (CharacterSheetDiminishedStatDialogUio) -> Unit, onConfirm: (CharacterSheetDiminishedStatDialogUio) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
AnimatedContent( dialog.value?.let {
modifier = Modifier.fillMaxSize(), Dialog(
targetState = dialog.value, onDismissRequest = onDismissRequest,
transitionSpec = { content = {
val enter = fadeIn() + slideInVertically { 32 } DiminishedDialogContent(
val exit = fadeOut() + slideOutVertically { 32 }
enter togetherWith exit using SizeTransform(clip = false)
},
) {
Box(
modifier = Modifier.fillMaxSize()
) {
when (it) {
null -> Box(
modifier = Modifier,
)
else -> Dialog(
dialog = it, dialog = it,
onConfirm = onConfirm, onConfirm = onConfirm,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) )
} }
} )
} }
} }
@Composable @Composable
private fun Dialog( private fun DiminishedDialogContent(
dialog: CharacterSheetDiminishedStatDialogUio, dialog: CharacterSheetDiminishedStatDialogUio,
onConfirm: (CharacterSheetDiminishedStatDialogUio) -> Unit, onConfirm: (CharacterSheetDiminishedStatDialogUio) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,

View file

@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
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.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
@ -19,14 +20,24 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf
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.draw.clip
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.pixelized.desktop.lwa.LocalBlurController
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedViewModel
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMActionDestination import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMActionDestination
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
@ -37,6 +48,9 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.nav
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterAlterationPage
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterCharacterPage
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterObjectPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterObjectPage
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTab import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTab
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTabUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTabUio
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors
@ -47,34 +61,36 @@ import lwacharactersheet.composeapp.generated.resources.game_master__title
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun GameMasterScreen( fun GameMasterScreen(
viewModel: GameMasterViewModel = koinViewModel(), gameMasterViewModel: GameMasterViewModel = koinViewModel(),
) { ) {
val screen = rememberNavController() val screen = rememberNavController()
val gameMaster = viewModel.isGameMaster.collectAsState() val gameMaster = gameMasterViewModel.isGameMaster.collectAsState()
Surface( CompositionLocalProvider(
modifier = Modifier.fillMaxSize() LocalScreenController provides screen,
) { ) {
CompositionLocalProvider( Surface(
LocalScreenController provides screen, modifier = Modifier.fillMaxSize()
) { ) {
GameMasterContent( Box {
modifier = Modifier.fillMaxSize(), GameMasterContent(
controller = screen, modifier = Modifier.fillMaxSize(),
gameMaster = gameMaster, controller = screen,
onGameMaster = viewModel::onGameMaster, gameMaster = gameMaster,
onTab = { onGameMaster = gameMasterViewModel::onGameMaster,
when (it) { onTab = {
GMTabUio.Actions -> screen.navigateToGameMasterActionPage() when (it) {
GMTabUio.Characters -> screen.navigateToGameMasterCharacterPage() GMTabUio.Actions -> screen.navigateToGameMasterActionPage()
GMTabUio.Alterations -> screen.navigateToGameMasterAlterationPage() GMTabUio.Characters -> screen.navigateToGameMasterCharacterPage()
GMTabUio.Objects -> screen.navigateToGameMasterObjectPage() GMTabUio.Alterations -> screen.navigateToGameMasterAlterationPage()
} GMTabUio.Objects -> screen.navigateToGameMasterObjectPage()
}, }
) },
)
}
} }
} }
} }
@ -121,44 +137,46 @@ private fun GameMasterContent(
) )
}, },
content = { paddingValues -> content = { paddingValues ->
Row( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues), .padding(paddingValues),
) { ) {
Surface( Row {
elevation = 1.dp, Surface(
) { elevation = 1.dp,
Column(
modifier = Modifier
.fillMaxHeight()
.width(width = 64.dp)
.padding(vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) { ) {
GMTabUio.entries.forEach { Column(
GMTab( modifier = Modifier
tab = it, .fillMaxHeight()
onClick = { onTab(it) }, .width(width = 64.dp)
) .padding(vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) {
GMTabUio.entries.forEach {
GMTab(
tab = it,
onClick = { onTab(it) },
)
}
} }
} }
} Surface(
Surface( modifier = Modifier
modifier = Modifier .fillMaxHeight()
.fillMaxHeight() .weight(weight = 1f),
.weight(weight = 1f),
) {
NavHost(
modifier = Modifier.fillMaxSize(),
navController = controller,
startDestination = GMActionDestination.navigationRoute(),
) { ) {
composableGameMasterActionPage() NavHost(
composableGameMasterCharacterPage() modifier = Modifier.fillMaxSize(),
composableGameMasterAlterationPage() navController = controller,
composableGameMasterObjectPage() startDestination = GMActionDestination.navigationRoute(),
) {
composableGameMasterActionPage()
composableGameMasterCharacterPage()
composableGameMasterAlterationPage()
composableGameMasterObjectPage()
}
} }
} }
} }

View file

@ -5,8 +5,10 @@ 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.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
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.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -23,17 +25,30 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalBlurController
import com.pixelized.desktop.lwa.LocalWindowController import com.pixelized.desktop.lwa.LocalWindowController
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialog
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedViewModel
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacter import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacter
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMFilterHeader import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMFilterHeader
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
import kotlinx.coroutines.flow.StateFlow
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__create__title
@ -46,35 +61,99 @@ import org.koin.compose.viewmodel.koinViewModel
@Composable @Composable
fun GMCharacterPage( fun GMCharacterPage(
viewModel: GMCharacterViewModel = koinViewModel(), viewModel: GMCharacterViewModel = koinViewModel(),
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
dismissedViewModel: CharacterSheetDiminishedViewModel = koinViewModel(),
) { ) {
val windows = LocalWindowController.current val windows = LocalWindowController.current
val blurController = LocalBlurController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val characters = viewModel.characters.collectAsState() val characters = viewModel.characters.collectAsState()
val tags = viewModel.tags.collectAsState() val tags = viewModel.tags.collectAsState()
GMCharacterContent( Box {
filter = viewModel.filter, GMCharacterContent(
tags = tags, filter = viewModel.filter,
characters = characters, tags = tags,
onTag = viewModel::onTag, characters = characters,
onCharacterAction = viewModel::onCharacterAction, onTag = viewModel::onTag,
onCharacterSheetEdit = { characterSheetId -> onCharacterAction = viewModel::onCharacterAction,
scope.launch { onCharacterSheetDetail = { characterSheetId ->
windows.navigateToCharacterSheetEdit( characterDetailViewModel.showCharacter(characterSheetId = characterSheetId)
characterId = characterSheetId, },
title = getString(Res.string.character_sheet_edit__edit__title), onCharacterSheetEdit = { characterSheetId ->
) scope.launch {
} windows.navigateToCharacterSheetEdit(
}, characterId = characterSheetId,
onCharacterSheetCreate = { title = getString(Res.string.character_sheet_edit__edit__title),
scope.launch { )
windows.navigateToCharacterSheetEdit( }
characterId = null, },
title = getString(Res.string.character_sheet_edit__create__title), onCharacterSheetCreate = {
) scope.launch {
} windows.navigateToCharacterSheetEdit(
}, characterId = null,
title = getString(Res.string.character_sheet_edit__create__title),
)
}
},
)
CharacterDetailPanel(
modifier = Modifier
.align(alignment = Alignment.TopEnd)
.padding(all = 8.dp)
.width(width = 128.dp * 4)
.fillMaxHeight(),
transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl),
blurController = blurController,
detailViewModel = characterDetailViewModel,
characterDiminishedViewModel = dismissedViewModel,
characteristicDialogViewModel = characteristicDialogViewModel,
)
CharacterSheetCharacteristicDialog(
dialog = characteristicDialogViewModel.statChangeDialog,
onConfirm = { dialog ->
scope.launch {
characteristicDialogViewModel.changeSubCharacteristic(
characterSheetId = dialog.characterSheetId,
characteristic = dialog.characteristic,
value = dialog.value().text.toIntOrNull() ?: 0,
)
characteristicDialogViewModel.hideSubCharacteristicDialog()
blurController.hide()
}
},
onDismissRequest = {
characteristicDialogViewModel.hideSubCharacteristicDialog()
blurController.hide()
},
)
DiminishedStatDialog(
dialog = dismissedViewModel.diminishedDialog,
onConfirm = { diminished ->
scope.launch {
dismissedViewModel.changeDiminished(
dialog = diminished
)
blurController.hide()
dismissedViewModel.hideDiminishedDialog()
}
},
onDismissRequest = {
blurController.hide()
dismissedViewModel.hideDiminishedDialog()
},
)
}
GameMasterCharacterKeyHandler(
onDismissRequest = {
characterDetailViewModel.hideCharacter()
}
) )
} }
@ -88,6 +167,7 @@ fun GMCharacterContent(
characters: State<List<GMCharacterItemUio>>, characters: State<List<GMCharacterItemUio>>,
onTag: (GMTagItemUio.TagId) -> Unit, onTag: (GMTagItemUio.TagId) -> Unit,
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit, onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
onCharacterSheetDetail: (String) -> Unit,
onCharacterSheetEdit: (String) -> Unit, onCharacterSheetEdit: (String) -> Unit,
onCharacterSheetCreate: () -> Unit, onCharacterSheetCreate: () -> Unit,
) { ) {
@ -129,7 +209,10 @@ fun GMCharacterContent(
.fillMaxWidth() .fillMaxWidth()
.animateItem(), .animateItem(),
character = character, character = character,
onEdit = { onClick = {
onCharacterSheetDetail(character.characterSheetId)
},
onSecondary = {
onCharacterSheetEdit(character.characterSheetId) onCharacterSheetEdit(character.characterSheetId)
}, },
onAction = { action -> onAction = { action ->
@ -162,4 +245,20 @@ fun GMCharacterContent(
} }
} }
} }
}
@Composable
private fun GameMasterCharacterKeyHandler(
onDismissRequest: () -> Unit,
) {
KeyHandler {
when {
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {
onDismissRequest()
true
}
else -> false
}
}
} }

View file

@ -1,5 +1,7 @@
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -8,7 +10,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.onClick
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
@ -24,8 +26,8 @@ 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.draw.clip
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
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.GMCharacterItemUio.Action import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
@ -42,6 +44,7 @@ import lwacharactersheet.composeapp.generated.resources.ic_group_24dp
import lwacharactersheet.composeapp.generated.resources.ic_group_off_24dp import lwacharactersheet.composeapp.generated.resources.ic_group_off_24dp
import lwacharactersheet.composeapp.generated.resources.ic_imagesmode_24dp import lwacharactersheet.composeapp.generated.resources.ic_imagesmode_24dp
import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@ -56,30 +59,36 @@ data class GMCharacterItemUio(
@Stable @Stable
sealed class Action( sealed class Action(
val icon: DrawableResource, val icon: DrawableResource,
val label: StringResource,
) { ) {
@Stable @Stable
data object DisplayPortrait : Action( data object DisplayPortrait : Action(
icon = Res.drawable.ic_imagesmode_24dp, icon = Res.drawable.ic_imagesmode_24dp,
label = Res.string.game_master__character_action__display_portrait,
) )
@Stable @Stable
data object AddToGroup : Action( data object AddToGroup : Action(
icon = Res.drawable.ic_group_24dp, icon = Res.drawable.ic_group_24dp,
label = Res.string.game_master__character_action__add_to_group,
) )
@Stable @Stable
data object RemoveFromGroup : Action( data object RemoveFromGroup : Action(
icon = Res.drawable.ic_group_off_24dp, icon = Res.drawable.ic_group_off_24dp,
label = Res.string.game_master__character_action__remove_from_group,
) )
@Stable @Stable
data object AddToNpc : Action( data object AddToNpc : Action(
icon = Res.drawable.ic_face_24dp, icon = Res.drawable.ic_face_24dp,
label = Res.string.game_master__character_action__add_to_npc,
) )
@Stable @Stable
data object RemoveFromNpc : Action( data object RemoveFromNpc : Action(
icon = Res.drawable.ic_face_retouching_off_24dp, icon = Res.drawable.ic_face_retouching_off_24dp,
label = Res.string.game_master__character_action__remove_from_npc,
) )
} }
} }
@ -88,12 +97,14 @@ object GMCharacterPreviewDefault {
val padding = PaddingValues(horizontal = 16.dp) val padding = PaddingValues(horizontal = 16.dp)
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun GMCharacter( fun GMCharacter(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
padding: PaddingValues = GMCharacterPreviewDefault.padding, padding: PaddingValues = GMCharacterPreviewDefault.padding,
character: GMCharacterItemUio, character: GMCharacterItemUio,
onEdit: () -> Unit, onClick: () -> Unit,
onSecondary: () -> Unit,
onAction: (Action) -> Unit, onAction: (Action) -> Unit,
onTag: (GMTagItemUio.TagId) -> Unit, onTag: (GMTagItemUio.TagId) -> Unit,
) { ) {
@ -103,7 +114,11 @@ fun GMCharacter(
Box( Box(
modifier = Modifier modifier = Modifier
.clip(shape = MaterialTheme.lwa.shapes.gameMaster) .clip(shape = MaterialTheme.lwa.shapes.gameMaster)
.clickable(onClick = onEdit) .onClick(
matcher = PointerMatcher.mouse(PointerButton.Secondary),
onClick = onSecondary,
)
.clickable(onClick = onClick)
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp) .background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
.then(other = modifier), .then(other = modifier),
) { ) {
@ -167,58 +182,37 @@ private fun OverflowActionMenu(
tint = MaterialTheme.colors.primary, tint = MaterialTheme.colors.primary,
contentDescription = null, contentDescription = null,
) )
} DropdownMenu(
DropdownMenu( expanded = overflowMenu.value,
offset = remember { DpOffset(x = -(48.dp + 8.dp), y = -(48.dp)) }, onDismissRequest = {
expanded = overflowMenu.value, overflowMenu.value = false
onDismissRequest = { },
overflowMenu.value = false content = {
}, character.actions.forEach { action ->
content = { DropdownMenuItem(
character.actions.forEach { action -> onClick = {
DropdownMenuItem( overflowMenu.value = false
onClick = { onAction(action)
overflowMenu.value = false },
onAction(action)
},
) {
Row(
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Icon( Row(
painter = painterResource(action.icon), horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
tint = MaterialTheme.lwa.colorScheme.base.primary, verticalAlignment = Alignment.CenterVertically,
contentDescription = null, ) {
) Icon(
Text( painter = painterResource(action.icon),
style = MaterialTheme.lwa.typography.base.body1, tint = MaterialTheme.lwa.colorScheme.base.primary,
color = MaterialTheme.lwa.colorScheme.base.primary, contentDescription = null,
text = when (action) { )
Action.DisplayPortrait -> stringResource( Text(
Res.string.game_master__character_action__display_portrait, style = MaterialTheme.lwa.typography.base.body1,
) color = MaterialTheme.lwa.colorScheme.base.primary,
text = stringResource(action.label),
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.RemoveFromGroup -> stringResource(
Res.string.game_master__character_action__remove_from_group,
)
Action.RemoveFromNpc -> stringResource(
Res.string.game_master__character_action__remove_from_npc,
)
}
)
} }
} }
} },
}, )
) }
} }