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__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>
|
||||
|
|
@ -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.destination.CharacterSheetEditWindow
|
||||
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.rememberMaxWindowHeight
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay
|
||||
import com.pixelized.desktop.lwa.ui.screen.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.characterSheet.CharacterSheetMainNavHost
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
|
||||
import com.pixelized.desktop.lwa.ui.theme.LwaTheme
|
||||
import com.pixelized.desktop.lwa.utils.InstallCoil
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -153,10 +149,6 @@ fun ApplicationScope.LwaApplication() {
|
|||
@Composable
|
||||
private fun MainWindowScreen(
|
||||
dataSyncViewModel: DataSyncViewModel = koinViewModel(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
||||
campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
|
||||
rollViewModel: RollHistoryViewModel = koinViewModel(),
|
||||
) {
|
||||
LaunchedEffect(Unit) {
|
||||
dataSyncViewModel.autoConnect()
|
||||
|
|
@ -221,7 +213,6 @@ private fun MainWindowScreen(
|
|||
)
|
||||
WindowsHandler(
|
||||
windowController = windowController,
|
||||
rollViewModel = rollViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -230,7 +221,6 @@ private fun MainWindowScreen(
|
|||
@Composable
|
||||
private fun WindowsHandler(
|
||||
windowController: WindowController,
|
||||
rollViewModel: RollHistoryViewModel = koinViewModel(),
|
||||
) {
|
||||
WindowsNavHost(
|
||||
controller = windowController,
|
||||
|
|
@ -248,11 +238,9 @@ private fun WindowsHandler(
|
|||
),
|
||||
)
|
||||
|
||||
is RollHistoryWindow -> RollHistoryPage(
|
||||
viewModel = rollViewModel,
|
||||
)
|
||||
is RollHistoryWindow -> RollHistoryPage()
|
||||
|
||||
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.CharacterSheetCharacteristicDialogFactory
|
||||
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.TextMessageFactory
|
||||
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.levelup.LevelUpFactory
|
||||
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.network.NetworkFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkFactory
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.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.settings.SettingsViewModel
|
||||
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
|
||||
|
|
@ -112,13 +114,13 @@ val factoryDependencies
|
|||
factoryOf(::CharacterSheetCharacteristicDialogFactory)
|
||||
factoryOf(::TextMessageFactory)
|
||||
factoryOf(::LevelUpFactory)
|
||||
factoryOf(::GameMasterFactory)
|
||||
}
|
||||
|
||||
val viewModelDependencies
|
||||
get() = module {
|
||||
viewModelOf(::DataSyncViewModel)
|
||||
viewModelOf(::CampaignViewModel)
|
||||
viewModelOf(::MainPageViewModel)
|
||||
viewModelOf(::CampaignToolbarViewModel)
|
||||
viewModelOf(::CharacterSheetViewModel)
|
||||
viewModelOf(::CharacterSheetEditViewModel)
|
||||
viewModelOf(::RollViewModel)
|
||||
|
|
@ -131,9 +133,11 @@ val viewModelDependencies
|
|||
viewModelOf(::CampaignChatViewModel)
|
||||
viewModelOf(::SettingsViewModel)
|
||||
viewModelOf(::LevelUpViewModel)
|
||||
viewModelOf(::GameMasterViewModel)
|
||||
}
|
||||
|
||||
val useCaseDependencies
|
||||
get() = module {
|
||||
factoryOf(::SettingsUseCase)
|
||||
factoryOf(::GameMasterActionUseCase)
|
||||
}
|
||||
|
|
@ -17,11 +17,11 @@ interface LwaClient {
|
|||
|
||||
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 campaignAddNpc(characterSheetId: String, instanceId: Int)
|
||||
suspend fun campaignAddNpc(characterSheetId: String)
|
||||
|
||||
suspend fun campaignDeleteNpc(characterSheetId: String, instanceId: Int)
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,8 @@ class LwaClientImpl(
|
|||
|
||||
override suspend fun campaignAddCharacter(
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
) = client
|
||||
.put("$root/campaign/character/update?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
||||
.put("$root/campaign/character/update?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun campaignDeleteCharacter(
|
||||
|
|
@ -59,9 +58,8 @@ class LwaClientImpl(
|
|||
|
||||
override suspend fun campaignAddNpc(
|
||||
characterSheetId: String,
|
||||
instanceId: Int,
|
||||
) = client
|
||||
.put("$root/campaign/npc/update?characterSheetId=$characterSheetId&instanceId=$instanceId")
|
||||
.put("$root/campaign/npc/update?characterSheetId=$characterSheetId")
|
||||
.body<Unit>()
|
||||
|
||||
override suspend fun campaignDeleteNpc(
|
||||
|
|
|
|||
|
|
@ -40,4 +40,32 @@ class CampaignRepository(
|
|||
): Campaign.CharacterInstance {
|
||||
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.repository.network.NetworkRepository
|
||||
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.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.model.campaign.npc
|
||||
import com.pixelized.shared.lwa.protocol.websocket.Message
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
|
||||
|
|
@ -47,6 +47,42 @@ class CampaignStore(
|
|||
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.
|
||||
|
||||
private suspend fun handleMessage(message: Message) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.desktop.lwa.repository.campaign.model
|
||||
|
||||
data class CharacterSheetPreview(
|
||||
val id: String,
|
||||
val characterSheetId: String,
|
||||
val name: String,
|
||||
val level: Int,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class CharacterSheetRepository(
|
|||
val characterDetailFlow get() = store.detailFlow
|
||||
|
||||
fun characterPreview(characterId: String?): CharacterSheetPreview? {
|
||||
return characterSheetPreviewFlow.value.firstOrNull { it.id == characterId }
|
||||
return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId }
|
||||
}
|
||||
|
||||
suspend fun characterDetail(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class CharacterSheetStore(
|
|||
val request = client.characters()
|
||||
val data = request.map {
|
||||
CharacterSheetPreview(
|
||||
id = it.id,
|
||||
characterSheetId = it.id,
|
||||
name = it.name,
|
||||
level = it.level,
|
||||
)
|
||||
|
|
@ -105,7 +105,7 @@ class CharacterSheetStore(
|
|||
|
||||
is RestSynchronisation.CharacterDelete -> {
|
||||
_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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class SettingsFactory(
|
|||
autoHideDelay = settings.autoHideDelay,
|
||||
autoShowChat = settings.autoShowChat,
|
||||
autoScrollChat = settings.autoScrollChat,
|
||||
isGM = settings.isGM,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ class SettingsFactory(
|
|||
autoHideDelay = json.autoHideDelay ?: default.autoHideDelay,
|
||||
autoShowChat = json.autoShowChat ?: default.autoShowChat,
|
||||
autoScrollChat = json.autoScrollChat ?: default.autoScrollChat,
|
||||
isGM = json.isGM ?: default.isGM,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ data class Settings(
|
|||
val autoHideDelay: Int,
|
||||
val autoShowChat: Boolean,
|
||||
val autoScrollChat: Boolean,
|
||||
val isGM: Boolean,
|
||||
) {
|
||||
val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}"
|
||||
}
|
||||
|
|
@ -12,4 +12,5 @@ data class SettingsJsonV1(
|
|||
val autoHideDelay: Int?,
|
||||
val autoShowChat: Boolean?,
|
||||
val autoScrollChat: Boolean?,
|
||||
val isGM: Boolean?,
|
||||
) : 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.composableLevelUp
|
||||
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.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> {
|
||||
error("MainNavHost controller is not yet ready")
|
||||
|
|
@ -35,9 +30,6 @@ fun MainNavHost(
|
|||
composableMainPage()
|
||||
composableSettingsPage()
|
||||
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
|
||||
|
||||
@Stable
|
||||
class NetworkWindows(
|
||||
class GameMasterWindow(
|
||||
title: String,
|
||||
size: DpSize,
|
||||
) : Window(
|
||||
|
|
@ -14,14 +14,15 @@ class NetworkWindows(
|
|||
size = size,
|
||||
)
|
||||
|
||||
fun WindowController.navigateToNetwork(
|
||||
title: String = "",
|
||||
fun WindowController.navigateToGameMasterWindow(
|
||||
title: String = "Game master",
|
||||
) {
|
||||
showWindow(
|
||||
window = NetworkWindows(
|
||||
title = title, size = DpSize(
|
||||
width = 464.dp,
|
||||
height = 300.dp,
|
||||
window = GameMasterWindow(
|
||||
title = title,
|
||||
size = DpSize(
|
||||
width = 400.dp + 64.dp,
|
||||
height = maxWindowHeight - 32.dp,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -19,7 +19,8 @@ fun WindowController.navigateToRollHistory(
|
|||
) {
|
||||
showWindow(
|
||||
window = RollHistoryWindow(
|
||||
title = title, size = DpSize(
|
||||
title = title,
|
||||
size = DpSize(
|
||||
width = 400.dp + 64.dp,
|
||||
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.toolbar.CampaignToolbar
|
||||
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.overlay.roll.RollHostState
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollOverlay
|
||||
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
|
|
@ -58,7 +56,7 @@ fun CampaignScreen(
|
|||
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
||||
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
|
||||
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
|
||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
||||
campaignViewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
|
||||
) {
|
||||
|
|
@ -88,8 +86,7 @@ fun CampaignScreen(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
top = {
|
||||
CampaignToolbar(
|
||||
campaignViewModel = campaignViewModel,
|
||||
networkViewModel = networkViewModel,
|
||||
viewModel = campaignViewModel,
|
||||
)
|
||||
},
|
||||
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.AnimatedVisibility
|
||||
|
|
@ -27,15 +27,11 @@ import androidx.compose.material.CircularProgressIndicator
|
|||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.SnackbarDuration
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
|
|
@ -44,12 +40,10 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.LocalSnackHost
|
||||
import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent
|
||||
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 kotlinx.coroutines.launch
|
||||
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__socket__connect_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.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
|
@ -100,93 +93,7 @@ data class NetworkPageUio(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun NetworkScreen(
|
||||
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(
|
||||
fun NetworkDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
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
|
||||
|
||||
|
|
@ -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.runtime.State
|
||||
|
|
@ -1,13 +1,18 @@
|
|||
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.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.LocalWindowController
|
||||
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.destination.navigateToOldMainPage
|
||||
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.screen.campaign.CampaignViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage
|
||||
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.network.NetworkDialog
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
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_wifi_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.settings__title
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun CampaignToolbar(
|
||||
campaignViewModel: CampaignViewModel = koinViewModel(),
|
||||
networkViewModel: NetworkViewModel = koinViewModel(),
|
||||
viewModel: CampaignToolbarViewModel = koinViewModel(),
|
||||
) {
|
||||
val windows = LocalWindowController.current
|
||||
val screen = LocalScreenController.current
|
||||
|
|
@ -51,60 +55,31 @@ fun CampaignToolbar(
|
|||
val isOverflowMenuOpen = remember { mutableStateOf(false) }
|
||||
val isNetworkMenuOpen = remember { mutableStateOf(false) }
|
||||
|
||||
val title = viewModel.title.collectAsState()
|
||||
val status = viewModel.status.collectAsState()
|
||||
val isGM = viewModel.isGM.collectAsState()
|
||||
|
||||
CampaignToolbarContent(
|
||||
title = campaignViewModel.title.collectAsState(initial = ""),
|
||||
networkStatus = campaignViewModel.networkStatus.collectAsState(),
|
||||
title = title,
|
||||
status = status,
|
||||
isGM = isGM,
|
||||
isNetworkMenuOpen = isNetworkMenuOpen,
|
||||
isOverflowMenuOpen = isOverflowMenuOpen,
|
||||
networkMenu = {
|
||||
NetworkPage(
|
||||
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",
|
||||
)
|
||||
}
|
||||
onGM = {
|
||||
windows.navigateToGameMasterWindow()
|
||||
},
|
||||
onNetwork = {
|
||||
isNetworkMenuOpen.value = true
|
||||
},
|
||||
onOverflow = {
|
||||
isOverflowMenuOpen.value = isOverflowMenuOpen.value.not()
|
||||
isOverflowMenuOpen.value = true
|
||||
},
|
||||
onRollHistory = {
|
||||
isOverflowMenuOpen.value = false
|
||||
windows.navigateToRollHistory()
|
||||
},
|
||||
onSettings = {
|
||||
isOverflowMenuOpen.value = false
|
||||
screen.navigateToSettings()
|
||||
},
|
||||
onDismissNetworkMenu = {
|
||||
|
|
@ -120,13 +95,14 @@ fun CampaignToolbar(
|
|||
private fun CampaignToolbarContent(
|
||||
modifier: Modifier = Modifier,
|
||||
title: State<String>,
|
||||
networkStatus: State<NetworkRepository.Status>,
|
||||
status: State<NetworkRepository.Status>,
|
||||
isGM: State<Boolean>,
|
||||
isNetworkMenuOpen: State<Boolean>,
|
||||
isOverflowMenuOpen: State<Boolean>,
|
||||
networkMenu: @Composable () -> Unit,
|
||||
overflowMenu: @Composable () -> Unit,
|
||||
onGM: () -> Unit,
|
||||
onNetwork: () -> Unit,
|
||||
onOverflow: () -> Unit,
|
||||
onRollHistory: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onDismissNetworkMenu: () -> Unit,
|
||||
onDismissOverflowMenu: () -> Unit,
|
||||
|
|
@ -139,38 +115,38 @@ private fun CampaignToolbarContent(
|
|||
)
|
||||
},
|
||||
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(
|
||||
onClick = onNetwork
|
||||
onClick = onNetwork,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
when (networkStatus.value) {
|
||||
when (status.value) {
|
||||
NetworkRepository.Status.CONNECTED -> Res.drawable.ic_wifi_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.DISCONNECTED -> MaterialTheme.lwa.colorScheme.base.error
|
||||
},
|
||||
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(
|
||||
onClick = onOverflow,
|
||||
) {
|
||||
|
|
@ -180,11 +156,50 @@ private fun CampaignToolbarContent(
|
|||
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(
|
||||
offset = remember { DpOffset(x = (-8).dp, y = 8.dp) },
|
||||
expanded = isOverflowMenuOpen.value,
|
||||
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
|
||||
) ?: return
|
||||
_displayDeleteConfirmationDialog.value = CharacterSheetDeleteConfirmationDialogUio(
|
||||
id = preview.id,
|
||||
id = preview.characterSheetId,
|
||||
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,
|
||||
rollRepository.rolls,
|
||||
) { sheets, message ->
|
||||
val name = sheets.firstOrNull { it.id == message.characterSheetId }?.name ?: ""
|
||||
val name = sheets.firstOrNull { it.characterSheetId == message.characterSheetId }?.name ?: ""
|
||||
val roll = RollHistoryItemUio(
|
||||
character = name,
|
||||
skillLabel = message.skillLabel,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class SettingsUseCase {
|
|||
autoHideDelay = 8,
|
||||
autoShowChat = true,
|
||||
autoScrollChat = true,
|
||||
isGM = false,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ class CampaignService(
|
|||
initialValue = factory.convertToJson(campaignFlow.value),
|
||||
)
|
||||
|
||||
fun campaign(): CampaignJson {
|
||||
fun campaign(): Campaign {
|
||||
return campaignFlow.value
|
||||
}
|
||||
|
||||
fun campaignJson(): CampaignJson {
|
||||
return campaignJsonFlow.value
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ import io.ktor.server.response.respond
|
|||
|
||||
fun Engine.getCampaign(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
call.respond(campaignService.campaign())
|
||||
call.respond(campaignService.campaignJson())
|
||||
}
|
||||
}
|
||||
|
|
@ -9,30 +9,43 @@ import io.ktor.server.response.respondText
|
|||
|
||||
fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
|
||||
return {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
||||
val id = if (characterSheetId != null && instanceId != null) {
|
||||
Campaign.CharacterInstance.Id(
|
||||
try {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
?: error("missing character sheet 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,
|
||||
instanceId = instanceId
|
||||
instanceId = 0,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val updated = id?.let { campaignService.addCharacter(it) } ?: false
|
||||
val code = when (updated) {
|
||||
true -> HttpStatusCode.Accepted
|
||||
else -> HttpStatusCode.UnprocessableEntity
|
||||
}
|
||||
call.respondText(
|
||||
text = "$code",
|
||||
status = code,
|
||||
)
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
|
||||
if (campaignService.addCharacter(id).not()) {
|
||||
error("Unexpected error occurred when the character instance was added to the party")
|
||||
}
|
||||
|
||||
call.respondText(
|
||||
text = "Character $characterSheetId successfully added to the party",
|
||||
status = HttpStatusCode.Accepted,
|
||||
)
|
||||
)
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
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 {
|
||||
return {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
val instanceId = call.queryParameters["instanceId"]?.toIntOrNull()
|
||||
try {
|
||||
val characterSheetId = call.queryParameters["characterSheetId"]
|
||||
?: error("missing character sheet id")
|
||||
|
||||
val id = if (characterSheetId != null && instanceId != null) {
|
||||
Campaign.CharacterInstance.Id(
|
||||
val instanceId = campaignService.campaign().npcs.keys
|
||||
.filter { it.characterSheetId == characterSheetId }
|
||||
.reduceOrNull { acc, id ->
|
||||
if (acc.instanceId < id.instanceId) {
|
||||
id
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
}
|
||||
|
||||
val id = Campaign.CharacterInstance.Id(
|
||||
characterSheetId = characterSheetId,
|
||||
instanceId = instanceId
|
||||
instanceId = instanceId?.let { it.instanceId + 1 } ?: 0,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val updated = id?.let { campaignService.addNpc(it) } ?: false
|
||||
if (campaignService.addNpc(id).not()) {
|
||||
error("Unexpected error occurred when the character instance was added to the npcs")
|
||||
}
|
||||
|
||||
val code = when (updated) {
|
||||
true -> HttpStatusCode.Accepted
|
||||
else -> HttpStatusCode.UnprocessableEntity
|
||||
}
|
||||
|
||||
call.respondText(
|
||||
text = "$code",
|
||||
status = code,
|
||||
)
|
||||
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
call.respondText(
|
||||
text = "Character $characterSheetId successfully added to the npcs",
|
||||
status = HttpStatusCode.Accepted,
|
||||
)
|
||||
)
|
||||
webSocket.emit(
|
||||
Message(
|
||||
from = "Server",
|
||||
value = RestSynchronisation.Campaign,
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.run {
|
||||
respondText(
|
||||
text = "${exception.message}",
|
||||
status = HttpStatusCode.UnprocessableEntity,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +87,8 @@ class CharacterSheetJsonFactory(
|
|||
): CharacterPreviewJson {
|
||||
return CharacterPreviewJson(
|
||||
id = sheet.id,
|
||||
portrait = sheet.portrait,
|
||||
thumbnail = sheet.thumbnail,
|
||||
name = sheet.name,
|
||||
level = sheet.level,
|
||||
)
|
||||
|
|
@ -98,8 +100,8 @@ class CharacterSheetJsonFactory(
|
|||
val json = CharacterSheetJsonV1(
|
||||
id = sheet.id,
|
||||
name = sheet.name,
|
||||
thumbnail = sheet.thumbnail,
|
||||
portrait = sheet.portrait,
|
||||
thumbnail = sheet.thumbnail,
|
||||
level = sheet.level,
|
||||
shouldLevelUp = sheet.shouldLevelUp,
|
||||
strength = sheet.strength,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
class CharacterPreviewJson(
|
||||
val id: String,
|
||||
val portrait: String?,
|
||||
val thumbnail: String?,
|
||||
val name: String,
|
||||
val level: Int,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue