GameMaster ui change to allow multiple screens
This commit is contained in:
parent
9d6f8e178b
commit
2fb0d3d4cd
29 changed files with 1034 additions and 513 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="M480,80q50,0 85,35t35,85q0,50 -35,85t-85,35q-50,0 -85,-35t-35,-85q0,-50 35,-85t85,-35ZM480,360q47,0 93,11t83,31q38,19 61,45t23,57v232q0,17 -8,33.5T710,800q-14,14 -32.5,26T636,848v-90q0,-38 -52.5,-62T480,672q-50,0 -96.5,20.5T326,746q38,15 78,21t82,7h34v104q-7,2 -14.5,2L490,880q-36,0 -82.5,-8T319,847q-42,-17 -70.5,-44.5T220,736v-232q0,-31 23,-57t60,-45q38,-20 84,-31t93,-11ZM480,600q33,0 56.5,-23.5T560,520q0,-33 -23.5,-56.5T480,440q-33,0 -56.5,23.5T400,520q0,33 23.5,56.5T480,600Z"
|
||||||
|
android:fillColor="#e3e3e3"/>
|
||||||
|
</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="M120,580q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM120,420q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM240,760q-17,0 -28.5,-11.5T200,720q0,-17 11.5,-28.5T240,680q17,0 28.5,11.5T280,720q0,17 -11.5,28.5T240,760ZM240,600q-17,0 -28.5,-11.5T200,560q0,-17 11.5,-28.5T240,520q17,0 28.5,11.5T280,560q0,17 -11.5,28.5T240,600ZM240,440q-17,0 -28.5,-11.5T200,400q0,-17 11.5,-28.5T240,360q17,0 28.5,11.5T280,400q0,17 -11.5,28.5T240,440ZM240,280q-17,0 -28.5,-11.5T200,240q0,-17 11.5,-28.5T240,200q17,0 28.5,11.5T280,240q0,17 -11.5,28.5T240,280ZM400,620q-25,0 -42.5,-17.5T340,560q0,-25 17.5,-42.5T400,500q25,0 42.5,17.5T460,560q0,25 -17.5,42.5T400,620ZM400,460q-25,0 -42.5,-17.5T340,400q0,-25 17.5,-42.5T400,340q25,0 42.5,17.5T460,400q0,25 -17.5,42.5T400,460ZM400,760q-17,0 -28.5,-11.5T360,720q0,-17 11.5,-28.5T400,680q17,0 28.5,11.5T440,720q0,17 -11.5,28.5T400,760ZM400,280q-17,0 -28.5,-11.5T360,240q0,-17 11.5,-28.5T400,200q17,0 28.5,11.5T440,240q0,17 -11.5,28.5T400,280ZM400,860q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM400,140q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM560,620q-25,0 -42.5,-17.5T500,560q0,-25 17.5,-42.5T560,500q25,0 42.5,17.5T620,560q0,25 -17.5,42.5T560,620ZM560,460q-25,0 -42.5,-17.5T500,400q0,-25 17.5,-42.5T560,340q25,0 42.5,17.5T620,400q0,25 -17.5,42.5T560,460ZM560,760q-17,0 -28.5,-11.5T520,720q0,-17 11.5,-28.5T560,680q17,0 28.5,11.5T600,720q0,17 -11.5,28.5T560,760ZM560,280q-17,0 -28.5,-11.5T520,240q0,-17 11.5,-28.5T560,200q17,0 28.5,11.5T600,240q0,17 -11.5,28.5T560,280ZM560,860q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM560,140q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM720,760q-17,0 -28.5,-11.5T680,720q0,-17 11.5,-28.5T720,680q17,0 28.5,11.5T760,720q0,17 -11.5,28.5T720,760ZM720,600q-17,0 -28.5,-11.5T680,560q0,-17 11.5,-28.5T720,520q17,0 28.5,11.5T760,560q0,17 -11.5,28.5T720,600ZM720,440q-17,0 -28.5,-11.5T680,400q0,-17 11.5,-28.5T720,360q17,0 28.5,11.5T760,400q0,17 -11.5,28.5T720,440ZM720,280q-17,0 -28.5,-11.5T680,240q0,-17 11.5,-28.5T720,200q17,0 28.5,11.5T760,240q0,17 -11.5,28.5T720,280ZM840,580q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6ZM840,420q-8,0 -14,-6t-6,-14q0,-8 6,-14t14,-6q8,0 14,6t6,14q0,8 -6,14t-14,6Z"
|
||||||
|
android:fillColor="#e3e3e3"/>
|
||||||
|
</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="M80,720v-120q0,-66 47,-113t113,-47h360v-40q0,-17 -11.5,-28.5T560,360L400,360q-17,0 -28.5,11.5T360,400h-80q0,-50 35,-85t85,-35h160q50,0 85,35t35,85v160q17,0 28.5,-11.5T720,520v-160q0,-50 35,-85t85,-35h40v80h-40q-17,0 -28.5,11.5T800,360v160q0,50 -35,85t-85,35v80L80,720ZM160,640h440v-120L240,520q-33,0 -56.5,23.5T160,600v40ZM600,640v-120,120Z"
|
||||||
|
android:fillColor="#e3e3e3"/>
|
||||||
|
</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="M120,840v-120h200q-84,-45 -132,-125t-48,-175q0,-142 99,-241t241,-99q142,0 241,99t99,241q0,95 -48,175T640,720h200v120L520,840v-204q78,-14 129,-75t51,-141q0,-92 -64,-156t-156,-64q-92,0 -156,64t-64,156q0,80 51,141t129,75v204L120,840Z"
|
||||||
|
android:fillColor="#e3e3e3"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -241,5 +241,6 @@
|
||||||
<string name="game_master__character_action__remove_from_group">Retirer du groupe</string>
|
<string name="game_master__character_action__remove_from_group">Retirer du groupe</string>
|
||||||
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
|
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
|
||||||
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
|
||||||
|
<string name="game_master__create_character_sheet">Créer un personnage</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package com.pixelized.desktop.lwa
|
package com.pixelized.desktop.lwa
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.ButtonDefaults
|
import androidx.compose.material.ButtonDefaults
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
|
|
@ -13,7 +10,6 @@ import androidx.compose.material.SnackbarDefaults
|
||||||
import androidx.compose.material.SnackbarDuration
|
import androidx.compose.material.SnackbarDuration
|
||||||
import androidx.compose.material.SnackbarHost
|
import androidx.compose.material.SnackbarHost
|
||||||
import androidx.compose.material.SnackbarHostState
|
import androidx.compose.material.SnackbarHostState
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextButton
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -34,9 +30,8 @@ import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
||||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
import com.pixelized.desktop.lwa.ui.composable.LwaScaffold
|
||||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
|
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
|
||||||
import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentController
|
|
||||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler
|
import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler
|
||||||
import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers
|
import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost
|
import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost
|
||||||
|
|
@ -51,7 +46,6 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.GameMasterWind
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow
|
import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
|
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
||||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||||
|
|
@ -97,12 +91,8 @@ val LocalBlurController = compositionLocalOf<BlurContentController> {
|
||||||
@Preview
|
@Preview
|
||||||
fun ApplicationScope.LwaApplication() {
|
fun ApplicationScope.LwaApplication() {
|
||||||
val maxWindowHeight = rememberMaxWindowHeight()
|
val maxWindowHeight = rememberMaxWindowHeight()
|
||||||
val snackHostState = remember { SnackbarHostState() }
|
|
||||||
val errorSnackHostState = remember { SnackbarHostState() }
|
|
||||||
val windowController = remember { WindowController(maxWindowHeight) }
|
val windowController = remember { WindowController(maxWindowHeight) }
|
||||||
val keyEventHandlers = remember { mutableStateListOf<KeyEventHandler>() }
|
val keyEventHandlers = remember { mutableStateListOf<KeyEventHandler>() }
|
||||||
val rollHostState = remember { RollHostState() }
|
|
||||||
val blurController = rememberBlurContentController()
|
|
||||||
val windowsState = rememberWindowState(
|
val windowsState = rememberWindowState(
|
||||||
size = DpSize(
|
size = DpSize(
|
||||||
width = 800.dp,
|
width = 800.dp,
|
||||||
|
|
@ -121,30 +111,31 @@ fun ApplicationScope.LwaApplication() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CompositionLocalProvider(
|
LwaTheme {
|
||||||
LocalApplicationScope provides this,
|
CompositionLocalProvider(
|
||||||
LocalSnackHost provides snackHostState,
|
LocalApplicationScope provides this,
|
||||||
LocalErrorSnackHost provides errorSnackHostState,
|
LocalWindowController provides windowController,
|
||||||
LocalWindowController provides windowController,
|
LocalKeyEventHandlers provides keyEventHandlers,
|
||||||
LocalKeyEventHandlers provides keyEventHandlers,
|
LocalWindowState provides windowsState,
|
||||||
LocalRollHostState provides rollHostState,
|
|
||||||
LocalBlurController provides blurController,
|
|
||||||
LocalWindowState provides windowsState,
|
|
||||||
) {
|
|
||||||
Window(
|
|
||||||
onCloseRequest = ::exitApplication,
|
|
||||||
state = windowsState,
|
|
||||||
title = runBlocking { getString(Res.string.app_name) },
|
|
||||||
onKeyEvent = { event ->
|
|
||||||
keyEventHandlers.reversed().any { it(event) }
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
MainWindowScreen()
|
Window(
|
||||||
|
onCloseRequest = ::exitApplication,
|
||||||
|
state = windowsState,
|
||||||
|
title = runBlocking { getString(Res.string.app_name) },
|
||||||
|
onKeyEvent = { event ->
|
||||||
|
keyEventHandlers.reversed().any { it(event) }
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
MainWindowScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowsHandler(
|
||||||
|
windowController = windowController,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainWindowScreen(
|
private fun MainWindowScreen(
|
||||||
dataSyncViewModel: DataSyncViewModel = koinViewModel(),
|
dataSyncViewModel: DataSyncViewModel = koinViewModel(),
|
||||||
|
|
@ -154,66 +145,14 @@ private fun MainWindowScreen(
|
||||||
dataSyncViewModel.synchronise()
|
dataSyncViewModel.synchronise()
|
||||||
}
|
}
|
||||||
|
|
||||||
val snackHostState = LocalSnackHost.current
|
LwaScaffold(
|
||||||
val errorSnackHostState = LocalErrorSnackHost.current
|
modifier = Modifier.fillMaxSize(),
|
||||||
val windowController = LocalWindowController.current
|
) {
|
||||||
val rollHostState = LocalRollHostState.current
|
MainNavHost()
|
||||||
val blurController = LocalBlurController.current
|
|
||||||
|
|
||||||
LwaTheme {
|
NetworkSnackHandler(
|
||||||
Surface(
|
snack = LocalSnackHost.current,
|
||||||
modifier = Modifier.fillMaxSize()
|
)
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
snackbarHost = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(all = 8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 4.dp)
|
|
||||||
) {
|
|
||||||
SnackbarHost(
|
|
||||||
hostState = snackHostState,
|
|
||||||
snackbar = {
|
|
||||||
Snackbar(
|
|
||||||
snackbarData = it,
|
|
||||||
backgroundColor = MaterialTheme.colors.surface,
|
|
||||||
contentColor = MaterialTheme.colors.onSurface,
|
|
||||||
actionColor = MaterialTheme.colors.onSurface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
SnackbarHost(
|
|
||||||
hostState = errorSnackHostState,
|
|
||||||
snackbar = {
|
|
||||||
Snackbar(
|
|
||||||
snackbarData = it,
|
|
||||||
backgroundColor = MaterialTheme.colors.error,
|
|
||||||
contentColor = MaterialTheme.colors.onError,
|
|
||||||
actionColor = MaterialTheme.colors.onError,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
content = {
|
|
||||||
BlurContent(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
controller = blurController,
|
|
||||||
) {
|
|
||||||
MainNavHost()
|
|
||||||
}
|
|
||||||
RollOverlay(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
hostState = rollHostState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
NetworkSnackHandler(
|
|
||||||
snack = snackHostState,
|
|
||||||
)
|
|
||||||
WindowsHandler(
|
|
||||||
windowController = windowController,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,7 +178,9 @@ private fun WindowsHandler(
|
||||||
|
|
||||||
is RollHistoryWindow -> RollHistoryPage()
|
is RollHistoryWindow -> RollHistoryPage()
|
||||||
|
|
||||||
is GameMasterWindow -> GameMasterScreen()
|
is GameMasterWindow -> LwaScaffold {
|
||||||
|
GameMasterScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsStore
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory
|
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogFactory
|
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogFactory
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel
|
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkFactory
|
||||||
|
|
@ -23,7 +24,6 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.CharacterRibbonFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbonViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbonViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel
|
||||||
|
|
@ -34,9 +34,10 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetV
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditFactory
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditViewModel
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEditViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillFieldFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterActionUseCase
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterFactory
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterFactory
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||||
|
|
@ -120,7 +121,8 @@ val factoryDependencies
|
||||||
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
||||||
factoryOf(::TextMessageFactory)
|
factoryOf(::TextMessageFactory)
|
||||||
factoryOf(::LevelUpFactory)
|
factoryOf(::LevelUpFactory)
|
||||||
factoryOf(::GameMasterFactory)
|
factoryOf(::GMCharacterFactory)
|
||||||
|
factoryOf(::GMActionViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModelDependencies
|
val viewModelDependencies
|
||||||
|
|
@ -140,12 +142,12 @@ val viewModelDependencies
|
||||||
viewModelOf(::CampaignChatViewModel)
|
viewModelOf(::CampaignChatViewModel)
|
||||||
viewModelOf(::SettingsViewModel)
|
viewModelOf(::SettingsViewModel)
|
||||||
viewModelOf(::LevelUpViewModel)
|
viewModelOf(::LevelUpViewModel)
|
||||||
viewModelOf(::GameMasterViewModel)
|
|
||||||
viewModelOf(::PortraitOverlayViewModel)
|
viewModelOf(::PortraitOverlayViewModel)
|
||||||
|
viewModelOf(::GMCharacterViewModel)
|
||||||
|
viewModelOf(::GameMasterViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val useCaseDependencies
|
val useCaseDependencies
|
||||||
get() = module {
|
get() = module {
|
||||||
factoryOf(::SettingsUseCase)
|
factoryOf(::SettingsUseCase)
|
||||||
factoryOf(::GameMasterActionUseCase)
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.SnackbarHost
|
||||||
|
import androidx.compose.material.SnackbarHostState
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.LocalBlurController
|
||||||
|
import com.pixelized.desktop.lwa.LocalErrorSnackHost
|
||||||
|
import com.pixelized.desktop.lwa.LocalRollHostState
|
||||||
|
import com.pixelized.desktop.lwa.LocalSnackHost
|
||||||
|
import com.pixelized.desktop.lwa.Snackbar
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentController
|
||||||
|
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
||||||
|
import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LwaScaffold(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
snackHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||||
|
errorSnackHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||||
|
rollHostState: RollHostState = remember { RollHostState() },
|
||||||
|
blurController: BlurContentController = rememberBlurContentController(),
|
||||||
|
content: @Composable BoxScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalSnackHost provides snackHostState,
|
||||||
|
LocalErrorSnackHost provides errorSnackHostState,
|
||||||
|
LocalRollHostState provides rollHostState,
|
||||||
|
LocalBlurController provides blurController,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
snackbarHost = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(all = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 4.dp)
|
||||||
|
) {
|
||||||
|
SnackbarHost(
|
||||||
|
hostState = snackHostState,
|
||||||
|
snackbar = {
|
||||||
|
Snackbar(
|
||||||
|
snackbarData = it,
|
||||||
|
backgroundColor = MaterialTheme.colors.surface,
|
||||||
|
contentColor = MaterialTheme.colors.onSurface,
|
||||||
|
actionColor = MaterialTheme.colors.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
SnackbarHost(
|
||||||
|
hostState = errorSnackHostState,
|
||||||
|
snackbar = {
|
||||||
|
Snackbar(
|
||||||
|
snackbarData = it,
|
||||||
|
backgroundColor = MaterialTheme.colors.error,
|
||||||
|
contentColor = MaterialTheme.colors.onError,
|
||||||
|
actionColor = MaterialTheme.colors.onError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
BlurContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
controller = blurController,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
RollOverlay(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
hostState = rollHostState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionPage
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
object GMActionDestination {
|
||||||
|
private const val ROUTE = "GameMasterAction"
|
||||||
|
|
||||||
|
fun baseRoute() = ROUTE
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
fun navigationRoute() = ROUTE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableGameMasterActionPage() {
|
||||||
|
composable(
|
||||||
|
route = GMActionDestination.baseRoute(),
|
||||||
|
) {
|
||||||
|
GMActionPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToGameMasterActionPage() {
|
||||||
|
val route = GMActionDestination.navigationRoute()
|
||||||
|
navigate(route = route)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
|
||||||
|
object GMAlterationDestination {
|
||||||
|
private const val ROUTE = "GameMasterAlteration"
|
||||||
|
|
||||||
|
fun baseRoute() = ROUTE
|
||||||
|
fun navigationRoute() = ROUTE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableGameMasterAlterationPage() {
|
||||||
|
composable(
|
||||||
|
route = GMAlterationDestination.baseRoute(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToGameMasterAlterationPage() {
|
||||||
|
val route = GMAlterationDestination.navigationRoute()
|
||||||
|
navigate(route = route)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.GMCharacterPage
|
||||||
|
|
||||||
|
object GMCharacterDestination {
|
||||||
|
private const val ROUTE = "GameMasterCharacter"
|
||||||
|
|
||||||
|
fun baseRoute() = ROUTE
|
||||||
|
fun navigationRoute() = ROUTE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableGameMasterCharacterPage() {
|
||||||
|
composable(
|
||||||
|
route = GMCharacterDestination.baseRoute(),
|
||||||
|
) {
|
||||||
|
GMCharacterPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToGameMasterCharacterPage() {
|
||||||
|
val route = GMCharacterDestination.navigationRoute()
|
||||||
|
navigate(route = route)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
|
||||||
|
object GMObjectDestination {
|
||||||
|
private const val ROUTE = "GameMasterObject"
|
||||||
|
|
||||||
|
fun baseRoute() = ROUTE
|
||||||
|
fun navigationRoute() = ROUTE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableGameMasterObjectPage() {
|
||||||
|
composable(
|
||||||
|
route = GMObjectDestination.baseRoute(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToGameMasterObjectPage() {
|
||||||
|
val route = GMObjectDestination.navigationRoute()
|
||||||
|
navigate(route = route)
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ fun WindowController.navigateToGameMasterWindow(
|
||||||
window = GameMasterWindow(
|
window = GameMasterWindow(
|
||||||
title = title,
|
title = title,
|
||||||
size = DpSize(
|
size = DpSize(
|
||||||
width = 400.dp + 64.dp,
|
width = 124.dp * 4 + 64.dp,
|
||||||
height = maxWindowHeight - 32.dp,
|
height = maxWindowHeight - 32.dp,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class PlayerRibbonViewModel(
|
||||||
campaign: Campaign,
|
campaign: Campaign,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
): Set<String> {
|
): Set<String> {
|
||||||
return if (campaign.options.showParty) campaign.characters else emptySet()
|
return if (campaign.options.showParty || settings.isGameMaster == true) campaign.characters else emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hideOverruled(campaign: Campaign, settings: Settings): Boolean {
|
override fun hideOverruled(campaign: Campaign, settings: Settings): Boolean {
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio.Action
|
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
|
||||||
|
|
||||||
class GameMasterActionUseCase(
|
|
||||||
private val campaignRepository: CampaignRepository,
|
|
||||||
private val networkRepository: NetworkRepository,
|
|
||||||
) {
|
|
||||||
suspend fun handleAction(
|
|
||||||
characterSheetId: String,
|
|
||||||
action: Action,
|
|
||||||
) {
|
|
||||||
when (action) {
|
|
||||||
Action.DisplayPortrait -> networkRepository.share(
|
|
||||||
GameMasterEvent.DisplayPortrait(
|
|
||||||
timestamp = System.currentTimeMillis(),
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Action.AddToGroup -> campaignRepository.addCharacter(
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
)
|
|
||||||
|
|
||||||
Action.AddToNpc -> campaignRepository.addNpc(
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
)
|
|
||||||
|
|
||||||
is Action.RemoveFromGroup -> campaignRepository.removeCharacter(
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
)
|
|
||||||
|
|
||||||
is Action.RemoveFromNpc -> campaignRepository.removeNpc(
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,144 +1,93 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
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.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
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.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.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.layout.width
|
||||||
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.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Icon
|
|
||||||
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.Switch
|
import androidx.compose.material.Switch
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
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.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.remember
|
|
||||||
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.draw.clip
|
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.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.LocalWindowController
|
import androidx.navigation.NavHostController
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
import androidx.navigation.compose.NavHost
|
||||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
|
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacter
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMActionDestination
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTag
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationPage
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterCharacterPage
|
||||||
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterObjectPage
|
||||||
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.navigateToGameMasterActionPage
|
||||||
|
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.navigateToGameMasterObjectPage
|
||||||
|
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.theme.color.component.LwaSwitchColors
|
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaSwitchColors
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
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.game_master__action
|
import lwacharactersheet.composeapp.generated.resources.game_master__action
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__title
|
import lwacharactersheet.composeapp.generated.resources.game_master__title
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_off_24dp
|
|
||||||
import org.jetbrains.compose.resources.getString
|
|
||||||
import org.jetbrains.compose.resources.painterResource
|
|
||||||
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(),
|
viewModel: GameMasterViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
val windows = LocalWindowController.current
|
val screen = rememberNavController()
|
||||||
val scope = rememberCoroutineScope()
|
val gameMaster = viewModel.isGameMaster.collectAsState()
|
||||||
|
|
||||||
val characters = viewModel.characters.collectAsState()
|
|
||||||
val gameMaster = viewModel.gameMaster.collectAsState()
|
|
||||||
val npcVisibility = viewModel.npcVisibility.collectAsState()
|
|
||||||
val tags = viewModel.tags.collectAsState()
|
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
GameMasterContent(
|
CompositionLocalProvider(
|
||||||
modifier = Modifier.fillMaxSize(),
|
LocalScreenController provides screen,
|
||||||
filter = viewModel.filter,
|
) {
|
||||||
tags = tags,
|
GameMasterContent(
|
||||||
gameMaster = gameMaster,
|
modifier = Modifier.fillMaxSize(),
|
||||||
npcVisibility = npcVisibility,
|
controller = screen,
|
||||||
characters = characters,
|
gameMaster = gameMaster,
|
||||||
onTag = viewModel::onTag,
|
onGameMaster = viewModel::onGameMaster,
|
||||||
onGameMaster = viewModel::onGameMaster,
|
onTab = {
|
||||||
onCharacterAction = viewModel::onCharacterAction,
|
when (it) {
|
||||||
onCharacterSheetEdit = { characterSheetId ->
|
GMTabUio.Actions -> screen.navigateToGameMasterActionPage()
|
||||||
scope.launch {
|
GMTabUio.Characters -> screen.navigateToGameMasterCharacterPage()
|
||||||
windows.navigateToCharacterSheetEdit(
|
GMTabUio.Alterations -> screen.navigateToGameMasterAlterationPage()
|
||||||
characterId = characterSheetId,
|
GMTabUio.Objects -> screen.navigateToGameMasterObjectPage()
|
||||||
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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onNpcVisibility = {
|
|
||||||
scope.launch {
|
|
||||||
viewModel.onNpcVisibility()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GameMasterContent(
|
private fun GameMasterContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
padding: Dp = 16.dp,
|
controller: NavHostController = rememberNavController(),
|
||||||
spacing: Dp = 8.dp,
|
|
||||||
filterChipsState: LazyListState = rememberLazyListState(),
|
|
||||||
filter: LwaTextFieldUio,
|
|
||||||
tags: State<List<GMTagUio>>,
|
|
||||||
gameMaster: State<Boolean>,
|
gameMaster: State<Boolean>,
|
||||||
npcVisibility: State<Boolean>,
|
|
||||||
characters: State<List<GMCharacterUio>>,
|
|
||||||
onGameMaster: (Boolean) -> Unit,
|
onGameMaster: (Boolean) -> Unit,
|
||||||
onTag: (GMTagUio.TagId) -> Unit,
|
onTab: (GMTabUio) -> Unit,
|
||||||
onCharacterAction: (String, GMCharacterUio.Action) -> Unit,
|
|
||||||
onCharacterSheetEdit: (String) -> Unit,
|
|
||||||
onCharacterSheetCreate: () -> Unit,
|
|
||||||
onNpcVisibility: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
Scaffold(
|
||||||
|
|
||||||
GameMasterLayout(
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
|
|
@ -171,160 +120,46 @@ private fun GameMasterContent(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = {
|
content = { paddingValues ->
|
||||||
Column {
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues),
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
elevation = 1.dp,
|
elevation = 1.dp,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(
|
||||||
LwaTextField(
|
modifier = Modifier
|
||||||
modifier = Modifier.fillMaxWidth(),
|
.fillMaxHeight()
|
||||||
field = filter,
|
.width(width = 64.dp)
|
||||||
trailingIcon = {
|
.padding(vertical = 8.dp),
|
||||||
val value = filter.valueFlow.collectAsState()
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
AnimatedVisibility(
|
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
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(padding, spacing) {
|
|
||||||
PaddingValues(horizontal = padding, vertical = spacing)
|
|
||||||
},
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = tags.value,
|
|
||||||
) { tag ->
|
|
||||||
GMTag(
|
|
||||||
tag = tag,
|
|
||||||
onTag = { onTag(tag.id) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f),
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.matchParentSize(),
|
|
||||||
contentPadding = remember {
|
|
||||||
PaddingValues(
|
|
||||||
start = padding,
|
|
||||||
top = padding,
|
|
||||||
end = padding,
|
|
||||||
bottom = padding + 48.dp + padding,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
|
||||||
) {
|
) {
|
||||||
items(
|
GMTabUio.entries.forEach {
|
||||||
items = characters.value,
|
GMTab(
|
||||||
key = { it.characterSheetId },
|
tab = it,
|
||||||
) { character ->
|
onClick = { onTab(it) },
|
||||||
GMCharacter(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.animateItem(),
|
|
||||||
character = character,
|
|
||||||
onEdit = {
|
|
||||||
onCharacterSheetEdit(character.characterSheetId)
|
|
||||||
},
|
|
||||||
onAction = { action ->
|
|
||||||
onCharacterAction(character.characterSheetId, action)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Surface(
|
||||||
},
|
modifier = Modifier
|
||||||
fab = {
|
.fillMaxHeight()
|
||||||
Row(
|
.weight(weight = 1f),
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(all = padding),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.background(
|
|
||||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
|
||||||
shape = CircleShape,
|
|
||||||
),
|
|
||||||
onClick = onNpcVisibility,
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
NavHost(
|
||||||
painter = when (npcVisibility.value) {
|
modifier = Modifier.fillMaxSize(),
|
||||||
true -> painterResource(Res.drawable.ic_visibility_off_24dp)
|
navController = controller,
|
||||||
else -> painterResource(Res.drawable.ic_visibility_24dp)
|
startDestination = GMActionDestination.navigationRoute(),
|
||||||
},
|
) {
|
||||||
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
|
composableGameMasterActionPage()
|
||||||
contentDescription = null,
|
composableGameMasterCharacterPage()
|
||||||
)
|
composableGameMasterAlterationPage()
|
||||||
}
|
composableGameMasterObjectPage()
|
||||||
IconButton(
|
}
|
||||||
modifier = Modifier.background(
|
|
||||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
|
||||||
shape = CircleShape,
|
|
||||||
),
|
|
||||||
onClick = onCharacterSheetCreate,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Add,
|
|
||||||
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun GameMasterLayout(
|
|
||||||
modifier: Modifier,
|
|
||||||
topBar: @Composable () -> Unit,
|
|
||||||
content: @Composable () -> Unit,
|
|
||||||
fab: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier,
|
|
||||||
topBar = topBar,
|
|
||||||
content = { paddingValues ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues = paddingValues)
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.align(alignment = Alignment.BottomStart),
|
|
||||||
) {
|
|
||||||
fab()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,83 +2,16 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
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.characterSheet.CharacterSheetRepository
|
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
|
||||||
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
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.GMCharacterUio
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio.TagId
|
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character__filter
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
|
||||||
import org.jetbrains.compose.resources.getString
|
|
||||||
|
|
||||||
class GameMasterViewModel(
|
class GameMasterViewModel(
|
||||||
campaignRepository: CampaignRepository,
|
|
||||||
characterSheetRepository: CharacterSheetRepository,
|
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val networkRepository: NetworkRepository,
|
): ViewModel() {
|
||||||
private val factory: GameMasterFactory,
|
|
||||||
private val useCase: GameMasterActionUseCase,
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
private val _filter = MutableStateFlow("")
|
val isGameMaster = settingsRepository.settingsFlow()
|
||||||
val filter = LwaTextFieldUio(
|
|
||||||
enable = true,
|
|
||||||
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
|
||||||
valueFlow = _filter,
|
|
||||||
isError = MutableStateFlow(false),
|
|
||||||
placeHolderFlow = MutableStateFlow(null),
|
|
||||||
onValueChange = { _filter.value = it },
|
|
||||||
)
|
|
||||||
|
|
||||||
private val _tags = MutableStateFlow(mapOf(TagId.PLAYER to false, TagId.NPC to false))
|
|
||||||
val tags = _tags.map { it: Map<TagId, Boolean> ->
|
|
||||||
it.map { (tag, highlight) ->
|
|
||||||
when (tag) {
|
|
||||||
TagId.PLAYER -> GMTagUio(
|
|
||||||
id = TagId.PLAYER,
|
|
||||||
label = getString(Res.string.game_master__character_tag__character),
|
|
||||||
highlight = highlight,
|
|
||||||
)
|
|
||||||
|
|
||||||
TagId.NPC -> GMTagUio(
|
|
||||||
id = TagId.NPC,
|
|
||||||
label = getString(Res.string.game_master__character_tag__npc),
|
|
||||||
highlight = highlight,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
initialValue = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val characters = combine(
|
|
||||||
campaignRepository.campaignFlow,
|
|
||||||
characterSheetRepository.characterSheetPreviewFlow,
|
|
||||||
filter.valueFlow,
|
|
||||||
_tags,
|
|
||||||
factory::convertToGMCharacterPreviewUio,
|
|
||||||
).stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
initialValue = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val gameMaster = settingsRepository.settingsFlow()
|
|
||||||
.map { it.isGameMaster ?: false }
|
.map { it.isGameMaster ?: false }
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
|
|
@ -86,14 +19,6 @@ class GameMasterViewModel(
|
||||||
initialValue = false,
|
initialValue = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val npcVisibility = campaignRepository.campaignFlow
|
|
||||||
.map { it.options.showNpcs }
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
initialValue = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun onGameMaster(value: Boolean) {
|
fun onGameMaster(value: Boolean) {
|
||||||
val settings = settingsRepository.settings()
|
val settings = settingsRepository.settings()
|
||||||
settingsRepository.update(
|
settingsRepository.update(
|
||||||
|
|
@ -102,36 +27,4 @@ class GameMasterViewModel(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCharacterAction(
|
|
||||||
characterSheetId: String,
|
|
||||||
action: GMCharacterUio.Action,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
useCase.handleAction(
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
action = action,
|
|
||||||
)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onTag(
|
|
||||||
id: TagId,
|
|
||||||
) {
|
|
||||||
_tags.value = _tags.value.toMutableMap().also {
|
|
||||||
it[id] = it.getOrPut(id) { true }.not()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun onNpcVisibility() {
|
|
||||||
networkRepository.share(
|
|
||||||
GameMasterEvent.ToggleNpc(
|
|
||||||
timestamp = System.currentTimeMillis(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.action
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMAction
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_visibility_off_24dp
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class ActionPageUio(
|
||||||
|
val party: Boolean,
|
||||||
|
val npc: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMActionPage(
|
||||||
|
viewModel: GMActionViewModel = koinViewModel(),
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val scroll = rememberScrollState()
|
||||||
|
val actions = viewModel.actions.collectAsState()
|
||||||
|
|
||||||
|
GMActionContent(
|
||||||
|
actions = actions,
|
||||||
|
scroll = scroll,
|
||||||
|
onPartyVisibility = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.onPlayerVisibility()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNpcVisibility = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.onNpcVisibility()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMActionContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
scroll: ScrollState,
|
||||||
|
spacing: Dp = 8.dp,
|
||||||
|
actions: State<ActionPageUio?>,
|
||||||
|
onPartyVisibility: () -> Unit,
|
||||||
|
onNpcVisibility: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.verticalScroll(state = scroll)
|
||||||
|
.padding(vertical = spacing, horizontal = spacing),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
|
) {
|
||||||
|
actions.value?.party?.let { party ->
|
||||||
|
GMAction(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
icon = when (party) {
|
||||||
|
true -> Res.drawable.ic_visibility_off_24dp
|
||||||
|
else -> Res.drawable.ic_visibility_24dp
|
||||||
|
},
|
||||||
|
label = when (party) {
|
||||||
|
true -> "Cacher les Joueurs"
|
||||||
|
else -> "Afficher les Joueurs"
|
||||||
|
},
|
||||||
|
onAction = onPartyVisibility,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
actions.value?.npc?.let { npc ->
|
||||||
|
GMAction(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
icon = when (npc) {
|
||||||
|
true -> Res.drawable.ic_visibility_off_24dp
|
||||||
|
else -> Res.drawable.ic_visibility_24dp
|
||||||
|
},
|
||||||
|
label = when (npc) {
|
||||||
|
true -> "Cacher les NPCs"
|
||||||
|
else -> "Afficher les NPCs"
|
||||||
|
},
|
||||||
|
onAction = onNpcVisibility,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.action
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||||
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
|
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
class GMActionViewModel(
|
||||||
|
private val networkRepository: NetworkRepository,
|
||||||
|
campaignRepository: CampaignRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val actions: StateFlow<ActionPageUio?> = campaignRepository.campaignFlow
|
||||||
|
.map {
|
||||||
|
ActionPageUio(
|
||||||
|
party = it.options.showParty,
|
||||||
|
npc = it.options.showNpcs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Lazily,
|
||||||
|
initialValue = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun onNpcVisibility() {
|
||||||
|
networkRepository.share(
|
||||||
|
GameMasterEvent.ToggleNpc(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun onPlayerVisibility() {
|
||||||
|
networkRepository.share(
|
||||||
|
GameMasterEvent.TogglePlayer(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterUio.Action
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio.Action
|
||||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||||
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
import com.pixelized.desktop.lwa.utils.extention.unAccent
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview
|
||||||
|
|
@ -11,14 +11,14 @@ import lwacharactersheet.composeapp.generated.resources.game_master__character_t
|
||||||
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
||||||
import org.jetbrains.compose.resources.getString
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
class GameMasterFactory {
|
class GMCharacterFactory {
|
||||||
|
|
||||||
suspend fun convertToGMCharacterPreviewUio(
|
suspend fun convertToGMCharacterPreviewUio(
|
||||||
campaign: Campaign,
|
campaign: Campaign,
|
||||||
characters: List<CharacterSheetPreview>,
|
characters: List<CharacterSheetPreview>,
|
||||||
filter: String,
|
filter: String,
|
||||||
tags: Map<GMTagUio.TagId, Boolean>,
|
tags: Map<GMTagItemUio.TagId, Boolean>,
|
||||||
): List<GMCharacterUio> {
|
): List<GMCharacterItemUio> {
|
||||||
val normalizedFilter = filter.unAccent()
|
val normalizedFilter = filter.unAccent()
|
||||||
|
|
||||||
return characters.mapNotNull {
|
return characters.mapNotNull {
|
||||||
|
|
@ -35,8 +35,8 @@ class GameMasterFactory {
|
||||||
campaign: Campaign,
|
campaign: Campaign,
|
||||||
character: CharacterSheetPreview,
|
character: CharacterSheetPreview,
|
||||||
filter: String,
|
filter: String,
|
||||||
tags: Map<GMTagUio.TagId, Boolean>,
|
tags: Map<GMTagItemUio.TagId, Boolean>,
|
||||||
): GMCharacterUio? {
|
): GMCharacterItemUio? {
|
||||||
// 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 isPlayer = campaign.characters.firstOrNull {
|
val isPlayer = campaign.characters.firstOrNull {
|
||||||
it == character.characterSheetId
|
it == character.characterSheetId
|
||||||
|
|
@ -55,30 +55,30 @@ class GameMasterFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tag filter process : Player.
|
// Tag filter process : Player.
|
||||||
if (tags[GMTagUio.TagId.PLAYER] == true && isPlayer.not()) {
|
if (tags[GMTagItemUio.TagId.PLAYER] == true && isPlayer.not()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Tag filter process : Npc.
|
// Tag filter process : Npc.
|
||||||
if (tags[GMTagUio.TagId.NPC] == true && isNpc.not()) {
|
if (tags[GMTagItemUio.TagId.NPC] == true && isNpc.not()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Build the call tag list.
|
// Build the call tag list.
|
||||||
val previewTagsList = buildList {
|
val previewTagsList = buildList {
|
||||||
if (isPlayer) {
|
if (isPlayer) {
|
||||||
add(
|
add(
|
||||||
GMTagUio(
|
GMTagItemUio(
|
||||||
id = GMTagUio.TagId.PLAYER,
|
id = GMTagItemUio.TagId.PLAYER,
|
||||||
label = getString(Res.string.game_master__character_tag__character),
|
label = getString(Res.string.game_master__character_tag__character),
|
||||||
highlight = tags[GMTagUio.TagId.PLAYER] ?: false,
|
highlight = tags[GMTagItemUio.TagId.PLAYER] ?: false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (isNpc) {
|
if (isNpc) {
|
||||||
add(
|
add(
|
||||||
GMTagUio(
|
GMTagItemUio(
|
||||||
id = GMTagUio.TagId.NPC,
|
id = GMTagItemUio.TagId.NPC,
|
||||||
label = getString(Res.string.game_master__character_tag__npc),
|
label = getString(Res.string.game_master__character_tag__npc),
|
||||||
highlight = tags[GMTagUio.TagId.NPC] ?: false,
|
highlight = tags[GMTagItemUio.TagId.NPC] ?: false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +96,7 @@ class GameMasterFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return the cell UIO.
|
// return the cell UIO.
|
||||||
return GMCharacterUio(
|
return GMCharacterItemUio(
|
||||||
characterSheetId = character.characterSheetId,
|
characterSheetId = character.characterSheetId,
|
||||||
name = character.name,
|
name = character.name,
|
||||||
level = character.level,
|
level = character.level,
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
||||||
|
|
||||||
|
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.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
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 androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.LocalWindowController
|
||||||
|
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.screen.gamemaster.items.GMCharacter
|
||||||
|
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.GMTagItemUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
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.game_master__create_character_sheet
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMCharacterPage(
|
||||||
|
viewModel: GMCharacterViewModel = koinViewModel(),
|
||||||
|
) {
|
||||||
|
val windows = LocalWindowController.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val characters = viewModel.characters.collectAsState()
|
||||||
|
val tags = viewModel.tags.collectAsState()
|
||||||
|
|
||||||
|
GMCharacterContent(
|
||||||
|
filter = viewModel.filter,
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMCharacterContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
padding: Dp = 8.dp,
|
||||||
|
spacing: Dp = 8.dp,
|
||||||
|
filter: LwaTextFieldUio,
|
||||||
|
tags: State<List<GMTagItemUio>>,
|
||||||
|
characters: State<List<GMCharacterItemUio>>,
|
||||||
|
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||||
|
onCharacterAction: (String, GMCharacterItemUio.Action) -> Unit,
|
||||||
|
onCharacterSheetEdit: (String) -> Unit,
|
||||||
|
onCharacterSheetCreate: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
elevation = 1.dp,
|
||||||
|
) {
|
||||||
|
GMFilterHeader(
|
||||||
|
padding = padding,
|
||||||
|
spacing = spacing,
|
||||||
|
filter = filter,
|
||||||
|
tags = tags,
|
||||||
|
onTag = onTag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
contentPadding = remember {
|
||||||
|
PaddingValues(
|
||||||
|
start = padding,
|
||||||
|
top = padding,
|
||||||
|
end = padding,
|
||||||
|
bottom = padding + 48.dp + padding,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
|
) {
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
onTag = onTag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.BottomEnd)
|
||||||
|
.padding(all = padding),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
colors = LwaButtonColors(),
|
||||||
|
shape = CircleShape,
|
||||||
|
onClick = onCharacterSheetCreate,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
|
text = stringResource(Res.string.game_master__create_character_sheet),
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.character
|
||||||
|
|
||||||
|
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.network.NetworkRepository
|
||||||
|
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.GMCharacterItemUio.Action
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagItemUio.TagId
|
||||||
|
import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character__filter
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
|
class GMCharacterViewModel(
|
||||||
|
private val networkRepository: NetworkRepository,
|
||||||
|
private val campaignRepository: CampaignRepository,
|
||||||
|
characterSheetRepository: CharacterSheetRepository,
|
||||||
|
private val factory: GMCharacterFactory,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _filter = MutableStateFlow("")
|
||||||
|
val filter = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
labelFlow = MutableStateFlow(runBlocking { getString(Res.string.game_master__character__filter) }),
|
||||||
|
valueFlow = _filter,
|
||||||
|
isError = MutableStateFlow(false),
|
||||||
|
placeHolderFlow = MutableStateFlow(null),
|
||||||
|
onValueChange = { _filter.value = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
private val _tags = MutableStateFlow(mapOf(TagId.PLAYER to false, TagId.NPC to false))
|
||||||
|
val tags = _tags.map { it: Map<TagId, Boolean> ->
|
||||||
|
it.map { (tag, highlight) ->
|
||||||
|
when (tag) {
|
||||||
|
TagId.PLAYER -> GMTagItemUio(
|
||||||
|
id = TagId.PLAYER,
|
||||||
|
label = getString(Res.string.game_master__character_tag__character),
|
||||||
|
highlight = highlight,
|
||||||
|
)
|
||||||
|
|
||||||
|
TagId.NPC -> GMTagItemUio(
|
||||||
|
id = TagId.NPC,
|
||||||
|
label = getString(Res.string.game_master__character_tag__npc),
|
||||||
|
highlight = highlight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val characters = combine(
|
||||||
|
campaignRepository.campaignFlow,
|
||||||
|
characterSheetRepository.characterSheetPreviewFlow,
|
||||||
|
filter.valueFlow,
|
||||||
|
_tags,
|
||||||
|
factory::convertToGMCharacterPreviewUio,
|
||||||
|
).stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onCharacterAction(
|
||||||
|
characterSheetId: String,
|
||||||
|
action: Action,
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
when (action) {
|
||||||
|
Action.DisplayPortrait -> networkRepository.share(
|
||||||
|
GameMasterEvent.DisplayPortrait(
|
||||||
|
timestamp = System.currentTimeMillis(),
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.AddToGroup -> campaignRepository.addCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.AddToNpc -> campaignRepository.addNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.RemoveFromGroup -> campaignRepository.removeCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.RemoveFromNpc -> campaignRepository.removeNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onTag(
|
||||||
|
id: TagId,
|
||||||
|
) {
|
||||||
|
_tags.value = _tags.value.toMutableMap().also {
|
||||||
|
it[id] = it.getOrPut(id) { true }.not()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.minimumInteractiveComponentSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
object GmActionDefault {
|
||||||
|
val padding = PaddingValues(horizontal = 16.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMAction(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
paddingValues: PaddingValues = GmActionDefault.padding,
|
||||||
|
icon: DrawableResource,
|
||||||
|
label: String,
|
||||||
|
onAction: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||||
|
.clickable(onClick = onAction)
|
||||||
|
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.padding(paddingValues = paddingValues)
|
||||||
|
.then(other = modifier),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(icon),
|
||||||
|
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.lwa.typography.base.body1,
|
||||||
|
text = label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,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.GMCharacterUio.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
|
||||||
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
|
||||||
|
|
@ -46,11 +46,11 @@ import org.jetbrains.compose.resources.painterResource
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class GMCharacterUio(
|
data class GMCharacterItemUio(
|
||||||
val characterSheetId: String,
|
val characterSheetId: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val level: Int,
|
val level: Int,
|
||||||
val tags: List<GMTagUio>,
|
val tags: List<GMTagItemUio>,
|
||||||
val actions: List<Action>,
|
val actions: List<Action>,
|
||||||
) {
|
) {
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -92,16 +92,17 @@ object GMCharacterPreviewDefault {
|
||||||
fun GMCharacter(
|
fun GMCharacter(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
padding: PaddingValues = GMCharacterPreviewDefault.padding,
|
padding: PaddingValues = GMCharacterPreviewDefault.padding,
|
||||||
character: GMCharacterUio,
|
character: GMCharacterItemUio,
|
||||||
onEdit: () -> Unit,
|
onEdit: () -> Unit,
|
||||||
onAction: (Action) -> Unit,
|
onAction: (Action) -> Unit,
|
||||||
|
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
val startPadding = padding.calculateStartPadding(layoutDirection)
|
val startPadding = padding.calculateStartPadding(layoutDirection)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(shape = remember { RoundedCornerShape(8.dp) })
|
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||||
.clickable(onClick = onEdit)
|
.clickable(onClick = onEdit)
|
||||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||||
.then(other = modifier),
|
.then(other = modifier),
|
||||||
|
|
@ -109,6 +110,7 @@ fun GMCharacter(
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(start = startPadding),
|
modifier = Modifier.padding(start = startPadding),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.weight(weight = 1f),
|
modifier = Modifier.weight(weight = 1f),
|
||||||
|
|
@ -133,6 +135,7 @@ fun GMCharacter(
|
||||||
GMTag(
|
GMTag(
|
||||||
elevation = 4.dp,
|
elevation = 4.dp,
|
||||||
tag = tag,
|
tag = tag,
|
||||||
|
onTag = { onTag(tag.id) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +150,7 @@ fun GMCharacter(
|
||||||
@Composable
|
@Composable
|
||||||
private fun OverflowActionMenu(
|
private fun OverflowActionMenu(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
character: GMCharacterUio,
|
character: GMCharacterItemUio,
|
||||||
onAction: (Action) -> Unit,
|
onAction: (Action) -> Unit,
|
||||||
) {
|
) {
|
||||||
val overflowMenu = remember(character) {
|
val overflowMenu = remember(character) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
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.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
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.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMFilterHeader(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
lazyListState: LazyListState = rememberLazyListState(),
|
||||||
|
padding: Dp = 16.dp,
|
||||||
|
spacing: Dp = 8.dp,
|
||||||
|
filter: LwaTextFieldUio,
|
||||||
|
tags: State<List<GMTagItemUio>>,
|
||||||
|
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
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 {
|
||||||
|
lazyListState.scrollBy(-delta)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
state = lazyListState,
|
||||||
|
contentPadding = remember(padding, spacing) {
|
||||||
|
PaddingValues(horizontal = padding, vertical = spacing)
|
||||||
|
},
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = spacing),
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = tags.value,
|
||||||
|
) { tag ->
|
||||||
|
GMTag(
|
||||||
|
tag = tag,
|
||||||
|
onTag = { onTag(tag.id) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||||
|
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_account_child_invert_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_blur_on_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_iron_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_special_character_24dp
|
||||||
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
enum class GMTabUio(
|
||||||
|
val icon: DrawableResource,
|
||||||
|
) {
|
||||||
|
Actions(icon = Res.drawable.ic_special_character_24dp),
|
||||||
|
Characters(icon = Res.drawable.ic_account_child_invert_24dp),
|
||||||
|
Alterations(icon = Res.drawable.ic_blur_on_24dp),
|
||||||
|
Objects(icon = Res.drawable.ic_iron_24dp),
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMTab(
|
||||||
|
tab: GMTabUio,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(tab.icon),
|
||||||
|
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,14 +12,13 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class GMTagUio(
|
data class GMTagItemUio(
|
||||||
val id: TagId,
|
val id: TagId,
|
||||||
val label: String,
|
val label: String,
|
||||||
val highlight: Boolean,
|
val highlight: Boolean,
|
||||||
|
|
@ -41,7 +40,7 @@ fun GMTag(
|
||||||
padding: PaddingValues = GmTagDefault.padding,
|
padding: PaddingValues = GmTagDefault.padding,
|
||||||
shape: Shape = CircleShape,
|
shape: Shape = CircleShape,
|
||||||
elevation: Dp = 2.dp,
|
elevation: Dp = 2.dp,
|
||||||
tag: GMTagUio,
|
tag: GMTagItemUio,
|
||||||
onTag: (() -> Unit)? = null,
|
onTag: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val animatedColor = animateColorAsState(
|
val animatedColor = animateColorAsState(
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.theme.color.component
|
||||||
|
|
||||||
|
import androidx.compose.material.ButtonColors
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
fun LwaButtonColors(): ButtonColors = ButtonDefaults.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.surface,
|
||||||
|
contentColor = MaterialTheme.colors.primary,
|
||||||
|
disabledContentColor = MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ data class LwaShapes(
|
||||||
val portrait: Shape,
|
val portrait: Shape,
|
||||||
val panel: Shape,
|
val panel: Shape,
|
||||||
val settings: Shape,
|
val settings: Shape,
|
||||||
|
val gameMaster: Shape,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -20,10 +21,12 @@ fun lwaShapes(
|
||||||
portrait: Shape = RoundedCornerShape(8.dp),
|
portrait: Shape = RoundedCornerShape(8.dp),
|
||||||
panel: Shape = RoundedCornerShape(8.dp),
|
panel: Shape = RoundedCornerShape(8.dp),
|
||||||
settings: Shape = RoundedCornerShape(8.dp),
|
settings: Shape = RoundedCornerShape(8.dp),
|
||||||
|
gameMaster: Shape = RoundedCornerShape(8.dp),
|
||||||
): LwaShapes = remember {
|
): LwaShapes = remember {
|
||||||
LwaShapes(
|
LwaShapes(
|
||||||
portrait = portrait,
|
portrait = portrait,
|
||||||
panel = panel,
|
panel = panel,
|
||||||
settings = settings,
|
settings = settings,
|
||||||
|
gameMaster = gameMaster,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue