Add a basic version of the GM screen.
This commit is contained in:
parent
35396b6069
commit
6b86a6c075
42 changed files with 969 additions and 784 deletions
|
|
@ -177,6 +177,15 @@
|
||||||
<string name="level_up__character_level_description">Passage du niveau %1$d ▸ %2$d</string>
|
<string name="level_up__character_level_description">Passage du niveau %1$d ▸ %2$d</string>
|
||||||
<string name="level_up__skill_level">niv : %1$d -</string>
|
<string name="level_up__skill_level">niv : %1$d -</string>
|
||||||
|
|
||||||
|
<string name="game_master__character_level__label">niv: %1$d</string>
|
||||||
|
<string name="game_master__character_tag__character_search">joueur</string>
|
||||||
|
<string name="game_master__character_tag__character_label">joueur: %1$d</string>
|
||||||
|
<string name="game_master__character_tag__npc_search">npc</string>
|
||||||
|
<string name="game_master__character_tag__npc_label">npc: %1$d</string>
|
||||||
|
<string name="game_master__character_action__display_portrait">Afficher le portrait</string>
|
||||||
|
<string name="game_master__character_action__add_to_group">Ajouter au groupe</string>
|
||||||
|
<string name="game_master__character_action__remove_from_group">Retirer du groupe (id: %1$d)</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 (id: %1$d)</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -47,19 +47,15 @@ import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.WindowsNavHost
|
import com.pixelized.desktop.lwa.ui.navigation.window.WindowsNavHost
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetEditWindow
|
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetEditWindow
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow
|
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.NetworkWindows
|
import com.pixelized.desktop.lwa.ui.navigation.window.destination.GameMasterWindow
|
||||||
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.overlay.roll.RollOverlay
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
||||||
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.network.NetworkPage
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
|
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
|
||||||
import com.pixelized.desktop.lwa.utils.InstallCoil
|
import com.pixelized.desktop.lwa.utils.InstallCoil
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -153,10 +149,6 @@ fun ApplicationScope.LwaApplication() {
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainWindowScreen(
|
private fun MainWindowScreen(
|
||||||
dataSyncViewModel: DataSyncViewModel = koinViewModel(),
|
dataSyncViewModel: DataSyncViewModel = koinViewModel(),
|
||||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
|
||||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
|
||||||
campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
|
|
||||||
rollViewModel: RollHistoryViewModel = koinViewModel(),
|
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
dataSyncViewModel.autoConnect()
|
dataSyncViewModel.autoConnect()
|
||||||
|
|
@ -221,7 +213,6 @@ private fun MainWindowScreen(
|
||||||
)
|
)
|
||||||
WindowsHandler(
|
WindowsHandler(
|
||||||
windowController = windowController,
|
windowController = windowController,
|
||||||
rollViewModel = rollViewModel,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +221,6 @@ private fun MainWindowScreen(
|
||||||
@Composable
|
@Composable
|
||||||
private fun WindowsHandler(
|
private fun WindowsHandler(
|
||||||
windowController: WindowController,
|
windowController: WindowController,
|
||||||
rollViewModel: RollHistoryViewModel = koinViewModel(),
|
|
||||||
) {
|
) {
|
||||||
WindowsNavHost(
|
WindowsNavHost(
|
||||||
controller = windowController,
|
controller = windowController,
|
||||||
|
|
@ -248,11 +238,9 @@ private fun WindowsHandler(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
is RollHistoryWindow -> RollHistoryPage(
|
is RollHistoryWindow -> RollHistoryPage()
|
||||||
viewModel = rollViewModel,
|
|
||||||
)
|
|
||||||
|
|
||||||
is NetworkWindows -> NetworkPage()
|
is GameMasterWindow -> GameMasterScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +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.overlay.roll.RollViewModel
|
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
|
||||||
|
|
@ -31,9 +31,11 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.CharacterSheetEdi
|
||||||
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.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.main.MainPageViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkFactory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkFactory
|
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
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.rollhistory.RollHistoryViewModel
|
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel
|
import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel
|
||||||
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
|
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
|
||||||
|
|
@ -112,13 +114,13 @@ val factoryDependencies
|
||||||
factoryOf(::CharacterSheetCharacteristicDialogFactory)
|
factoryOf(::CharacterSheetCharacteristicDialogFactory)
|
||||||
factoryOf(::TextMessageFactory)
|
factoryOf(::TextMessageFactory)
|
||||||
factoryOf(::LevelUpFactory)
|
factoryOf(::LevelUpFactory)
|
||||||
|
factoryOf(::GameMasterFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewModelDependencies
|
val viewModelDependencies
|
||||||
get() = module {
|
get() = module {
|
||||||
viewModelOf(::DataSyncViewModel)
|
viewModelOf(::DataSyncViewModel)
|
||||||
viewModelOf(::CampaignViewModel)
|
viewModelOf(::CampaignToolbarViewModel)
|
||||||
viewModelOf(::MainPageViewModel)
|
|
||||||
viewModelOf(::CharacterSheetViewModel)
|
viewModelOf(::CharacterSheetViewModel)
|
||||||
viewModelOf(::CharacterSheetEditViewModel)
|
viewModelOf(::CharacterSheetEditViewModel)
|
||||||
viewModelOf(::RollViewModel)
|
viewModelOf(::RollViewModel)
|
||||||
|
|
@ -131,9 +133,11 @@ val viewModelDependencies
|
||||||
viewModelOf(::CampaignChatViewModel)
|
viewModelOf(::CampaignChatViewModel)
|
||||||
viewModelOf(::SettingsViewModel)
|
viewModelOf(::SettingsViewModel)
|
||||||
viewModelOf(::LevelUpViewModel)
|
viewModelOf(::LevelUpViewModel)
|
||||||
|
viewModelOf(::GameMasterViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val useCaseDependencies
|
val useCaseDependencies
|
||||||
get() = module {
|
get() = module {
|
||||||
factoryOf(::SettingsUseCase)
|
factoryOf(::SettingsUseCase)
|
||||||
|
factoryOf(::GameMasterActionUseCase)
|
||||||
}
|
}
|
||||||
|
|
@ -17,11 +17,11 @@ interface LwaClient {
|
||||||
|
|
||||||
suspend fun campaign(): CampaignJson
|
suspend fun campaign(): CampaignJson
|
||||||
|
|
||||||
suspend fun campaignAddCharacter(characterSheetId: String, instanceId: Int)
|
suspend fun campaignAddCharacter(characterSheetId: String)
|
||||||
|
|
||||||
suspend fun campaignDeleteCharacter(characterSheetId: String, instanceId: Int)
|
suspend fun campaignDeleteCharacter(characterSheetId: String, instanceId: Int)
|
||||||
|
|
||||||
suspend fun campaignAddNpc(characterSheetId: String, instanceId: Int)
|
suspend fun campaignAddNpc(characterSheetId: String)
|
||||||
|
|
||||||
suspend fun campaignDeleteNpc(characterSheetId: String, instanceId: Int)
|
suspend fun campaignDeleteNpc(characterSheetId: String, instanceId: Int)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,8 @@ class LwaClientImpl(
|
||||||
|
|
||||||
override suspend fun campaignAddCharacter(
|
override suspend fun campaignAddCharacter(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
instanceId: Int,
|
|
||||||
) = client
|
) = client
|
||||||
.put("$root/campaign/character/update?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
.put("$root/campaign/character/update?characterSheetId=$characterSheetId")
|
||||||
.body<Unit>()
|
.body<Unit>()
|
||||||
|
|
||||||
override suspend fun campaignDeleteCharacter(
|
override suspend fun campaignDeleteCharacter(
|
||||||
|
|
@ -59,9 +58,8 @@ class LwaClientImpl(
|
||||||
|
|
||||||
override suspend fun campaignAddNpc(
|
override suspend fun campaignAddNpc(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
instanceId: Int,
|
|
||||||
) = client
|
) = client
|
||||||
.put("$root/campaign/npc/update?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
.put("$root/campaign/npc/update?characterSheetId=$characterSheetId")
|
||||||
.body<Unit>()
|
.body<Unit>()
|
||||||
|
|
||||||
override suspend fun campaignDeleteNpc(
|
override suspend fun campaignDeleteNpc(
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,32 @@ class CampaignRepository(
|
||||||
): Campaign.CharacterInstance {
|
): Campaign.CharacterInstance {
|
||||||
return campaignFlow.value.character(characterInstanceId)
|
return campaignFlow.value.character(characterInstanceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun addCharacter(
|
||||||
|
characterSheetId: String,
|
||||||
|
) = store.addCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun removeCharacter(
|
||||||
|
characterSheetId: String,
|
||||||
|
instanceId: Int,
|
||||||
|
) = store.removeCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = instanceId,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun addNpc(
|
||||||
|
characterSheetId: String,
|
||||||
|
) = store.addNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun removeNpc(
|
||||||
|
characterSheetId: String,
|
||||||
|
instanceId: Int,
|
||||||
|
) = store.removeNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = instanceId,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -3,8 +3,8 @@ package com.pixelized.desktop.lwa.repository.campaign
|
||||||
import com.pixelized.desktop.lwa.network.LwaClient
|
import com.pixelized.desktop.lwa.network.LwaClient
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
|
||||||
import com.pixelized.shared.lwa.model.campaign.character
|
import com.pixelized.shared.lwa.model.campaign.character
|
||||||
|
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||||
import com.pixelized.shared.lwa.model.campaign.npc
|
import com.pixelized.shared.lwa.model.campaign.npc
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
|
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
|
||||||
|
|
@ -47,6 +47,42 @@ class CampaignStore(
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun addCharacter(
|
||||||
|
characterSheetId: String,
|
||||||
|
) {
|
||||||
|
client.campaignAddCharacter(
|
||||||
|
characterSheetId = characterSheetId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeCharacter(
|
||||||
|
characterSheetId: String,
|
||||||
|
instanceId: Int,
|
||||||
|
) {
|
||||||
|
client.campaignDeleteCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = instanceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addNpc(
|
||||||
|
characterSheetId: String,
|
||||||
|
) {
|
||||||
|
client.campaignAddNpc(
|
||||||
|
characterSheetId = characterSheetId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeNpc(
|
||||||
|
characterSheetId: String,
|
||||||
|
instanceId: Int,
|
||||||
|
) {
|
||||||
|
client.campaignDeleteNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = instanceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// region : WebSocket message Handling.
|
// region : WebSocket message Handling.
|
||||||
|
|
||||||
private suspend fun handleMessage(message: Message) {
|
private suspend fun handleMessage(message: Message) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.pixelized.desktop.lwa.repository.campaign.model
|
package com.pixelized.desktop.lwa.repository.campaign.model
|
||||||
|
|
||||||
data class CharacterSheetPreview(
|
data class CharacterSheetPreview(
|
||||||
val id: String,
|
val characterSheetId: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val level: Int,
|
val level: Int,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class CharacterSheetRepository(
|
||||||
val characterDetailFlow get() = store.detailFlow
|
val characterDetailFlow get() = store.detailFlow
|
||||||
|
|
||||||
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
||||||
return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId }
|
return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun characterDetail(
|
suspend fun characterDetail(
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class CharacterSheetStore(
|
||||||
val request = client.characters()
|
val request = client.characters()
|
||||||
val data = request.map {
|
val data = request.map {
|
||||||
CharacterSheetPreview(
|
CharacterSheetPreview(
|
||||||
id = it.id,
|
characterSheetId = it.id,
|
||||||
name = it.name,
|
name = it.name,
|
||||||
level = it.level,
|
level = it.level,
|
||||||
)
|
)
|
||||||
|
|
@ -105,7 +105,7 @@ class CharacterSheetStore(
|
||||||
|
|
||||||
is RestSynchronisation.CharacterDelete -> {
|
is RestSynchronisation.CharacterDelete -> {
|
||||||
_previewFlow.value = previewFlow.value.toMutableList()
|
_previewFlow.value = previewFlow.value.toMutableList()
|
||||||
.also { sheets -> sheets.removeIf { it.id == payload.characterId } }
|
.also { sheets -> sheets.removeIf { it.characterSheetId == payload.characterId } }
|
||||||
_detailFlow.delete(payload.characterId)
|
_detailFlow.delete(payload.characterId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class SettingsFactory(
|
||||||
autoHideDelay = settings.autoHideDelay,
|
autoHideDelay = settings.autoHideDelay,
|
||||||
autoShowChat = settings.autoShowChat,
|
autoShowChat = settings.autoShowChat,
|
||||||
autoScrollChat = settings.autoScrollChat,
|
autoScrollChat = settings.autoScrollChat,
|
||||||
|
isGM = settings.isGM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +46,7 @@ class SettingsFactory(
|
||||||
autoHideDelay = json.autoHideDelay ?: default.autoHideDelay,
|
autoHideDelay = json.autoHideDelay ?: default.autoHideDelay,
|
||||||
autoShowChat = json.autoShowChat ?: default.autoShowChat,
|
autoShowChat = json.autoShowChat ?: default.autoShowChat,
|
||||||
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
|
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
|
||||||
|
isGM = json.isGM ?: default.isGM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ data class Settings(
|
||||||
val autoHideDelay: Int,
|
val autoHideDelay: Int,
|
||||||
val autoShowChat: Boolean,
|
val autoShowChat: Boolean,
|
||||||
val autoScrollChat: Boolean,
|
val autoScrollChat: Boolean,
|
||||||
|
val isGM: Boolean,
|
||||||
) {
|
) {
|
||||||
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
|
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
|
||||||
}
|
}
|
||||||
|
|
@ -12,4 +12,5 @@ data class SettingsJsonV1(
|
||||||
val autoHideDelay: Int?,
|
val autoHideDelay: Int?,
|
||||||
val autoShowChat: Boolean?,
|
val autoShowChat: Boolean?,
|
||||||
val autoScrollChat: Boolean?,
|
val autoScrollChat: Boolean?,
|
||||||
|
val isGM: Boolean?,
|
||||||
) : SettingsJson
|
) : SettingsJson
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.composable.textfield
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextField
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusDirection
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.utils.rememberKeyboardActions
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class LwaTextFieldUio(
|
||||||
|
val enable: Boolean,
|
||||||
|
val labelFlow: StateFlow<String?>,
|
||||||
|
val valueFlow: StateFlow<String>,
|
||||||
|
val placeHolderFlow: StateFlow<String?>,
|
||||||
|
val onValueChange: (String) -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LwaTextField(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
singleLine: Boolean = true,
|
||||||
|
field: LwaTextFieldUio,
|
||||||
|
) {
|
||||||
|
val focus = LocalFocusManager.current
|
||||||
|
val colorScheme = MaterialTheme.colors
|
||||||
|
|
||||||
|
val localModifier = if (singleLine) {
|
||||||
|
Modifier.height(height = 56.dp)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
val label = field.labelFlow.collectAsState()
|
||||||
|
val value = field.valueFlow.collectAsState()
|
||||||
|
val placeHolder = field.placeHolderFlow.collectAsState()
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
modifier = localModifier.then(other = modifier),
|
||||||
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
backgroundColor = remember(field.enable) {
|
||||||
|
when (field.enable) {
|
||||||
|
true -> colorScheme.onSurface.copy(alpha = 0.03f)
|
||||||
|
else -> colorScheme.surface
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
keyboardActions = rememberKeyboardActions {
|
||||||
|
focus.moveFocus(FocusDirection.Next)
|
||||||
|
},
|
||||||
|
enabled = field.enable,
|
||||||
|
singleLine = singleLine,
|
||||||
|
placeholder = placeHolder.value?.let {
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
text = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = label.value?.let {
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
text = it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingIcon = leadingIcon,
|
||||||
|
trailingIcon = trailingIcon,
|
||||||
|
onValueChange = { field.onValueChange(it) },
|
||||||
|
value = value.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -9,12 +9,7 @@ import androidx.navigation.compose.rememberNavController
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.MainDestination
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.MainDestination
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableLevelUp
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableLevelUp
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableMainPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableMainPage
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableNetworkPage
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableOldMainPage
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableSettingsPage
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableSettingsPage
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
|
||||||
|
|
||||||
val LocalScreenController = compositionLocalOf<NavHostController> {
|
val LocalScreenController = compositionLocalOf<NavHostController> {
|
||||||
error("MainNavHost controller is not yet ready")
|
error("MainNavHost controller is not yet ready")
|
||||||
|
|
@ -35,9 +30,6 @@ fun MainNavHost(
|
||||||
composableMainPage()
|
composableMainPage()
|
||||||
composableSettingsPage()
|
composableSettingsPage()
|
||||||
composableLevelUp()
|
composableLevelUp()
|
||||||
|
|
||||||
composableNetworkPage()
|
|
||||||
composableOldMainPage()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
|
||||||
|
|
||||||
import androidx.navigation.NavGraphBuilder
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkScreen
|
|
||||||
|
|
||||||
@Deprecated(message = "Part of the old UI")
|
|
||||||
object NetworkDestination {
|
|
||||||
private const val ROUTE = "network"
|
|
||||||
|
|
||||||
fun baseRoute() = ROUTE
|
|
||||||
fun navigationRoute() = ROUTE
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(message = "Part of the old UI")
|
|
||||||
fun NavGraphBuilder.composableNetworkPage() {
|
|
||||||
composable(
|
|
||||||
route = NetworkDestination.baseRoute(),
|
|
||||||
) {
|
|
||||||
NetworkScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(message = "Part of the old UI")
|
|
||||||
fun NavHostController.navigateToNetwork() {
|
|
||||||
val route = NetworkDestination.navigationRoute()
|
|
||||||
navigate(route = route)
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.navigation.screen.destination
|
|
||||||
|
|
||||||
import androidx.navigation.NavGraphBuilder
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.main.OldMainPage
|
|
||||||
|
|
||||||
@Deprecated(message = "Part of the old UI")
|
|
||||||
object OldMainDestination {
|
|
||||||
private const val ROUTE = "old_main"
|
|
||||||
|
|
||||||
fun baseRoute() = ROUTE
|
|
||||||
fun navigationRoute() = ROUTE
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(message = "Part of the old UI")
|
|
||||||
fun NavGraphBuilder.composableOldMainPage() {
|
|
||||||
composable(
|
|
||||||
route = OldMainDestination.baseRoute(),
|
|
||||||
) {
|
|
||||||
OldMainPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(message = "Part of the old UI")
|
|
||||||
fun NavHostController.navigateToOldMainPage() {
|
|
||||||
val route = OldMainDestination.navigationRoute()
|
|
||||||
navigate(route = route)
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
|
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class NetworkWindows(
|
class GameMasterWindow(
|
||||||
title: String,
|
title: String,
|
||||||
size: DpSize,
|
size: DpSize,
|
||||||
) : Window(
|
) : Window(
|
||||||
|
|
@ -14,14 +14,15 @@ class NetworkWindows(
|
||||||
size = size,
|
size = size,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun WindowController.navigateToNetwork(
|
fun WindowController.navigateToGameMasterWindow(
|
||||||
title: String = "",
|
title: String = "Game master",
|
||||||
) {
|
) {
|
||||||
showWindow(
|
showWindow(
|
||||||
window = NetworkWindows(
|
window = GameMasterWindow(
|
||||||
title = title, size = DpSize(
|
title = title,
|
||||||
width = 464.dp,
|
size = DpSize(
|
||||||
height = 300.dp,
|
width = 400.dp + 64.dp,
|
||||||
|
height = maxWindowHeight - 32.dp,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +19,8 @@ fun WindowController.navigateToRollHistory(
|
||||||
) {
|
) {
|
||||||
showWindow(
|
showWindow(
|
||||||
window = RollHistoryWindow(
|
window = RollHistoryWindow(
|
||||||
title = title, size = DpSize(
|
title = title,
|
||||||
|
size = DpSize(
|
||||||
width = 400.dp + 64.dp,
|
width = 400.dp + 64.dp,
|
||||||
height = maxWindowHeight,
|
height = maxWindowHeight,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,8 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDimin
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
|
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
|
||||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay
|
|
||||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
|
@ -58,7 +56,7 @@ fun CampaignScreen(
|
||||||
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
||||||
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
|
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
|
||||||
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
|
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
|
||||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
campaignViewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||||
campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
|
campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -88,8 +86,7 @@ fun CampaignScreen(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
top = {
|
top = {
|
||||||
CampaignToolbar(
|
CampaignToolbar(
|
||||||
campaignViewModel = campaignViewModel,
|
viewModel = campaignViewModel,
|
||||||
networkViewModel = networkViewModel,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottom = {
|
bottom = {
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
|
||||||
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 kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class CampaignViewModel(
|
|
||||||
private val characterRepository: CharacterSheetRepository,
|
|
||||||
private val alterationRepository: AlterationRepository,
|
|
||||||
private val campaignRepository: CampaignRepository,
|
|
||||||
private val network: NetworkRepository,
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
val title: Flow<String> = campaignRepository.campaignFlow
|
|
||||||
.map { it.scene.name }
|
|
||||||
|
|
||||||
val networkStatus = network.status
|
|
||||||
|
|
||||||
fun init() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
launch {
|
|
||||||
network.status.collect { status ->
|
|
||||||
if (status == NetworkRepository.Status.CONNECTED) {
|
|
||||||
campaignRepository.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
combine(
|
|
||||||
network.status,
|
|
||||||
campaignRepository.campaignFlow,
|
|
||||||
) { status, campaign ->
|
|
||||||
status to campaign
|
|
||||||
}.collectLatest { (status, campaign) ->
|
|
||||||
if (status == NetworkRepository.Status.CONNECTED) {
|
|
||||||
campaign.characters.keys.forEach { id ->
|
|
||||||
characterRepository.characterDetail(
|
|
||||||
characterSheetId = id.characterSheetId,
|
|
||||||
forceUpdate = true,
|
|
||||||
)
|
|
||||||
alterationRepository.updateActiveAlterations(
|
|
||||||
characterInstanceId = id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.network
|
package com.pixelized.desktop.lwa.ui.screen.campaign.network
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
|
@ -27,15 +27,11 @@ import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Scaffold
|
|
||||||
import androidx.compose.material.SnackbarDuration
|
import androidx.compose.material.SnackbarDuration
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextButton
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.material.TextField
|
import androidx.compose.material.TextField
|
||||||
import androidx.compose.material.TopAppBar
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
|
@ -44,12 +40,10 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.LocalSnackHost
|
import com.pixelized.desktop.lwa.LocalSnackHost
|
||||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
||||||
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnack
|
import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnack
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
|
@ -59,7 +53,6 @@ import lwacharactersheet.composeapp.generated.resources.network__player_name__la
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__port__label
|
import lwacharactersheet.composeapp.generated.resources.network__port__label
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
|
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
|
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__title
|
|
||||||
import org.jetbrains.compose.resources.painterResource
|
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
|
||||||
|
|
@ -100,93 +93,7 @@ data class NetworkPageUio(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NetworkScreen(
|
fun NetworkDialog(
|
||||||
viewModel: NetworkViewModel = koinViewModel(),
|
|
||||||
) {
|
|
||||||
val screen = LocalScreenController.current
|
|
||||||
val snack = LocalSnackHost.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
BlurContent(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
controller = viewModel.blurController,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 1,
|
|
||||||
text = stringResource(Res.string.network__title),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = { screen.popBackStack() },
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
content = { paddingValues ->
|
|
||||||
NetworkContent(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
paddingValues = paddingValues,
|
|
||||||
network = viewModel.network.collectAsState(),
|
|
||||||
onPlayerChange = viewModel::onPlayerNameChange,
|
|
||||||
onHostChange = viewModel::onHostChange,
|
|
||||||
onResetPortChange = viewModel::onResetPortChange,
|
|
||||||
onPortChange = viewModel::onPortChange,
|
|
||||||
onResetHostChange = viewModel::onResetHostChange,
|
|
||||||
onConnect = { scope.launch { viewModel.connect() } },
|
|
||||||
onDisconnect = viewModel::disconnect,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedContent(
|
|
||||||
modifier = Modifier.size(size = 64.dp),
|
|
||||||
targetState = viewModel.isLoading.value,
|
|
||||||
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
|
||||||
) {
|
|
||||||
when (it) {
|
|
||||||
true -> CircularProgressIndicator()
|
|
||||||
else -> Box(modifier = Modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.message.collect {
|
|
||||||
snack.showSnackbar(
|
|
||||||
message = it,
|
|
||||||
duration = SnackbarDuration.Short,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorSnack(
|
|
||||||
error = viewModel.networkError,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NetworkPage(
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: NetworkViewModel = koinViewModel(),
|
viewModel: NetworkViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.network
|
package com.pixelized.desktop.lwa.ui.screen.campaign.network
|
||||||
|
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.network
|
package com.pixelized.desktop.lwa.ui.screen.campaign.network
|
||||||
|
|
||||||
import androidx.compose.material.SnackbarDuration
|
import androidx.compose.material.SnackbarDuration
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.DropdownMenu
|
import androidx.compose.material.DropdownMenu
|
||||||
import androidx.compose.material.DropdownMenuItem
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.material.TopAppBar
|
import androidx.compose.material.TopAppBar
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
|
@ -17,33 +22,32 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.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.LocalWindowController
|
import com.pixelized.desktop.lwa.LocalWindowController
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToOldMainPage
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToSettings
|
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToSettings
|
||||||
|
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToGameMasterWindow
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory
|
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
|
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkDialog
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage
|
|
||||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
|
||||||
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.ic_settings_24dp
|
import lwacharactersheet.composeapp.generated.resources.ic_settings_24dp
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_table_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_timeline_24dp
|
import lwacharactersheet.composeapp.generated.resources.ic_timeline_24dp
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_wifi_24dp
|
import lwacharactersheet.composeapp.generated.resources.ic_wifi_24dp
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_wifi_off_24dp
|
import lwacharactersheet.composeapp.generated.resources.ic_wifi_off_24dp
|
||||||
import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
|
import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.settings__title
|
||||||
import org.jetbrains.compose.resources.painterResource
|
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 CampaignToolbar(
|
fun CampaignToolbar(
|
||||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
viewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
|
||||||
) {
|
) {
|
||||||
val windows = LocalWindowController.current
|
val windows = LocalWindowController.current
|
||||||
val screen = LocalScreenController.current
|
val screen = LocalScreenController.current
|
||||||
|
|
@ -51,60 +55,31 @@ fun CampaignToolbar(
|
||||||
val isOverflowMenuOpen = remember { mutableStateOf(false) }
|
val isOverflowMenuOpen = remember { mutableStateOf(false) }
|
||||||
val isNetworkMenuOpen = remember { mutableStateOf(false) }
|
val isNetworkMenuOpen = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val title = viewModel.title.collectAsState()
|
||||||
|
val status = viewModel.status.collectAsState()
|
||||||
|
val isGM = viewModel.isGM.collectAsState()
|
||||||
|
|
||||||
CampaignToolbarContent(
|
CampaignToolbarContent(
|
||||||
title = campaignViewModel.title.collectAsState(initial = ""),
|
title = title,
|
||||||
networkStatus = campaignViewModel.networkStatus.collectAsState(),
|
status = status,
|
||||||
|
isGM = isGM,
|
||||||
isNetworkMenuOpen = isNetworkMenuOpen,
|
isNetworkMenuOpen = isNetworkMenuOpen,
|
||||||
isOverflowMenuOpen = isOverflowMenuOpen,
|
isOverflowMenuOpen = isOverflowMenuOpen,
|
||||||
networkMenu = {
|
onGM = {
|
||||||
NetworkPage(
|
windows.navigateToGameMasterWindow()
|
||||||
modifier = Modifier.size(384.dp + 96.dp, 240.dp),
|
|
||||||
viewModel = networkViewModel
|
|
||||||
)
|
|
||||||
},
|
|
||||||
overflowMenu = {
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
isOverflowMenuOpen.value = false
|
|
||||||
windows.navigateToRollHistory()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_timeline_24dp),
|
|
||||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = 8.dp),
|
|
||||||
color = MaterialTheme.colors.primary,
|
|
||||||
text = stringResource(Res.string.main_page__roll_history_action),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
isOverflowMenuOpen.value = false
|
|
||||||
screen.navigateToOldMainPage()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_table_24dp),
|
|
||||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = 8.dp),
|
|
||||||
color = MaterialTheme.colors.primary,
|
|
||||||
text = "Ancienne interface utilisateur",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onNetwork = {
|
onNetwork = {
|
||||||
isNetworkMenuOpen.value = true
|
isNetworkMenuOpen.value = true
|
||||||
},
|
},
|
||||||
onOverflow = {
|
onOverflow = {
|
||||||
isOverflowMenuOpen.value = isOverflowMenuOpen.value.not()
|
isOverflowMenuOpen.value = true
|
||||||
|
},
|
||||||
|
onRollHistory = {
|
||||||
|
isOverflowMenuOpen.value = false
|
||||||
|
windows.navigateToRollHistory()
|
||||||
},
|
},
|
||||||
onSettings = {
|
onSettings = {
|
||||||
|
isOverflowMenuOpen.value = false
|
||||||
screen.navigateToSettings()
|
screen.navigateToSettings()
|
||||||
},
|
},
|
||||||
onDismissNetworkMenu = {
|
onDismissNetworkMenu = {
|
||||||
|
|
@ -120,13 +95,14 @@ fun CampaignToolbar(
|
||||||
private fun CampaignToolbarContent(
|
private fun CampaignToolbarContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
title: State<String>,
|
title: State<String>,
|
||||||
networkStatus: State<NetworkRepository.Status>,
|
status: State<NetworkRepository.Status>,
|
||||||
|
isGM: State<Boolean>,
|
||||||
isNetworkMenuOpen: State<Boolean>,
|
isNetworkMenuOpen: State<Boolean>,
|
||||||
isOverflowMenuOpen: State<Boolean>,
|
isOverflowMenuOpen: State<Boolean>,
|
||||||
networkMenu: @Composable () -> Unit,
|
onGM: () -> Unit,
|
||||||
overflowMenu: @Composable () -> Unit,
|
|
||||||
onNetwork: () -> Unit,
|
onNetwork: () -> Unit,
|
||||||
onOverflow: () -> Unit,
|
onOverflow: () -> Unit,
|
||||||
|
onRollHistory: () -> Unit,
|
||||||
onSettings: () -> Unit,
|
onSettings: () -> Unit,
|
||||||
onDismissNetworkMenu: () -> Unit,
|
onDismissNetworkMenu: () -> Unit,
|
||||||
onDismissOverflowMenu: () -> Unit,
|
onDismissOverflowMenu: () -> Unit,
|
||||||
|
|
@ -139,38 +115,38 @@ private fun CampaignToolbarContent(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isGM.value,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.size(size = 48.dp).clip(shape = CircleShape),
|
||||||
|
onClick = onGM,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
text = "GM",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onNetwork
|
onClick = onNetwork,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
when (networkStatus.value) {
|
when (status.value) {
|
||||||
NetworkRepository.Status.CONNECTED -> Res.drawable.ic_wifi_24dp
|
NetworkRepository.Status.CONNECTED -> Res.drawable.ic_wifi_24dp
|
||||||
NetworkRepository.Status.DISCONNECTED -> Res.drawable.ic_wifi_off_24dp
|
NetworkRepository.Status.DISCONNECTED -> Res.drawable.ic_wifi_off_24dp
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
tint = when (networkStatus.value) {
|
tint = when (status.value) {
|
||||||
NetworkRepository.Status.CONNECTED -> MaterialTheme.lwa.colorScheme.base.primary
|
NetworkRepository.Status.CONNECTED -> MaterialTheme.lwa.colorScheme.base.primary
|
||||||
NetworkRepository.Status.DISCONNECTED -> MaterialTheme.lwa.colorScheme.base.error
|
NetworkRepository.Status.DISCONNECTED -> MaterialTheme.lwa.colorScheme.base.error
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
|
||||||
offset = remember { DpOffset(x = -(48.dp * 2 + 8.dp), y = 8.dp) },
|
|
||||||
expanded = isNetworkMenuOpen.value,
|
|
||||||
onDismissRequest = onDismissNetworkMenu,
|
|
||||||
content = { networkMenu() },
|
|
||||||
)
|
|
||||||
IconButton(
|
|
||||||
onClick = onSettings
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_settings_24dp),
|
|
||||||
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onOverflow,
|
onClick = onOverflow,
|
||||||
) {
|
) {
|
||||||
|
|
@ -180,11 +156,50 @@ private fun CampaignToolbarContent(
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
offset = remember { DpOffset(x = -(48.dp + 8.dp), y = 8.dp) },
|
||||||
|
expanded = isNetworkMenuOpen.value,
|
||||||
|
onDismissRequest = onDismissNetworkMenu,
|
||||||
|
content = {
|
||||||
|
NetworkDialog(
|
||||||
|
modifier = Modifier.size(384.dp + 96.dp, 240.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
offset = remember { DpOffset(x = (-8).dp, y = 8.dp) },
|
offset = remember { DpOffset(x = (-8).dp, y = 8.dp) },
|
||||||
expanded = isOverflowMenuOpen.value,
|
expanded = isOverflowMenuOpen.value,
|
||||||
onDismissRequest = onDismissOverflowMenu,
|
onDismissRequest = onDismissOverflowMenu,
|
||||||
content = { overflowMenu() },
|
content = {
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = onRollHistory,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_timeline_24dp),
|
||||||
|
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
color = MaterialTheme.colors.primary,
|
||||||
|
text = stringResource(Res.string.main_page__roll_history_action),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = onSettings,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_settings_24dp),
|
||||||
|
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
color = MaterialTheme.colors.primary,
|
||||||
|
text = stringResource(Res.string.settings__title),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
|
||||||
|
|
||||||
|
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.desktop.lwa.repository.settings.SettingsRepository
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
class CampaignToolbarViewModel(
|
||||||
|
campaignRepository: CampaignRepository,
|
||||||
|
networkRepository: NetworkRepository,
|
||||||
|
settingsRepository: SettingsRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val status = networkRepository.status
|
||||||
|
|
||||||
|
val title = campaignRepository.campaignFlow
|
||||||
|
.map { it.scene.name }
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Lazily,
|
||||||
|
initialValue = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
val isGM = settingsRepository.settingsFlow()
|
||||||
|
.map { it.isGM }
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Lazily,
|
||||||
|
initialValue = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ class CharacterSheetViewModel(
|
||||||
characterId = argument.characterInstanceId.characterSheetId
|
characterId = argument.characterInstanceId.characterSheetId
|
||||||
) ?: return
|
) ?: return
|
||||||
_displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio(
|
_displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio(
|
||||||
id = preview.id,
|
id = preview.characterSheetId,
|
||||||
name = preview.name,
|
name = preview.name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
||||||
|
|
||||||
|
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action
|
||||||
|
|
||||||
|
class GameMasterActionUseCase(
|
||||||
|
private val campaignRepository: CampaignRepository,
|
||||||
|
) {
|
||||||
|
suspend fun handleAction(
|
||||||
|
characterSheetId: String,
|
||||||
|
action: Action,
|
||||||
|
) {
|
||||||
|
when (action) {
|
||||||
|
Action.DisplayPortrait -> TODO()
|
||||||
|
|
||||||
|
Action.AddToGroup -> campaignRepository.addCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.AddToNpc -> campaignRepository.addNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
is Action.RemoveFromGroup -> campaignRepository.removeCharacter(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = action.instanceId
|
||||||
|
)
|
||||||
|
|
||||||
|
is Action.RemoveFromNpc -> campaignRepository.removeNpc(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = action.instanceId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster
|
||||||
|
|
||||||
|
import com.pixelized.desktop.lwa.repository.campaign.model.CharacterSheetPreview
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action
|
||||||
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character_label
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__character_search
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc_label
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_tag__npc_search
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
|
import java.text.Normalizer
|
||||||
|
|
||||||
|
class GameMasterFactory {
|
||||||
|
|
||||||
|
suspend fun convertToGMCharacterPreviewUio(
|
||||||
|
campaign: Campaign,
|
||||||
|
characters: List<CharacterSheetPreview>,
|
||||||
|
filter: String,
|
||||||
|
): List<GMCharacterPreviewUio> {
|
||||||
|
val normalizedFilter = Normalizer.normalize(filter, Normalizer.Form.NFD)
|
||||||
|
|
||||||
|
return characters.mapNotNull {
|
||||||
|
convertToGMCharacterPreviewUio(
|
||||||
|
campaign = campaign,
|
||||||
|
character = it,
|
||||||
|
filter = normalizedFilter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun convertToGMCharacterPreviewUio(
|
||||||
|
campaign: Campaign,
|
||||||
|
character: CharacterSheetPreview,
|
||||||
|
filter: String,
|
||||||
|
): GMCharacterPreviewUio? {
|
||||||
|
val characterId = campaign.characters.keys.firstOrNull {
|
||||||
|
it.characterSheetId == character.characterSheetId
|
||||||
|
}
|
||||||
|
|
||||||
|
val npcIds = campaign.npcs.keys.filter {
|
||||||
|
it.characterSheetId == character.characterSheetId
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerTagHighlighted = false
|
||||||
|
var npcTagHighlighted = false
|
||||||
|
|
||||||
|
// Filter process.
|
||||||
|
if (filter.isNotEmpty()) {
|
||||||
|
val normalizedName = Normalizer.normalize(character.name, Normalizer.Form.NFD)
|
||||||
|
// If the filter is not empty and the character is not
|
||||||
|
val playerTag = getString(Res.string.game_master__character_tag__character_search)
|
||||||
|
val npcTag = getString(Res.string.game_master__character_tag__npc_search)
|
||||||
|
|
||||||
|
playerTagHighlighted = playerTag.contains(other = filter, ignoreCase = true)
|
||||||
|
if (playerTagHighlighted && characterId == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
npcTagHighlighted = npcTag.contains(other = filter, ignoreCase = true)
|
||||||
|
if (npcTagHighlighted && npcIds.isEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val nameHighlight = normalizedName.contains(other = filter, ignoreCase = true)
|
||||||
|
if (nameHighlight.not() && playerTagHighlighted.not() && npcTagHighlighted.not()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val tags = buildList {
|
||||||
|
if (characterId != null) {
|
||||||
|
add(
|
||||||
|
GMCharacterPreviewUio.Tag(
|
||||||
|
label = getString(
|
||||||
|
Res.string.game_master__character_tag__character_label,
|
||||||
|
characterId.instanceId,
|
||||||
|
),
|
||||||
|
highlight = playerTagHighlighted,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addAll(
|
||||||
|
npcIds.map { npcId ->
|
||||||
|
GMCharacterPreviewUio.Tag(
|
||||||
|
label = getString(
|
||||||
|
Res.string.game_master__character_tag__npc_label,
|
||||||
|
npcId.instanceId
|
||||||
|
),
|
||||||
|
highlight = npcTagHighlighted,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val actions = buildList {
|
||||||
|
add(
|
||||||
|
when (characterId) {
|
||||||
|
null -> Action.AddToGroup
|
||||||
|
else -> Action.RemoveFromGroup(instanceId = characterId.instanceId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
add(Action.AddToNpc)
|
||||||
|
addAll(
|
||||||
|
npcIds.map { npcId ->
|
||||||
|
Action.RemoveFromNpc(instanceId = npcId.instanceId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GMCharacterPreviewUio(
|
||||||
|
characterSheetId = character.characterSheetId,
|
||||||
|
name = character.name, level = character.level,
|
||||||
|
tags = tags,
|
||||||
|
actions = actions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
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.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
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.items
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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.screen.gamemaster.items.GMCharacterPreview
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameMasterScreen(
|
||||||
|
viewModel: GameMasterViewModel = koinViewModel(),
|
||||||
|
) {
|
||||||
|
val characters = viewModel.characters.collectAsState()
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
GameMasterContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
filter = viewModel.filter,
|
||||||
|
characters = characters,
|
||||||
|
onCharacterAction = viewModel::onCharacterAction,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GameMasterContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
filter: LwaTextFieldUio,
|
||||||
|
characters: State<List<GMCharacterPreviewUio>>,
|
||||||
|
onCharacterAction: (String, GMCharacterPreviewUio.Action) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(paddingValues = paddingValues)
|
||||||
|
) {
|
||||||
|
LwaTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
field = filter,
|
||||||
|
trailingIcon = {
|
||||||
|
val value = filter.valueFlow.collectAsState()
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = value.value.isNotBlank(),
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { filter.onValueChange.invoke("") },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_cancel_24dp),
|
||||||
|
tint = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
contentPadding = PaddingValues(all = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = characters.value,
|
||||||
|
) { character ->
|
||||||
|
GMCharacterPreview(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
character = character,
|
||||||
|
onAction = { action ->
|
||||||
|
onCharacterAction(character.characterSheetId, action)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
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.ui.composable.textfield.LwaTextFieldUio
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class GameMasterViewModel(
|
||||||
|
private val campaignRepository: CampaignRepository,
|
||||||
|
private val characterSheetRepository: CharacterSheetRepository,
|
||||||
|
private val gameMasterFactory: GameMasterFactory,
|
||||||
|
private val useCase: GameMasterActionUseCase,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _filter = MutableStateFlow("")
|
||||||
|
val filter = LwaTextFieldUio(
|
||||||
|
enable = true,
|
||||||
|
labelFlow = MutableStateFlow("Filtre"),
|
||||||
|
valueFlow = _filter,
|
||||||
|
placeHolderFlow = MutableStateFlow(null),
|
||||||
|
onValueChange = { _filter.value = it },
|
||||||
|
)
|
||||||
|
|
||||||
|
val characters = combine(
|
||||||
|
campaignRepository.campaignFlow,
|
||||||
|
characterSheetRepository.characterSheetPreviewFlow,
|
||||||
|
filter.valueFlow,
|
||||||
|
gameMasterFactory::convertToGMCharacterPreviewUio,
|
||||||
|
).stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun onCharacterAction(
|
||||||
|
characterSheetId: String,
|
||||||
|
action: GMCharacterPreviewUio.Action,
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
useCase.handleAction(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
action = action,
|
||||||
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
package com.pixelized.desktop.lwa.ui.screen.gamemaster.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.DropdownMenu
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.ui.screen.gamemaster.items.GMCharacterPreviewUio.Action
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
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_npc
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__display_portrait
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__remove_from_group
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_action__remove_from_npc
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.game_master__character_level__label
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class GMCharacterPreviewUio(
|
||||||
|
val characterSheetId: String,
|
||||||
|
val name: String,
|
||||||
|
val level: Int,
|
||||||
|
val tags: List<Tag>,
|
||||||
|
val actions: List<Action>,
|
||||||
|
) {
|
||||||
|
@Stable
|
||||||
|
data class Tag(
|
||||||
|
val label: String,
|
||||||
|
val highlight: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
sealed class Action {
|
||||||
|
@Stable
|
||||||
|
data object DisplayPortrait : Action()
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data object AddToGroup : Action()
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class RemoveFromGroup(val instanceId: Int) : Action()
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data object AddToNpc : Action()
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class RemoveFromNpc(val instanceId: Int) : Action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object GMCharacterPreviewDefault {
|
||||||
|
val padding = PaddingValues(horizontal = 16.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GMCharacterPreview(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
padding: PaddingValues = GMCharacterPreviewDefault.padding,
|
||||||
|
character: GMCharacterPreviewUio,
|
||||||
|
onAction: (Action) -> Unit,
|
||||||
|
) {
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
val startPadding = padding.calculateStartPadding(layoutDirection)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(shape = remember { RoundedCornerShape(8.dp) })
|
||||||
|
.background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp)
|
||||||
|
.then(other = modifier),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(start = startPadding),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(weight = 1f),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.lwa.typography.base.body1,
|
||||||
|
text = character.name,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.lwa.typography.base.caption,
|
||||||
|
text = stringResource(
|
||||||
|
Res.string.game_master__character_level__label,
|
||||||
|
character.level,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OverflowActionMenu(
|
||||||
|
character = character,
|
||||||
|
onAction = onAction,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues = padding)
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||||
|
) {
|
||||||
|
character.tags.forEach { tag ->
|
||||||
|
Tag(tag = tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun OverflowActionMenu(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
character: GMCharacterPreviewUio,
|
||||||
|
onAction: (Action) -> Unit,
|
||||||
|
) {
|
||||||
|
val overflowMenu = remember(character) {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = {
|
||||||
|
overflowMenu.value = true
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.MoreVert,
|
||||||
|
tint = MaterialTheme.colors.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
offset = remember { DpOffset(x = -(48.dp + 8.dp), y = -(48.dp)) },
|
||||||
|
expanded = overflowMenu.value,
|
||||||
|
onDismissRequest = {
|
||||||
|
overflowMenu.value = false
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
character.actions.forEach { action ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
overflowMenu.value = false
|
||||||
|
onAction(action)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.lwa.typography.base.body1,
|
||||||
|
color = MaterialTheme.lwa.colorScheme.base.primary,
|
||||||
|
text = when (action) {
|
||||||
|
Action.DisplayPortrait -> stringResource(
|
||||||
|
Res.string.game_master__character_action__display_portrait,
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.AddToGroup -> stringResource(
|
||||||
|
Res.string.game_master__character_action__add_to_group,
|
||||||
|
)
|
||||||
|
|
||||||
|
Action.AddToNpc -> stringResource(
|
||||||
|
Res.string.game_master__character_action__add_to_npc,
|
||||||
|
)
|
||||||
|
|
||||||
|
is Action.RemoveFromGroup -> stringResource(
|
||||||
|
Res.string.game_master__character_action__remove_from_group,
|
||||||
|
action.instanceId,
|
||||||
|
)
|
||||||
|
|
||||||
|
is Action.RemoveFromNpc -> stringResource(
|
||||||
|
Res.string.game_master__character_action__remove_from_npc,
|
||||||
|
action.instanceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Tag(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
padding: PaddingValues = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
|
||||||
|
tag: GMCharacterPreviewUio.Tag,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = modifier
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.lwa.colorScheme.elevated.base4dp,
|
||||||
|
shape = CircleShape,
|
||||||
|
)
|
||||||
|
.padding(paddingValues = padding),
|
||||||
|
style = MaterialTheme.lwa.typography.base.caption,
|
||||||
|
color = when (tag.highlight) {
|
||||||
|
true -> MaterialTheme.lwa.colorScheme.base.secondary
|
||||||
|
else -> MaterialTheme.lwa.colorScheme.base.onSurface
|
||||||
|
},
|
||||||
|
text = tag.label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.main
|
|
||||||
|
|
||||||
import androidx.compose.foundation.ScrollState
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.Scaffold
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextButton
|
|
||||||
import androidx.compose.material.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.pixelized.desktop.lwa.LocalWindowController
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToMainPage
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToNetwork
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheet
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit
|
|
||||||
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory
|
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.app_name
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__create__title
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_file_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_folder_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_swords_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_table_24dp
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.main_page__network_action
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.main_page__open_save_directory
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
|
|
||||||
import org.jetbrains.compose.resources.getString
|
|
||||||
import org.jetbrains.compose.resources.painterResource
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
data class CharacterUio(
|
|
||||||
val id: Campaign.CharacterInstance.Id,
|
|
||||||
val name: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun OldMainPage(
|
|
||||||
viewModel: MainPageViewModel = koinViewModel(),
|
|
||||||
) {
|
|
||||||
val window = LocalWindowController.current
|
|
||||||
val screen = LocalScreenController.current
|
|
||||||
val characters = viewModel.characters.collectAsState()
|
|
||||||
val npcs = viewModel.npcs.collectAsState()
|
|
||||||
val enableRollHistory = viewModel.enableRollHistoryFlow.collectAsState()
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
MainPageContent(
|
|
||||||
characters = characters,
|
|
||||||
npcs = npcs,
|
|
||||||
enableRollHistory = enableRollHistory,
|
|
||||||
onCharacter = {
|
|
||||||
window.navigateToCharacterSheet(
|
|
||||||
characterId = it.id,
|
|
||||||
title = it.name,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onCreateCharacter = {
|
|
||||||
window.navigateToCharacterSheetEdit(
|
|
||||||
characterId = null,
|
|
||||||
title = runBlocking { getString(Res.string.character_sheet_edit__create__title) },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onRollHistory = {
|
|
||||||
window.navigateToRollHistory()
|
|
||||||
},
|
|
||||||
onOpenSaveDirectory = {
|
|
||||||
viewModel.openSaveDirectory()
|
|
||||||
},
|
|
||||||
onNetwork = {
|
|
||||||
screen.navigateToNetwork()
|
|
||||||
},
|
|
||||||
onMainPage = {
|
|
||||||
screen.navigateToMainPage()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MainPageContent(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
scrollState: ScrollState = rememberScrollState(),
|
|
||||||
characters: State<List<CharacterUio>>,
|
|
||||||
npcs: State<List<CharacterUio>>,
|
|
||||||
enableRollHistory: State<Boolean>,
|
|
||||||
onCharacter: (CharacterUio) -> Unit,
|
|
||||||
onCreateCharacter: () -> Unit,
|
|
||||||
onRollHistory: () -> Unit,
|
|
||||||
onOpenSaveDirectory: () -> Unit,
|
|
||||||
onNetwork: () -> Unit,
|
|
||||||
onMainPage: () -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier,
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = runBlocking { getString(Res.string.app_name) },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
TextButton(
|
|
||||||
onClick = onMainPage,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_swords_24dp),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
maxLines = 1,
|
|
||||||
text = "Nouvelle interface utilisateur",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
content = {
|
|
||||||
Column {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.verticalScroll(state = scrollState)
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
if (characters.value.isNotEmpty()) {
|
|
||||||
Column {
|
|
||||||
characters.value.forEach { sheet ->
|
|
||||||
TextButton(
|
|
||||||
onClick = { onCharacter(sheet) },
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
maxLines = 1,
|
|
||||||
text = sheet.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characters.value.isNotEmpty() && npcs.value.isNotEmpty()) {
|
|
||||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (npcs.value.isNotEmpty()) {
|
|
||||||
Column {
|
|
||||||
npcs.value.forEach { sheet ->
|
|
||||||
TextButton(
|
|
||||||
onClick = { onCharacter(sheet) },
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
maxLines = 1,
|
|
||||||
text = sheet.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
|
||||||
|
|
||||||
TextButton(
|
|
||||||
onClick = onCreateCharacter,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_file_24dp),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
text = stringResource(Res.string.main_page__create_action),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextButton(
|
|
||||||
onClick = onOpenSaveDirectory,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_folder_24dp),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
text = stringResource(Res.string.main_page__open_save_directory),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextButton(
|
|
||||||
enabled = enableRollHistory.value,
|
|
||||||
onClick = onRollHistory,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_d20_24dp),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
text = stringResource(Res.string.main_page__roll_history_action),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextButton(
|
|
||||||
onClick = onNetwork,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(Res.drawable.ic_table_24dp),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
maxLines = 1,
|
|
||||||
text = stringResource(Res.string.main_page__network_action),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.main
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.lordcodes.turtle.shellRun
|
|
||||||
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.shared.lwa.utils.OperatingSystem
|
|
||||||
import com.pixelized.shared.lwa.utils.PathProvider
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.flatMapMerge
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
|
|
||||||
class MainPageViewModel(
|
|
||||||
private val pathProvider: PathProvider,
|
|
||||||
private val characterSheetRepository: CharacterSheetRepository,
|
|
||||||
private val campaignRepository: CampaignRepository,
|
|
||||||
networkRepository: NetworkRepository,
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
val characters: StateFlow<List<CharacterUio>> = campaignRepository.campaignFlow
|
|
||||||
.flatMapMerge { campaign ->
|
|
||||||
combine(
|
|
||||||
campaign.characters
|
|
||||||
.map { entry ->
|
|
||||||
characterSheetRepository
|
|
||||||
.characterDetailFlow(characterSheetId = entry.key.characterSheetId)
|
|
||||||
.map transform@{ sheet ->
|
|
||||||
if (sheet == null) return@transform null
|
|
||||||
CharacterUio(id = entry.key, name = sheet.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ifEmpty {
|
|
||||||
listOf(flowOf(null))
|
|
||||||
}
|
|
||||||
) { data ->
|
|
||||||
data.mapNotNull { it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
initialValue = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
val npcs: StateFlow<List<CharacterUio>> = campaignRepository.campaignFlow
|
|
||||||
.flatMapMerge { campaign ->
|
|
||||||
combine(
|
|
||||||
campaign.npcs
|
|
||||||
.map { entry ->
|
|
||||||
characterSheetRepository
|
|
||||||
.characterDetailFlow(characterSheetId = entry.key.characterSheetId)
|
|
||||||
.map transform@{ sheet ->
|
|
||||||
if (sheet == null) return@transform null
|
|
||||||
CharacterUio(id = entry.key, name = sheet.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ifEmpty {
|
|
||||||
listOf(flowOf(null))
|
|
||||||
}
|
|
||||||
) { data ->
|
|
||||||
data.mapNotNull { it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.Eagerly,
|
|
||||||
initialValue = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val enableRollHistoryFlow = networkRepository.status
|
|
||||||
.map { it == NetworkRepository.Status.CONNECTED }
|
|
||||||
.stateIn(
|
|
||||||
scope = viewModelScope,
|
|
||||||
started = SharingStarted.Lazily,
|
|
||||||
initialValue = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun openSaveDirectory(
|
|
||||||
os: OperatingSystem = OperatingSystem.current,
|
|
||||||
) {
|
|
||||||
val path = pathProvider.storePath(os = os)
|
|
||||||
when (os) {
|
|
||||||
OperatingSystem.Windows -> shellRun("explorer.exe", listOf(path))
|
|
||||||
OperatingSystem.Macintosh -> shellRun("open", listOf(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,7 +18,7 @@ class RollHistoryViewModel(
|
||||||
characterRepository.characterSheetPreviewFlow,
|
characterRepository.characterSheetPreviewFlow,
|
||||||
rollRepository.rolls,
|
rollRepository.rolls,
|
||||||
) { sheets, message ->
|
) { sheets, message ->
|
||||||
val name = sheets.firstOrNull { it.id == message.characterSheetId }?.name ?: ""
|
val name = sheets.firstOrNull { it.characterSheetId == message.characterSheetId }?.name ?: ""
|
||||||
val roll = RollHistoryItemUio(
|
val roll = RollHistoryItemUio(
|
||||||
character = name,
|
character = name,
|
||||||
skillLabel = message.skillLabel,
|
skillLabel = message.skillLabel,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class SettingsUseCase {
|
||||||
autoHideDelay = 8,
|
autoHideDelay = 8,
|
||||||
autoShowChat = true,
|
autoShowChat = true,
|
||||||
autoScrollChat = true,
|
autoScrollChat = true,
|
||||||
|
isGM = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,11 @@ class CampaignService(
|
||||||
initialValue = factory.convertToJson(campaignFlow.value),
|
initialValue = factory.convertToJson(campaignFlow.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun campaign(): CampaignJson {
|
fun campaign(): Campaign {
|
||||||
|
return campaignFlow.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun campaignJson(): CampaignJson {
|
||||||
return campaignJsonFlow.value
|
return campaignJsonFlow.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@ import io.ktor.server.response.respond
|
||||||
|
|
||||||
fun Engine.getCampaign(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
fun Engine.getCampaign(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||||
return {
|
return {
|
||||||
call.respond(campaignService.campaign())
|
call.respond(campaignService.campaignJson())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,24 +9,29 @@ import io.ktor.server.response.respondText
|
||||||
|
|
||||||
fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||||
return {
|
return {
|
||||||
|
try {
|
||||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
?: error("missing character sheet id")
|
||||||
val id = if (characterSheetId != null && instanceId != null) {
|
|
||||||
Campaign.CharacterInstance.Id(
|
val instanceId = campaignService.campaign().characters.keys
|
||||||
|
.firstOrNull { key -> key.characterSheetId == characterSheetId }
|
||||||
|
|
||||||
|
if (instanceId != null) {
|
||||||
|
error("Character Already in party")
|
||||||
|
}
|
||||||
|
|
||||||
|
val id = Campaign.CharacterInstance.Id(
|
||||||
characterSheetId = characterSheetId,
|
characterSheetId = characterSheetId,
|
||||||
instanceId = instanceId
|
instanceId = 0,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
null
|
if (campaignService.addCharacter(id).not()) {
|
||||||
}
|
error("Unexpected error occurred when the character instance was added to the party")
|
||||||
val updated = id?.let { campaignService.addCharacter(it) } ?: false
|
|
||||||
val code = when (updated) {
|
|
||||||
true -> HttpStatusCode.Accepted
|
|
||||||
else -> HttpStatusCode.UnprocessableEntity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
call.respondText(
|
call.respondText(
|
||||||
text = "$code",
|
text = "Character $characterSheetId successfully added to the party",
|
||||||
status = code,
|
status = HttpStatusCode.Accepted,
|
||||||
)
|
)
|
||||||
webSocket.emit(
|
webSocket.emit(
|
||||||
Message(
|
Message(
|
||||||
|
|
@ -34,5 +39,13 @@ fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext
|
||||||
value = RestSynchronisation.Campaign,
|
value = RestSynchronisation.Campaign,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
call.run {
|
||||||
|
respondText(
|
||||||
|
text = "${exception.message}",
|
||||||
|
status = HttpStatusCode.UnprocessableEntity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,35 +9,46 @@ import io.ktor.server.response.respondText
|
||||||
|
|
||||||
fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||||
return {
|
return {
|
||||||
|
try {
|
||||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
?: error("missing character sheet id")
|
||||||
|
|
||||||
val id = if (characterSheetId != null && instanceId != null) {
|
val instanceId = campaignService.campaign().npcs.keys
|
||||||
Campaign.CharacterInstance.Id(
|
.filter { it.characterSheetId == characterSheetId }
|
||||||
characterSheetId = characterSheetId,
|
.reduceOrNull { acc, id ->
|
||||||
instanceId = instanceId
|
if (acc.instanceId < id.instanceId) {
|
||||||
)
|
id
|
||||||
} else {
|
} else {
|
||||||
null
|
acc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val updated = id?.let { campaignService.addNpc(it) } ?: false
|
val id = Campaign.CharacterInstance.Id(
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
instanceId = instanceId?.let { it.instanceId + 1 } ?: 0,
|
||||||
|
)
|
||||||
|
|
||||||
val code = when (updated) {
|
if (campaignService.addNpc(id).not()) {
|
||||||
true -> HttpStatusCode.Accepted
|
error("Unexpected error occurred when the character instance was added to the npcs")
|
||||||
else -> HttpStatusCode.UnprocessableEntity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
call.respondText(
|
call.respondText(
|
||||||
text = "$code",
|
text = "Character $characterSheetId successfully added to the npcs",
|
||||||
status = code,
|
status = HttpStatusCode.Accepted,
|
||||||
)
|
)
|
||||||
|
|
||||||
webSocket.emit(
|
webSocket.emit(
|
||||||
Message(
|
Message(
|
||||||
from = "Server",
|
from = "Server",
|
||||||
value = RestSynchronisation.Campaign,
|
value = RestSynchronisation.Campaign,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
call.run {
|
||||||
|
respondText(
|
||||||
|
text = "${exception.message}",
|
||||||
|
status = HttpStatusCode.UnprocessableEntity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +87,8 @@ class CharacterSheetJsonFactory(
|
||||||
): CharacterPreviewJson {
|
): CharacterPreviewJson {
|
||||||
return CharacterPreviewJson(
|
return CharacterPreviewJson(
|
||||||
id = sheet.id,
|
id = sheet.id,
|
||||||
|
portrait = sheet.portrait,
|
||||||
|
thumbnail = sheet.thumbnail,
|
||||||
name = sheet.name,
|
name = sheet.name,
|
||||||
level = sheet.level,
|
level = sheet.level,
|
||||||
)
|
)
|
||||||
|
|
@ -98,8 +100,8 @@ class CharacterSheetJsonFactory(
|
||||||
val json = CharacterSheetJsonV1(
|
val json = CharacterSheetJsonV1(
|
||||||
id = sheet.id,
|
id = sheet.id,
|
||||||
name = sheet.name,
|
name = sheet.name,
|
||||||
thumbnail = sheet.thumbnail,
|
|
||||||
portrait = sheet.portrait,
|
portrait = sheet.portrait,
|
||||||
|
thumbnail = sheet.thumbnail,
|
||||||
level = sheet.level,
|
level = sheet.level,
|
||||||
shouldLevelUp = sheet.shouldLevelUp,
|
shouldLevelUp = sheet.shouldLevelUp,
|
||||||
strength = sheet.strength,
|
strength = sheet.strength,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable
|
||||||
@Serializable
|
@Serializable
|
||||||
class CharacterPreviewJson(
|
class CharacterPreviewJson(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
val portrait: String?,
|
||||||
|
val thumbnail: String?,
|
||||||
val name: String,
|
val name: String,
|
||||||
val level: Int,
|
val level: Int,
|
||||||
)
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue