diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 48d2236..aff4200 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1,5 +1,7 @@ + La table de Lwa + Confirmer Annuler diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index 7d63c8f..7213333 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -42,17 +42,21 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheet import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow 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.screen.campaign.CampaignScreen +import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost +import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage import com.pixelized.desktop.lwa.ui.theme.LwaTheme import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.app_name import lwacharactersheet.composeapp.generated.resources.network__connect__message import lwacharactersheet.composeapp.generated.resources.network__disconnect__message import org.jetbrains.compose.resources.getString import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.koinInject +import org.koin.compose.viewmodel.koinViewModel val LocalWindowController = compositionLocalOf { error("Local Window Controller is not yet ready") @@ -84,11 +88,7 @@ fun ApplicationScope.App() { ) { Window( onCloseRequest = ::exitApplication, -// state = rememberWindowState( -// width = 320.dp + 64.dp, -// height = 900.dp, -// ), - title = "LwaCharacterSheet", + title = runBlocking { getString(Res.string.app_name) }, onKeyEvent = { event -> keyEventHandlers.reversed().any { it(event) } }, @@ -125,8 +125,7 @@ fun ApplicationScope.App() { } }, content = { -// MainNavHost() - CampaignScreen() + MainScreen() } ) NetworkSnackHandler( @@ -141,6 +140,19 @@ fun ApplicationScope.App() { } } +@Composable +private fun MainScreen( + campaignViewModel: CampaignViewModel = koinViewModel(), + networkViewModel: NetworkViewModel = koinViewModel(), +) { + LaunchedEffect(Unit) { + networkViewModel.connect() + campaignViewModel.init() + } + + MainNavHost() +} + @Composable private fun WindowsHandler( windowController: WindowController, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt index 147317c..cacb771 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -32,7 +32,7 @@ import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.usecase.SettingsUseCase -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import io.ktor.client.HttpClient import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt index dec6ca7..8c6804e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt @@ -5,7 +5,7 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.model.alteration.Alteration import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.protocol.websocket.Message import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation import kotlinx.coroutines.CoroutineScope @@ -90,7 +90,7 @@ class AlterationStore( when (val payload = message.value) { is RestSynchronisation.ToggleActiveAlteration -> { setActiveAlteration( - characterInstanceId = campaignJsonFactory.convertFromV1( + characterInstanceId = campaignJsonFactory.characterInstanceIdFromJson( characterInstanceIdJson = payload.characterId, ), alterationId = payload.alterationId, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt index b337c1f..d924209 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt @@ -3,7 +3,7 @@ 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.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.character import com.pixelized.shared.lwa.model.campaign.npc import com.pixelized.shared.lwa.protocol.websocket.Message @@ -63,7 +63,7 @@ class CampaignStore( when (payload) { is CampaignMessage.UpdateCharacteristic -> updateCharacteristic( characterInstanceId = instanceId, - characteristic = factory.convertFromV1(characteristicJson = payload.characteristic), + characteristic = factory.convertFromJson(json = payload.characteristic), value = payload.value, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt index 3836d53..8b2b816 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/characteristic/CharacterDetailCharacteristicDialogViewModel.kt @@ -11,7 +11,7 @@ import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory import com.pixelized.shared.lwa.model.alteration.FieldAlteration import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign.CharacterInstance.Characteristic -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/circle/MasteryShape.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/circle/MasteryShape.kt index 28152aa..c743ae6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/circle/MasteryShape.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/circle/MasteryShape.kt @@ -61,7 +61,7 @@ fun MasteryShape( shape = shape, color = color, borderWidth = borderWidth, - size = size - borderWidth * 2, + size = size - borderWidth * 3, multiplier = multiplier - 1, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt index 4fed814..0103989 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt @@ -2,7 +2,6 @@ package com.pixelized.desktop.lwa.ui.navigation.screen import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.compositionLocalOf import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -10,8 +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.composableMainPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableNetworkPage -import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel -import org.koin.compose.viewmodel.koinViewModel +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableOldMainPage val LocalScreenController = compositionLocalOf { error("MainNavHost controller is not yet ready") @@ -21,12 +19,7 @@ val LocalScreenController = compositionLocalOf { fun MainNavHost( controller: NavHostController = rememberNavController(), startDestination: String = MainDestination.navigationRoute(), - networkViewModel: NetworkViewModel = koinViewModel(), ) { - LaunchedEffect(Unit) { - networkViewModel.connect() - } - CompositionLocalProvider( LocalScreenController provides controller, ) { @@ -35,6 +28,7 @@ fun MainNavHost( startDestination = startDestination, ) { composableMainPage() + composableOldMainPage() composableNetworkPage() } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt index 67f194f..dc0dd26 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt @@ -3,7 +3,7 @@ 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.MainPage +import com.pixelized.desktop.lwa.ui.screen.campaign.MainPage object MainDestination { private const val ROUTE = "main" diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/OldMainDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/OldMainDestination.kt new file mode 100644 index 0000000..307eb2e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/OldMainDestination.kt @@ -0,0 +1,26 @@ +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 + +object OldMainDestination { + private const val ROUTE = "old_main" + + fun baseRoute() = ROUTE + fun navigationRoute() = ROUTE +} + +fun NavGraphBuilder.composableOldMainPage() { + composable( + route = OldMainDestination.baseRoute(), + ) { + OldMainPage() + } +} + +fun NavHostController.navigateToOldMainPage() { + val route = OldMainDestination.navigationRoute() + navigate(route = route) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt index 1879ee3..430814d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt @@ -13,17 +13,17 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key @@ -39,31 +39,30 @@ import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentControlle import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialog import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler +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.screen.campaign.player.detail.CharacterDetailPanel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon +import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.Toolbar 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.roll.RollPage import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel import kotlinx.coroutines.launch +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.ic_table_24dp +import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel @Composable -fun CampaignScreen( +fun MainPage( campaignViewModel: CampaignViewModel = koinViewModel(), characterDetailViewModel: CharacterDetailViewModel = koinViewModel(), characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(), dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(), - networkViewModel: NetworkViewModel = koinViewModel(), rollViewModel: RollViewModel = koinViewModel(), ) { - LaunchedEffect(Unit) { - networkViewModel.connect() - campaignViewModel.init() - } - KeyHandler { when { it.type == KeyEventType.KeyUp && it.key == Key.Escape -> { @@ -75,6 +74,7 @@ fun CampaignScreen( } } + val screen = LocalScreenController.current val scope = rememberCoroutineScope() val blurController = rememberBlurContentController() @@ -88,14 +88,21 @@ fun CampaignScreen( CampaignScreenLayout( modifier = Modifier.fillMaxSize(), top = { - Surface( - modifier = Modifier -// .height(32.dp) - .fillMaxWidth(), - elevation = 1.dp, - ) { - - } + Toolbar( + title = campaignViewModel.title.collectAsState(initial = "").value, + actions = { + IconButton( + onClick = { + screen.navigateToOldMainPage() + }, + ) { + Icon( + painter = painterResource(Res.drawable.ic_table_24dp), + contentDescription = null, + ) + } + }, + ) }, bottom = { Surface( @@ -123,8 +130,7 @@ fun CampaignScreen( modifier = Modifier .width(width = 128.dp * 4) .fillMaxHeight() - .padding(all = 8.dp) - .clip(shape = remember { RoundedCornerShape(16.dp) }), + .padding(all = 8.dp), blurController = blurController, detailViewModel = characterDetailViewModel, rollViewModel = rollViewModel, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt index 2cc12f6..13cca63 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt @@ -5,7 +5,9 @@ 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class CampaignViewModel( @@ -14,8 +16,10 @@ class CampaignViewModel( private val campaignRepository: CampaignRepository, ) : ViewModel() { - suspend fun init() { + val title: Flow = campaignRepository.campaignFlow + .map { it.scene.name } + fun init() { viewModelScope.launch { campaignRepository.campaignFlow.collectLatest { it.characters.keys.forEach { id -> diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt index 8afbc4c..4a64c17 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -22,8 +23,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel @@ -178,6 +181,7 @@ fun CharacterDetailAnimatedPanel( @Composable fun CharacterDetailContent( modifier: Modifier = Modifier, + shape: Shape = remember { RoundedCornerShape(16.dp) }, header: State, sheet: State, onDismissRequest: () -> Unit, @@ -191,6 +195,7 @@ fun CharacterDetailContent( ) { Surface( modifier = modifier.fillMaxSize(), + shape = shape, color = MaterialTheme.lwa.colorScheme.elevated.base1dp, ) { Column { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt index a51d5c7..a4207b2 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheet.kt @@ -59,7 +59,7 @@ fun CharacterDetailSheet( } Column( modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(space = 16.dp) + verticalArrangement = Arrangement.spacedBy(space = 8.dp) ) { DecoratedBox( modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheetAction.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheetAction.kt index 2d50df7..1e19d97 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheetAction.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/sheet/CharacterDetailSheetAction.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Icon @@ -15,6 +16,7 @@ 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.ui.composable.circle.MasteryShape import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio import com.pixelized.desktop.lwa.ui.screen.roll.RollActionUio import lwacharactersheet.composeapp.generated.resources.Res @@ -32,7 +34,12 @@ data class CharacterDetailSheetActionUio( @Composable fun CharacterDetailSheetAction( modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues(start = 28.dp, end = 9.dp, top = 6.dp, bottom = 6.dp), + paddingValues: PaddingValues = PaddingValues( + start = 10.dp, + end = 9.dp, + top = 6.dp, + bottom = 6.dp + ), action: CharacterDetailSheetActionUio, onClick: (CharacterDetailSheetActionUio) -> Unit, ) { @@ -41,9 +48,13 @@ fun CharacterDetailSheetAction( .clickable(onClick = { onClick(action) }) .padding(paddingValues = paddingValues) .then(other = modifier), - horizontalArrangement = Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), verticalAlignment = Alignment.CenterVertically, ) { + MasteryShape( + modifier = Modifier.padding(top = 4.dp, end = 2.dp), + multiplier = 2, + ) Text( modifier = Modifier.weight(1f), style = MaterialTheme.typography.body1, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt index c1be104..49437d6 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortrait.kt @@ -6,13 +6,15 @@ 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.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable @@ -21,13 +23,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage -import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.shared.lwa.model.campaign.Campaign import lwacharactersheet.composeapp.generated.resources.Res @@ -55,11 +57,11 @@ fun PlayerPortrait( ) { val colorScheme = MaterialTheme.lwa.colorScheme - Box ( + Box( modifier = modifier .size(size = size) .clip(shape = remember { RoundedCornerShape(8.dp) }) - .background(color = MaterialTheme.lwa.colorScheme.elevated.base1dp) + .background(color = colorScheme.elevated.base1dp) .clickable { onCharacter(character.id) }, ) { AsyncImage( @@ -74,50 +76,54 @@ fun PlayerPortrait( modifier = Modifier .fillMaxSize() .drawWithContent { - drawRect( - brush = Brush.verticalGradient( - listOf( - colorScheme.elevated.base1dp.copy(alpha = 0.0f), - colorScheme.elevated.base1dp.copy(alpha = 0.0f), - colorScheme.elevated.base1dp.copy(alpha = 0.0f), - colorScheme.elevated.base1dp.copy(alpha = 0.5f), - colorScheme.elevated.base1dp.copy(alpha = 0.8f), - ) - ) - ) + drawRect(brush = colorScheme.portraitBackgroundBrush) drawContent() } .padding(vertical = 2.dp, horizontal = 4.dp), verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom), ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(space = 2.dp), - ) { + Row { Icon( - modifier = Modifier.size(12.dp), + modifier = Modifier.size(12.dp).offset(y = 3.dp), painter = painterResource(Res.drawable.ic_heart_24dp), contentDescription = null ) + Spacer( + modifier = Modifier.width(width = 2.dp), + ) Text( - modifier = Modifier.padding(bottom = 2.dp), + modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.caption, - text = "${character.hp}/${character.maxHp}", + fontWeight = FontWeight.Bold, + text = "${character.hp}", + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Light, + text = "/${character.maxHp}", ) } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(space = 2.dp), - ) { + Row { Icon( - modifier = Modifier.size(12.dp), + modifier = Modifier.size(12.dp).offset(y = 2.dp), painter = painterResource(Res.drawable.ic_water_drop_24dp), contentDescription = null ) + Spacer( + modifier = Modifier.width(width = 2.dp), + ) Text( - modifier = Modifier.padding(bottom = 2.dp), + modifier = Modifier.alignByBaseline(), style = MaterialTheme.typography.caption, - text = "${character.pp}/${character.maxPp}", + fontWeight = FontWeight.Bold, + text = "${character.pp}", + ) + Text( + modifier = Modifier.alignByBaseline(), + fontWeight = FontWeight.Light, + style = MaterialTheme.typography.caption, + text = "/${character.maxPp}", ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt new file mode 100644 index 0000000..650d645 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt @@ -0,0 +1,52 @@ +package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.LocalContentColor +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.theme.lwa + +@Composable +fun Toolbar( + modifier: Modifier = Modifier, + title: String, + actions: @Composable RowScope.() -> Unit = { }, +) { + Surface( + modifier = modifier, + elevation = 1.dp, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .minimumInteractiveComponentSize(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.h6, + text = title, + ) + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.lwa.colorScheme.base.primary, + ) { + Row( + modifier = Modifier.padding(end = 8.dp), + content = actions, + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt index e87f43b..85d8969 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt @@ -19,7 +19,6 @@ 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 @@ -27,11 +26,11 @@ 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 @@ -39,6 +38,7 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__cr 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 @@ -56,12 +56,13 @@ data class CharacterUio( ) @Composable -fun MainPage( +fun OldMainPage( viewModel: MainPageViewModel = koinViewModel(), ) { val window = LocalWindowController.current val screen = LocalScreenController.current val characters = viewModel.characters.collectAsState() + val npcs = viewModel.npcs.collectAsState() Surface( modifier = Modifier.fillMaxSize(), @@ -75,6 +76,7 @@ fun MainPage( ) { MainPageContent( characters = characters, + npcs = npcs, enableRollHistory = viewModel.enableRollHistory, onCharacter = { window.navigateToCharacterSheet( @@ -97,6 +99,9 @@ fun MainPage( onNetwork = { screen.navigateToNetwork() }, + onMainPage = { + screen.navigateToMainPage() + } ) } } @@ -106,28 +111,55 @@ fun MainPage( fun MainPageContent( modifier: Modifier = Modifier, characters: State>, + npcs: State>, enableRollHistory: State, onCharacter: (CharacterUio) -> Unit, onCreateCharacter: () -> Unit, onRollHistory: () -> Unit, onOpenSaveDirectory: () -> Unit, onNetwork: () -> Unit, + onMainPage: () -> Unit, ) { Column( modifier = modifier, - verticalArrangement = Arrangement.spacedBy(4.dp), ) { - 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()) { + 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, + ) + } + } } } @@ -217,5 +249,25 @@ fun MainPageContent( ) } } + TextButton( + onClick = onMainPage, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + 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", + ) + } + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt index 345cb63..5b6b7da 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPageViewModel.kt @@ -15,8 +15,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn class MainPageViewModel( @@ -27,21 +28,48 @@ class MainPageViewModel( @OptIn(ExperimentalCoroutinesApi::class) val characters: StateFlow> = campaignRepository.campaignFlow - .flatMapLatest { campaign -> + .flatMapMerge { campaign -> combine( - campaign.characters.map { entry -> - characterSheetRepository.characterDetailFlow(characterId = entry.key.characterSheetId) - .mapNotNull { sheet -> - sheet?.let { - CharacterUio( - id = entry.key, - name = it.name, - ) + campaign.characters + .map { entry -> + characterSheetRepository + .characterDetailFlow(characterId = entry.key.characterSheetId) + .map transform@{ sheet -> + if (sheet == null) return@transform null + CharacterUio(id = entry.key, name = sheet.name) } - } - } - ) { - it.asList() + } + .ifEmpty { + listOf(flowOf(null)) + } + ) { data -> + data.mapNotNull { it } + } + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = emptyList(), + ) + + @OptIn(ExperimentalCoroutinesApi::class) + val npcs: StateFlow> = campaignRepository.campaignFlow + .flatMapMerge { campaign -> + combine( + campaign.npcs + .map { entry -> + characterSheetRepository + .characterDetailFlow(characterId = 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( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt index da0bd36..315271d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt @@ -59,12 +59,14 @@ class RollViewModel( private val _displayOverlay = mutableStateOf(false) val displayOverlay: State get() = _displayOverlay + @Deprecated(message = "@See prepareRoll(RollActionUio)") fun prepareRoll( sheet: CharacterSheetPageUio, characteristic: CharacterSheetPageUio.Characteristic, ) { - val diminished = - 0 // TODO characterSheetRepository.characterDiminishedFlow(id = sheet.id).value + // TODO characterSheetRepository.characterDiminishedFlow(id = sheet.id).value + val diminished = 0 + prepareRoll( characterSheetId = sheet.id, label = characteristic.label, @@ -73,6 +75,7 @@ class RollViewModel( ) } + @Deprecated(message = "@See prepareRoll(RollActionUio)") fun prepareRoll( sheet: CharacterSheetPageUio, node: CharacterSheetPageUio.Node, @@ -85,6 +88,7 @@ class RollViewModel( ) } + @Deprecated(message = "@See prepareRoll(RollActionUio)") fun prepareRoll( sheet: CharacterSheetPageUio, roll: CharacterSheetPageUio.Roll, @@ -115,8 +119,8 @@ class RollViewModel( this.sheet = runBlocking { rollRotation.snapTo(0f) rollScale.snapTo(1f) - characterSheetRepository.characterDetail(characterSheetId = characterSheetId)!! - } + characterSheetRepository.characterDetail(characterSheetId = characterSheetId) + } ?: return this.rollAction = rollAction this.rollSuccessValue = rollSuccessValue diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt index ecca34a..22a4a39 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt @@ -5,6 +5,7 @@ import androidx.compose.material.darkColors import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.unit.Dp @@ -15,6 +16,7 @@ import kotlin.math.ln data class LwaColorTheme( val base: Colors, val elevated: Elevated, + val portraitBackgroundBrush: Brush, ) { @Stable data class Elevated( @@ -39,9 +41,19 @@ fun darkLwaColorTheme( elevation = 2.dp, ), ), + portraitBackgroundBrush: Brush = Brush.verticalGradient( + listOf( + elevated.base1dp.copy(alpha = 0.0f), + elevated.base1dp.copy(alpha = 0.0f), + elevated.base1dp.copy(alpha = 0.0f), + elevated.base1dp.copy(alpha = 0.5f), + elevated.base1dp.copy(alpha = 0.8f), + ) + ) ): LwaColorTheme = LwaColorTheme( base = base, elevated = elevated, + portraitBackgroundBrush = portraitBackgroundBrush, ) @ReadOnlyComposable diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt index a411720..72bb5e4 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt @@ -2,7 +2,7 @@ package com.pixelized.server.lwa.model.alteration import com.pixelized.shared.lwa.model.alteration.AlterationJson import com.pixelized.shared.lwa.model.campaign.Campaign -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -19,7 +19,7 @@ class AlterationService( private val actives = store.activeFlow() .map { data -> data.mapKeys { it: Map.Entry> -> - campaignJsonFactory.convertFromV1(characterInstanceIdJson = it.key) + campaignJsonFactory.characterInstanceIdFromJson(characterInstanceIdJson = it.key) } }.stateIn( scope = scope, diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt index 4caa8b2..34bb1a2 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt @@ -3,14 +3,13 @@ package com.pixelized.server.lwa.model.alteration import com.pixelized.shared.lwa.alterationsPath import com.pixelized.shared.lwa.model.alteration.AlterationJson import com.pixelized.shared.lwa.model.campaign.Campaign -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt index 50cb5ae..f0cdf5a 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt @@ -2,8 +2,8 @@ package com.pixelized.server.lwa.model.campaign import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.CampaignJson -import com.pixelized.shared.lwa.model.campaign.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.usecase.CampaignUseCase import kotlinx.coroutines.CoroutineScope @@ -47,10 +47,14 @@ class CampaignService( // update the corresponding character characters[characterInstanceId] = campaign.character(id = characterInstanceId) // save the campaign to the disk + update the flow. - store.save( - campaign = campaign.copy(characters = characters) - ) - return true + return try { + store.save( + campaign = campaign.copy(characters = characters) + ) + true + } catch (exception: Exception) { + false + } } suspend fun removeCharacter( @@ -63,10 +67,14 @@ class CampaignService( // update the corresponding character characters.remove(characterInstanceId) // save the campaign to the disk + update the flow. - store.save( - campaign = campaign.copy(characters = characters) - ) - return true + return try { + store.save( + campaign = campaign.copy(characters = characters) + ) + true + } catch (exception: Exception) { + false + } } suspend fun addNpc( @@ -79,10 +87,14 @@ class CampaignService( // update the corresponding character npcs[npcInstanceId] = campaign.npc(id = npcInstanceId) // save the campaign to the disk + update the flow. - store.save( - campaign = campaign.copy(npcs = npcs) - ) - return true + return try { + store.save( + campaign = campaign.copy(npcs = npcs) + ) + true + } catch (exception: Exception) { + false + } } suspend fun removeNpc( @@ -95,10 +107,28 @@ class CampaignService( // update the corresponding character npcs.remove(npcInstanceId) // save the campaign to the disk + update the flow. - store.save( - campaign = campaign.copy(npcs = npcs) - ) - return true + return try { + store.save( + campaign = campaign.copy(npcs = npcs) + ) + true + } catch (exception: Exception) { + false + } + } + + suspend fun setScene( + scene: Campaign.Scene, + ): Boolean { + // save the campaign to the disk + update the flow. + return try { + store.save( + campaign.copy(scene = scene) + ) + true + } catch (exception: Exception) { + false + } } // Data manipulation through WebSocket. diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index 297bc70..e554115 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.model.campaign import com.pixelized.shared.lwa.campaignPath import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.CampaignJson -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt index 358c028..a9efa08 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt @@ -4,7 +4,7 @@ import com.pixelized.server.lwa.model.alteration.AlterationService import com.pixelized.server.lwa.model.campaign.CampaignService import com.pixelized.server.lwa.model.character.CharacterSheetService import com.pixelized.shared.lwa.model.campaign.Campaign -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory 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.RestSynchronisation @@ -33,7 +33,7 @@ class Engine( when (data) { is CampaignMessage.UpdateCharacteristic -> campaignService.updateCharacteristic( characterInstanceId = instanceId, - characteristic = campaignJsonFactory.convertFromV1(characteristicJson = data.characteristic), + characteristic = campaignJsonFactory.convertFromJson(json = data.characteristic), value = data.value, ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt index a894923..c7f284e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt @@ -11,6 +11,7 @@ import com.pixelized.server.lwa.server.rest.campaign.deleteCampaignNpc import com.pixelized.server.lwa.server.rest.campaign.getCampaign import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter import com.pixelized.server.lwa.server.rest.campaign.putCampaignNpc +import com.pixelized.server.lwa.server.rest.campaign.putCampaignScene import com.pixelized.server.lwa.server.rest.character.deleteCharacter import com.pixelized.server.lwa.server.rest.character.getCharacter import com.pixelized.server.lwa.server.rest.character.getCharacters @@ -146,6 +147,10 @@ class LocalServer { body = engine.deleteCampaignNpc(), ) } + put( + path = "/scene", + body = engine.putCampaignScene(), + ) } route(path = "/alterations") { get( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt new file mode 100644 index 0000000..146f9af --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt @@ -0,0 +1,38 @@ +package com.pixelized.server.lwa.server.rest.campaign + +import com.pixelized.server.lwa.server.Engine +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1 +import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson +import com.pixelized.shared.lwa.protocol.websocket.Message +import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation +import io.ktor.http.HttpStatusCode +import io.ktor.server.request.receive +import io.ktor.server.response.respondText + +fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { + return { + val form = call.receive() + + val scene = campaignJsonFactory.convertFromJson(json = form) + + val updated = campaignService.setScene(scene = scene) + + val code = when (updated) { + true -> HttpStatusCode.Accepted + else -> HttpStatusCode.UnprocessableEntity + } + + call.respondText( + text = "$code", + status = code, + ) + + webSocket.emit( + Message( + from = "Server", + value = RestSynchronisation.Campaign, + ) + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt index 97c75da..4a94907 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt @@ -2,7 +2,8 @@ package com.pixelized.shared.lwa import com.pixelized.shared.lwa.model.AlteredCharacterSheetFactory import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory -import com.pixelized.shared.lwa.model.campaign.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory +import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonV1Factory import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory import com.pixelized.shared.lwa.parser.dice.DiceParser import com.pixelized.shared.lwa.parser.expression.ExpressionParser @@ -38,6 +39,7 @@ val factoryDependencies get() = module { factoryOf(::CharacterSheetJsonFactory) factoryOf(::CampaignJsonFactory) + factoryOf(::CampaignJsonV1Factory) factoryOf(::AlteredCharacterSheetFactory) factoryOf(::AlterationJsonFactory) } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt index e95aef0..cfcc8ab 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/Campaign.kt @@ -3,6 +3,7 @@ package com.pixelized.shared.lwa.model.campaign data class Campaign( val characters: Map, val npcs: Map, + val scene: Scene, ) { data class CharacterInstance( val characteristic: Map, @@ -26,10 +27,21 @@ data class Campaign( } } + data class Scene( + val name: String, + ) { + companion object { + fun empty() = Scene( + name = "", + ) + } + } + companion object { val EMPTY = Campaign( characters = emptyMap(), npcs = emptyMap(), + scene = Scene.empty(), ) } } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt index a99af46..0fe01ce 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJson.kt @@ -3,4 +3,15 @@ package com.pixelized.shared.lwa.model.campaign import kotlinx.serialization.Serializable @Serializable -sealed interface CampaignJson \ No newline at end of file +sealed interface CampaignJson { + + @Serializable + sealed interface CharacterInstanceJson { + + @Serializable + sealed interface CharacteristicJson + } + + @Serializable + sealed interface SceneJson +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt deleted file mode 100644 index d6b9f81..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonFactory.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.pixelized.shared.lwa.model.campaign - - -class CampaignJsonFactory { - fun convertFromJson( - json: CampaignJson, - ): Campaign { - return when (json) { - is CampaignJsonV1 -> convertFromV1(campaignJson = json) - } - } - - private fun convertFromV1( - campaignJson: CampaignJsonV1, - ): Campaign { - return Campaign( - characters = campaignJson.characters - .map { - convertFromV1(characterInstanceIdJson = it.key) to convertFromV1( - characterInstanceJson = it.value - ) - } - .toMap(), - npcs = campaignJson.npcs - .map { - convertFromV1(characterInstanceIdJson = it.key) to convertFromV1( - characterInstanceJson = it.value - ) - } - .toMap(), - ) - } - - fun convertFromV1( - characterInstanceIdJson: String, - ): Campaign.CharacterInstance.Id { - return Campaign.CharacterInstance.Id( - characterSheetId = characterInstanceIdJson.drop(4), // drop first 3 number then the - - instanceId = characterInstanceIdJson.take(3).toIntOrNull() ?: 0, - ) - } - - fun convertFromV1( - characterInstanceJson: CampaignJsonV1.CharacterInstanceJson, - ): Campaign.CharacterInstance { - return Campaign.CharacterInstance( - characteristic = characterInstanceJson.characteristic - .map { char -> convertFromV1(characteristicJson = char.key) to char.value } - .toMap(), - diminished = characterInstanceJson.diminished ?: 0, - ) - } - - fun convertFromV1( - characteristicJson: CampaignJsonV1.CharacterInstanceJson.Characteristic, - ): Campaign.CharacterInstance.Characteristic { - return when (characteristicJson) { - CampaignJsonV1.CharacterInstanceJson.Characteristic.Damage -> Campaign.CharacterInstance.Characteristic.Damage - CampaignJsonV1.CharacterInstanceJson.Characteristic.Power -> Campaign.CharacterInstance.Characteristic.Power - } - } - - fun convertToJson( - data: Campaign, - ): CampaignJson { - return CampaignJsonV1( - characters = data.characters - .map { convertToJson(id = it.key) to convertToJson(data = it.value) } - .toMap(), - npcs = data.npcs - .map { convertToJson(id = it.key) to convertToJson(data = it.value) } - .toMap(), - ) - } - - fun convertToJson( - id: Campaign.CharacterInstance.Id, - ): String { - return "${String.format("%03d", id.instanceId)}-${id.characterSheetId}" - } - - fun convertToJson( - data: Campaign.CharacterInstance, - ): CampaignJsonV1.CharacterInstanceJson { - return CampaignJsonV1.CharacterInstanceJson( - characteristic = data.characteristic - .map { char -> convertToJson(characteristic = char.key) to char.value } - .toMap(), - diminished = data.diminished, - ) - } - - fun convertToJson( - characteristic: Campaign.CharacterInstance.Characteristic, - ): CampaignJsonV1.CharacterInstanceJson.Characteristic { - return when (characteristic) { - Campaign.CharacterInstance.Characteristic.Damage -> CampaignJsonV1.CharacterInstanceJson.Characteristic.Damage - Campaign.CharacterInstance.Characteristic.Power -> CampaignJsonV1.CharacterInstanceJson.Characteristic.Power - } - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt index 64deba9..67bb6f7 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/CampaignJsonV1.kt @@ -4,18 +4,24 @@ import kotlinx.serialization.Serializable @Serializable data class CampaignJsonV1( - val characters: Map, - val npcs: Map, + val characters: Map, + val npcs: Map, + val scene: SceneJsonV1?, ) : CampaignJson { @Serializable - data class CharacterInstanceJson( - val characteristic: Map, + data class CharacterInstanceJsonV1( + val characteristic: Map, val diminished: Int?, - ) { - enum class Characteristic { + ) : CampaignJson.CharacterInstanceJson { + enum class CharacteristicV1 : CampaignJson.CharacterInstanceJson.CharacteristicJson { Damage, Power, } } -} \ No newline at end of file + + @Serializable + data class SceneJsonV1( + val name: String, + ) : CampaignJson.SceneJson +} diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/factory/CampaignJsonFactory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/factory/CampaignJsonFactory.kt new file mode 100644 index 0000000..a50027d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/factory/CampaignJsonFactory.kt @@ -0,0 +1,98 @@ +package com.pixelized.shared.lwa.model.campaign.factory + +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.CampaignJson +import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1 + +class CampaignJsonFactory( + private val v1: CampaignJsonV1Factory, +) { + fun characterInstanceIdFromJson( + characterInstanceIdJson: String, + ): Campaign.CharacterInstance.Id { + return v1.convertFromV1(characterInstanceIdJson) + } + + fun convertFromJson( + json: CampaignJson, + ): Campaign { + return when (json) { + is CampaignJsonV1 -> v1.convertFromV1(campaignJson = json) + } + } + + fun convertFromJson( + json: CampaignJson.CharacterInstanceJson, + ): Campaign.CharacterInstance { + return when (json) { + is CampaignJsonV1.CharacterInstanceJsonV1 -> v1.convertFromV1(characterInstanceJson = json) + } + } + + fun convertFromJson( + json: CampaignJson.CharacterInstanceJson.CharacteristicJson, + ): Campaign.CharacterInstance.Characteristic { + return when (json) { + CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage -> Campaign.CharacterInstance.Characteristic.Damage + CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power -> Campaign.CharacterInstance.Characteristic.Power + } + } + + fun convertFromJson( + json: CampaignJson.SceneJson, + ): Campaign.Scene { + return when (json) { + is CampaignJsonV1.SceneJsonV1 -> v1.convertFromV1(sceneJson = json) + } + } + + fun convertToJson( + data: Campaign, + ): CampaignJson { + return CampaignJsonV1( + characters = data.characters + .map { convertToJson(id = it.key) to convertToJson(data = it.value) } + .toMap(), + npcs = data.npcs + .map { convertToJson(id = it.key) to convertToJson(data = it.value) } + .toMap(), + scene = CampaignJsonV1.SceneJsonV1( + name = data.scene.name + ) + ) + } + + fun convertToJson( + id: Campaign.CharacterInstance.Id, + ): String { + return "${String.format("%03d", id.instanceId)}-${id.characterSheetId}" + } + + fun convertToJson( + data: Campaign.CharacterInstance, + ): CampaignJsonV1.CharacterInstanceJsonV1 { + return CampaignJsonV1.CharacterInstanceJsonV1( + characteristic = data.characteristic + .map { char -> convertToJson(characteristic = char.key) to char.value } + .toMap(), + diminished = data.diminished, + ) + } + + fun convertToJson( + characteristic: Campaign.CharacterInstance.Characteristic, + ): CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1 { + return when (characteristic) { + Campaign.CharacterInstance.Characteristic.Damage -> CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage + Campaign.CharacterInstance.Characteristic.Power -> CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power + } + } + + fun convertToJson( + scene: Campaign.Scene, + ): CampaignJsonV1.SceneJsonV1 { + return CampaignJsonV1.SceneJsonV1( + name = scene.name, + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/factory/CampaignJsonV1Factory.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/factory/CampaignJsonV1Factory.kt new file mode 100644 index 0000000..8a7797a --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/model/campaign/factory/CampaignJsonV1Factory.kt @@ -0,0 +1,68 @@ +package com.pixelized.shared.lwa.model.campaign.factory + +import com.pixelized.shared.lwa.model.campaign.Campaign +import com.pixelized.shared.lwa.model.campaign.CampaignJsonV1 + +class CampaignJsonV1Factory { + + fun convertFromV1( + campaignJson: CampaignJsonV1, + ): Campaign { + return Campaign( + characters = campaignJson.characters + .map { + val key = convertFromV1(characterInstanceIdJson = it.key) + val instance = convertFromV1(characterInstanceJson = it.value) + key to instance + } + .toMap(), + npcs = campaignJson.npcs + .map { + val key = convertFromV1(characterInstanceIdJson = it.key) + val instance = convertFromV1(characterInstanceJson = it.value) + key to instance + } + .toMap(), + scene = campaignJson.scene + ?.let { convertFromV1(it) } + ?: Campaign.Scene.empty(), + ) + } + + fun convertFromV1( + characterInstanceIdJson: String, + ): Campaign.CharacterInstance.Id { + return Campaign.CharacterInstance.Id( + characterSheetId = characterInstanceIdJson.drop(4), // drop first 3 number then the - + instanceId = characterInstanceIdJson.take(3).toIntOrNull() ?: 0, + ) + } + + fun convertFromV1( + characterInstanceJson: CampaignJsonV1.CharacterInstanceJsonV1, + ): Campaign.CharacterInstance { + return Campaign.CharacterInstance( + characteristic = characterInstanceJson.characteristic + .mapKeys { convertFromV1(characteristicJson = it.key) } + .toMap(), + diminished = characterInstanceJson.diminished ?: 0, + ) + } + + fun convertFromV1( + characteristicJson: CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1, + ): Campaign.CharacterInstance.Characteristic { + return when (characteristicJson) { + CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Damage -> Campaign.CharacterInstance.Characteristic.Damage + CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1.Power -> Campaign.CharacterInstance.Characteristic.Power + } + } + + fun convertFromV1( + sceneJson: CampaignJsonV1.SceneJsonV1, + ): Campaign.Scene { + return Campaign.Scene( + name = sceneJson.name + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt index db3090d..580ecaa 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/CampaignMessage.kt @@ -12,7 +12,7 @@ sealed interface CampaignMessage : MessagePayload { data class UpdateCharacteristic( override val characterSheetId: String, override val instanceId: Int, - val characteristic: CampaignJsonV1.CharacterInstanceJson.Characteristic, + val characteristic: CampaignJsonV1.CharacterInstanceJsonV1.CharacteristicV1, val value: Int, ) : CampaignMessage diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt index 2f490d3..54b143a 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/ExpressionUseCase.kt @@ -60,13 +60,16 @@ class ExpressionUseCase( alterations: Map>, expression: String, ): Int { - return expressionParser.parse(input = expression)?.let { + print("Roll::$expression::") + val roll = expressionParser.parse(input = expression)?.let { computeExpression( sheet = sheet, alterations = alterations, expression = it, ) } ?: 0 + println("::$roll") + return roll } fun computeExpression( diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/RollUseCase.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/RollUseCase.kt index f66ffd8..f7e1bf2 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/RollUseCase.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/usecase/RollUseCase.kt @@ -59,7 +59,7 @@ class RollUseCase { if (quantity > 1 && left != 1) print(",") } }.also { - println("}") + print("}") } }