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__add_to_npc">Ajouter aux 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>
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
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.padding
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
|
|
@ -13,7 +10,6 @@ import androidx.compose.material.SnackbarDefaults
|
|||
import androidx.compose.material.SnackbarDuration
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -34,9 +30,8 @@ import androidx.compose.ui.window.Window
|
|||
import androidx.compose.ui.window.rememberWindowState
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
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.rememberBlurContentController
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler
|
||||
import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers
|
||||
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.rememberMaxWindowHeight
|
||||
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.gamemaster.GameMasterScreen
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||
|
|
@ -97,12 +91,8 @@ val LocalBlurController = compositionLocalOf<BlurContentController> {
|
|||
@Preview
|
||||
fun ApplicationScope.LwaApplication() {
|
||||
val maxWindowHeight = rememberMaxWindowHeight()
|
||||
val snackHostState = remember { SnackbarHostState() }
|
||||
val errorSnackHostState = remember { SnackbarHostState() }
|
||||
val windowController = remember { WindowController(maxWindowHeight) }
|
||||
val keyEventHandlers = remember { mutableStateListOf<KeyEventHandler>() }
|
||||
val rollHostState = remember { RollHostState() }
|
||||
val blurController = rememberBlurContentController()
|
||||
val windowsState = rememberWindowState(
|
||||
size = DpSize(
|
||||
width = 800.dp,
|
||||
|
|
@ -121,14 +111,11 @@ fun ApplicationScope.LwaApplication() {
|
|||
}
|
||||
)
|
||||
|
||||
LwaTheme {
|
||||
CompositionLocalProvider(
|
||||
LocalApplicationScope provides this,
|
||||
LocalSnackHost provides snackHostState,
|
||||
LocalErrorSnackHost provides errorSnackHostState,
|
||||
LocalWindowController provides windowController,
|
||||
LocalKeyEventHandlers provides keyEventHandlers,
|
||||
LocalRollHostState provides rollHostState,
|
||||
LocalBlurController provides blurController,
|
||||
LocalWindowState provides windowsState,
|
||||
) {
|
||||
Window(
|
||||
|
|
@ -141,10 +128,14 @@ fun ApplicationScope.LwaApplication() {
|
|||
) {
|
||||
MainWindowScreen()
|
||||
}
|
||||
|
||||
WindowsHandler(
|
||||
windowController = windowController,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun MainWindowScreen(
|
||||
dataSyncViewModel: DataSyncViewModel = koinViewModel(),
|
||||
|
|
@ -154,66 +145,14 @@ private fun MainWindowScreen(
|
|||
dataSyncViewModel.synchronise()
|
||||
}
|
||||
|
||||
val snackHostState = LocalSnackHost.current
|
||||
val errorSnackHostState = LocalErrorSnackHost.current
|
||||
val windowController = LocalWindowController.current
|
||||
val rollHostState = LocalRollHostState.current
|
||||
val blurController = LocalBlurController.current
|
||||
|
||||
LwaTheme {
|
||||
Surface(
|
||||
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(
|
||||
LwaScaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
controller = blurController,
|
||||
) {
|
||||
MainNavHost()
|
||||
}
|
||||
RollOverlay(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
hostState = rollHostState,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
NetworkSnackHandler(
|
||||
snack = snackHostState,
|
||||
snack = LocalSnackHost.current,
|
||||
)
|
||||
WindowsHandler(
|
||||
windowController = windowController,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +178,9 @@ private fun WindowsHandler(
|
|||
|
||||
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.CharacterSheetCharacteristicDialogFactory
|
||||
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.roll.RollViewModel
|
||||
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.detail.CharacterDetailFactory
|
||||
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.player.PlayerRibbonViewModel
|
||||
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.CharacterSheetEditViewModel
|
||||
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.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.LevelUpViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||
|
|
@ -120,7 +121,8 @@ val factoryDependencies
|
|||
factoryOf(::CharacterSheetDiminishedDialogFactory)
|
||||
factoryOf(::TextMessageFactory)
|
||||
factoryOf(::LevelUpFactory)
|
||||
factoryOf(::GameMasterFactory)
|
||||
factoryOf(::GMCharacterFactory)
|
||||
factoryOf(::GMActionViewModel)
|
||||
}
|
||||
|
||||
val viewModelDependencies
|
||||
|
|
@ -140,12 +142,12 @@ val viewModelDependencies
|
|||
viewModelOf(::CampaignChatViewModel)
|
||||
viewModelOf(::SettingsViewModel)
|
||||
viewModelOf(::LevelUpViewModel)
|
||||
viewModelOf(::GameMasterViewModel)
|
||||
viewModelOf(::PortraitOverlayViewModel)
|
||||
viewModelOf(::GMCharacterViewModel)
|
||||
viewModelOf(::GameMasterViewModel)
|
||||
}
|
||||
|
||||
val useCaseDependencies
|
||||
get() = module {
|
||||
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(
|
||||
title = title,
|
||||
size = DpSize(
|
||||
width = 400.dp + 64.dp,
|
||||
width = 124.dp * 4 + 64.dp,
|
||||
height = maxWindowHeight - 32.dp,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class PlayerRibbonViewModel(
|
|||
campaign: Campaign,
|
||||
settings: Settings,
|
||||
): 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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
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.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
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.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
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.CompositionLocalProvider
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.LocalWindowController
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField
|
||||
import com.pixelized.desktop.lwa.ui.composable.textfield.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.GMCharacterUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTag
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
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.composableGameMasterActionPage
|
||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationPage
|
||||
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.lwa
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__create__title
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__edit__title
|
||||
import lwacharactersheet.composeapp.generated.resources.game_master__action
|
||||
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.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
|
||||
@Composable
|
||||
fun GameMasterScreen(
|
||||
viewModel: GameMasterViewModel = koinViewModel(),
|
||||
) {
|
||||
val windows = LocalWindowController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val characters = viewModel.characters.collectAsState()
|
||||
val gameMaster = viewModel.gameMaster.collectAsState()
|
||||
val npcVisibility = viewModel.npcVisibility.collectAsState()
|
||||
val tags = viewModel.tags.collectAsState()
|
||||
val screen = rememberNavController()
|
||||
val gameMaster = viewModel.isGameMaster.collectAsState()
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalScreenController provides screen,
|
||||
) {
|
||||
GameMasterContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
filter = viewModel.filter,
|
||||
tags = tags,
|
||||
controller = screen,
|
||||
gameMaster = gameMaster,
|
||||
npcVisibility = npcVisibility,
|
||||
characters = characters,
|
||||
onTag = viewModel::onTag,
|
||||
onGameMaster = viewModel::onGameMaster,
|
||||
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),
|
||||
)
|
||||
}
|
||||
},
|
||||
onNpcVisibility = {
|
||||
scope.launch {
|
||||
viewModel.onNpcVisibility()
|
||||
onTab = {
|
||||
when (it) {
|
||||
GMTabUio.Actions -> screen.navigateToGameMasterActionPage()
|
||||
GMTabUio.Characters -> screen.navigateToGameMasterCharacterPage()
|
||||
GMTabUio.Alterations -> screen.navigateToGameMasterAlterationPage()
|
||||
GMTabUio.Objects -> screen.navigateToGameMasterObjectPage()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GameMasterContent(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: Dp = 16.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
filterChipsState: LazyListState = rememberLazyListState(),
|
||||
filter: LwaTextFieldUio,
|
||||
tags: State<List<GMTagUio>>,
|
||||
controller: NavHostController = rememberNavController(),
|
||||
gameMaster: State<Boolean>,
|
||||
npcVisibility: State<Boolean>,
|
||||
characters: State<List<GMCharacterUio>>,
|
||||
onGameMaster: (Boolean) -> Unit,
|
||||
onTag: (GMTagUio.TagId) -> Unit,
|
||||
onCharacterAction: (String, GMCharacterUio.Action) -> Unit,
|
||||
onCharacterSheetEdit: (String) -> Unit,
|
||||
onCharacterSheetCreate: () -> Unit,
|
||||
onNpcVisibility: () -> Unit,
|
||||
onTab: (GMTabUio) -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
GameMasterLayout(
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
|
@ -171,160 +120,46 @@ private fun GameMasterContent(
|
|||
}
|
||||
)
|
||||
},
|
||||
content = {
|
||||
Column {
|
||||
content = { paddingValues ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues),
|
||||
) {
|
||||
Surface(
|
||||
elevation = 1.dp,
|
||||
) {
|
||||
Column {
|
||||
LwaTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = filter,
|
||||
trailingIcon = {
|
||||
val value = filter.valueFlow.collectAsState()
|
||||
AnimatedVisibility(
|
||||
visible = value.value.isNotBlank(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { filter.onValueChange.invoke("") },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_cancel_24dp),
|
||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
LazyRow(
|
||||
modifier = Modifier.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
scope.launch {
|
||||
filterChipsState.scrollBy(-delta)
|
||||
}
|
||||
},
|
||||
),
|
||||
state = filterChipsState,
|
||||
contentPadding = remember(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(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
.fillMaxHeight()
|
||||
.width(width = 64.dp)
|
||||
.padding(vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
contentPadding = remember {
|
||||
PaddingValues(
|
||||
start = padding,
|
||||
top = padding,
|
||||
end = padding,
|
||||
bottom = padding + 48.dp + padding,
|
||||
GMTabUio.entries.forEach {
|
||||
GMTab(
|
||||
tab = it,
|
||||
onClick = { onTab(it) },
|
||||
)
|
||||
},
|
||||
verticalArrangement = Arrangement.spacedBy(space = spacing),
|
||||
) {
|
||||
items(
|
||||
items = characters.value,
|
||||
key = { it.characterSheetId },
|
||||
) { character ->
|
||||
GMCharacter(
|
||||
}
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.animateItem(),
|
||||
character = character,
|
||||
onEdit = {
|
||||
onCharacterSheetEdit(character.characterSheetId)
|
||||
},
|
||||
onAction = { action ->
|
||||
onCharacterAction(character.characterSheetId, action)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fab = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = padding),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
.fillMaxHeight()
|
||||
.weight(weight = 1f),
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.background(
|
||||
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
onClick = onNpcVisibility,
|
||||
NavHost(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
navController = controller,
|
||||
startDestination = GMActionDestination.navigationRoute(),
|
||||
) {
|
||||
Icon(
|
||||
painter = when (npcVisibility.value) {
|
||||
true -> painterResource(Res.drawable.ic_visibility_off_24dp)
|
||||
else -> painterResource(Res.drawable.ic_visibility_24dp)
|
||||
},
|
||||
tint = MaterialTheme.lwa.colorScheme.base.onPrimary,
|
||||
contentDescription = null,
|
||||
)
|
||||
composableGameMasterActionPage()
|
||||
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.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.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.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 GameMasterViewModel(
|
||||
campaignRepository: CampaignRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val networkRepository: NetworkRepository,
|
||||
private val factory: GameMasterFactory,
|
||||
private val useCase: GameMasterActionUseCase,
|
||||
) : ViewModel() {
|
||||
): 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 -> 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()
|
||||
val isGameMaster = settingsRepository.settingsFlow()
|
||||
.map { it.isGameMaster ?: false }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
|
|
@ -86,14 +19,6 @@ class GameMasterViewModel(
|
|||
initialValue = false,
|
||||
)
|
||||
|
||||
val npcVisibility = campaignRepository.campaignFlow
|
||||
.map { it.options.showNpcs }
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
fun onGameMaster(value: Boolean) {
|
||||
val settings = settingsRepository.settings()
|
||||
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.GMCharacterUio.Action
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMTagUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterItemUio
|
||||
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.utils.extention.unAccent
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
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 org.jetbrains.compose.resources.getString
|
||||
|
||||
class GameMasterFactory {
|
||||
class GMCharacterFactory {
|
||||
|
||||
suspend fun convertToGMCharacterPreviewUio(
|
||||
campaign: Campaign,
|
||||
characters: List<CharacterSheetPreview>,
|
||||
filter: String,
|
||||
tags: Map<GMTagUio.TagId, Boolean>,
|
||||
): List<GMCharacterUio> {
|
||||
tags: Map<GMTagItemUio.TagId, Boolean>,
|
||||
): List<GMCharacterItemUio> {
|
||||
val normalizedFilter = filter.unAccent()
|
||||
|
||||
return characters.mapNotNull {
|
||||
|
|
@ -35,8 +35,8 @@ class GameMasterFactory {
|
|||
campaign: Campaign,
|
||||
character: CharacterSheetPreview,
|
||||
filter: String,
|
||||
tags: Map<GMTagUio.TagId, Boolean>,
|
||||
): GMCharacterUio? {
|
||||
tags: Map<GMTagItemUio.TagId, Boolean>,
|
||||
): GMCharacterItemUio? {
|
||||
// get the characterInstanceId from the player list corresponding to this CharacterSheet if any
|
||||
val isPlayer = campaign.characters.firstOrNull {
|
||||
it == character.characterSheetId
|
||||
|
|
@ -55,30 +55,30 @@ class GameMasterFactory {
|
|||
}
|
||||
}
|
||||
// Tag filter process : Player.
|
||||
if (tags[GMTagUio.TagId.PLAYER] == true && isPlayer.not()) {
|
||||
if (tags[GMTagItemUio.TagId.PLAYER] == true && isPlayer.not()) {
|
||||
return null
|
||||
}
|
||||
// Tag filter process : Npc.
|
||||
if (tags[GMTagUio.TagId.NPC] == true && isNpc.not()) {
|
||||
if (tags[GMTagItemUio.TagId.NPC] == true && isNpc.not()) {
|
||||
return null
|
||||
}
|
||||
// Build the call tag list.
|
||||
val previewTagsList = buildList {
|
||||
if (isPlayer) {
|
||||
add(
|
||||
GMTagUio(
|
||||
id = GMTagUio.TagId.PLAYER,
|
||||
GMTagItemUio(
|
||||
id = GMTagItemUio.TagId.PLAYER,
|
||||
label = getString(Res.string.game_master__character_tag__character),
|
||||
highlight = tags[GMTagUio.TagId.PLAYER] ?: false,
|
||||
highlight = tags[GMTagItemUio.TagId.PLAYER] ?: false,
|
||||
)
|
||||
)
|
||||
}
|
||||
if (isNpc) {
|
||||
add(
|
||||
GMTagUio(
|
||||
id = GMTagUio.TagId.NPC,
|
||||
GMTagItemUio(
|
||||
id = GMTagItemUio.TagId.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 GMCharacterUio(
|
||||
return GMCharacterItemUio(
|
||||
characterSheetId = character.characterSheetId,
|
||||
name = character.name,
|
||||
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.unit.DpOffset
|
||||
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 lwacharactersheet.composeapp.generated.resources.Res
|
||||
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
|
||||
|
||||
@Stable
|
||||
data class GMCharacterUio(
|
||||
data class GMCharacterItemUio(
|
||||
val characterSheetId: String,
|
||||
val name: String,
|
||||
val level: Int,
|
||||
val tags: List<GMTagUio>,
|
||||
val tags: List<GMTagItemUio>,
|
||||
val actions: List<Action>,
|
||||
) {
|
||||
@Stable
|
||||
|
|
@ -92,16 +92,17 @@ object GMCharacterPreviewDefault {
|
|||
fun GMCharacter(
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = GMCharacterPreviewDefault.padding,
|
||||
character: GMCharacterUio,
|
||||
character: GMCharacterItemUio,
|
||||
onEdit: () -> Unit,
|
||||
onAction: (Action) -> Unit,
|
||||
onTag: (GMTagItemUio.TagId) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val startPadding = padding.calculateStartPadding(layoutDirection)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(shape = remember { RoundedCornerShape(8.dp) })
|
||||
.clip(shape = MaterialTheme.lwa.shapes.gameMaster)
|
||||
.clickable(onClick = onEdit)
|
||||
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||
.then(other = modifier),
|
||||
|
|
@ -109,6 +110,7 @@ fun GMCharacter(
|
|||
Row(
|
||||
modifier = Modifier.padding(start = startPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
|
|
@ -133,6 +135,7 @@ fun GMCharacter(
|
|||
GMTag(
|
||||
elevation = 4.dp,
|
||||
tag = tag,
|
||||
onTag = { onTag(tag.id) }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +150,7 @@ fun GMCharacter(
|
|||
@Composable
|
||||
private fun OverflowActionMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
character: GMCharacterUio,
|
||||
character: GMCharacterItemUio,
|
||||
onAction: (Action) -> Unit,
|
||||
) {
|
||||
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.ui.Modifier
|
||||
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 com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
|
||||
|
||||
@Stable
|
||||
data class GMTagUio(
|
||||
data class GMTagItemUio(
|
||||
val id: TagId,
|
||||
val label: String,
|
||||
val highlight: Boolean,
|
||||
|
|
@ -41,7 +40,7 @@ fun GMTag(
|
|||
padding: PaddingValues = GmTagDefault.padding,
|
||||
shape: Shape = CircleShape,
|
||||
elevation: Dp = 2.dp,
|
||||
tag: GMTagUio,
|
||||
tag: GMTagItemUio,
|
||||
onTag: (() -> Unit)? = null,
|
||||
) {
|
||||
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 panel: Shape,
|
||||
val settings: Shape,
|
||||
val gameMaster: Shape,
|
||||
)
|
||||
|
||||
@Stable
|
||||
|
|
@ -20,10 +21,12 @@ fun lwaShapes(
|
|||
portrait: Shape = RoundedCornerShape(8.dp),
|
||||
panel: Shape = RoundedCornerShape(8.dp),
|
||||
settings: Shape = RoundedCornerShape(8.dp),
|
||||
gameMaster: Shape = RoundedCornerShape(8.dp),
|
||||
): LwaShapes = remember {
|
||||
LwaShapes(
|
||||
portrait = portrait,
|
||||
panel = panel,
|
||||
settings = settings,
|
||||
gameMaster = gameMaster,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue