From 171651c5e81d0efd184ffb3a5ab526a6185b1cb7 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 18 Oct 2025 00:50:07 +0200 Subject: [PATCH 1/3] Add scene title change from admin panel. --- .../drawable/ic_edit_note_24dp.xml | 10 ++ .../composeResources/values/strings.xml | 1 + .../desktop/lwa/network/LwaClient.kt | 4 + .../desktop/lwa/network/LwaClientImpl.kt | 10 ++ .../repository/campaign/CampaignRepository.kt | 6 ++ .../lwa/repository/campaign/CampaignStore.kt | 11 +++ .../common/CharacterRibbonAlteration.kt | 15 +-- .../screen/gamemaster/action/GMActionPage.kt | 24 ++++- .../gamemaster/action/GMActionUseCase.kt | 10 ++ .../gamemaster/action/GMActionViewModel.kt | 95 +++++++++++++----- .../screen/gamemaster/action/GMEditDialog.kt | 97 +++++++++++++++++++ .../utils/extention/LayoutDirection+Invert.kt | 2 + .../campaign/factory/CampaignJsonFactory.kt | 8 ++ 13 files changed, 257 insertions(+), 36 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_edit_note_24dp.xml create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMEditDialog.kt diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_edit_note_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_edit_note_24dp.xml new file mode 100644 index 0000000..6bdce88 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_edit_note_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index dcb2884..393fff4 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -325,6 +325,7 @@ Édition de personnage Synchronisation du serveur Demander au serveur d'invalider son cache + Titre de la scène Soigner les personnages joueurs Cette action réinitialisera les points de vie, de pouvoir et d'état diminué de chaque personnage joueur présent dans le groupe. Cacher le groupe de personnages joueur diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt index 84d8ceb..b595475 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt @@ -33,6 +33,10 @@ interface LwaClient { suspend fun getCampaign(): APIResponse + suspend fun putCampaignScene( + scene: CampaignJson.SceneJson, + ): APIResponse + suspend fun putCampaignCharacter( characterSheetId: String, ): APIResponse diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt index 28f3b41..44b73cc 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt @@ -58,6 +58,16 @@ class LwaClientImpl( .get("$root/campaign") .body() + @Throws + override suspend fun putCampaignScene( + scene: CampaignJson.SceneJson, + ): APIResponse = client + .put("$root/campaign/scene") { + contentType(ContentType.Application.Json) + setBody(scene) + } + .body>() + @Throws override suspend fun putCampaignCharacter( characterSheetId: String, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt index b61a3f2..d242d9c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt @@ -14,6 +14,12 @@ class CampaignRepository( store.updateCampaignFlow() } + suspend fun updateSceneTitle( + title: String, + ) { + store.changeSceneTitle(title = title) + } + @Throws suspend fun addCharacter( characterSheetId: String, 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 402211f..3693f8a 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 @@ -40,6 +40,17 @@ class CampaignStore( } } + @Throws + suspend fun changeSceneTitle( + title: String, + ) { + val scene = factory.createScene(title = title) + val request = client.putCampaignScene(scene = scene) + if (request.success.not()) { + LwaClient.error(error = request) + } + } + @Throws suspend fun addCharacter( characterSheetId: String, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt index 5da895b..000d761 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import coil3.PlatformContext @@ -30,10 +29,10 @@ import coil3.request.ImageRequest import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipLayout import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio import com.pixelized.desktop.lwa.ui.theme.lwa +import com.pixelized.desktop.lwa.utils.extention.invert @Stable data class CharacterRibbonAlterationUio( - val icon: String, val tooltips: BasicTooltipUio?, ) @@ -63,14 +62,10 @@ fun CharacterRibbonAlteration( delayMillis = 0, tooltip = it.tooltips, tooltipPlacement = remember(direction) { - TooltipPlacement.ComponentRect( - anchor = when (direction) { - LayoutDirection.Ltr -> Alignment.CenterEnd - LayoutDirection.Rtl -> Alignment.CenterStart - }, - alignment = when (direction) { - LayoutDirection.Ltr -> Alignment.CenterEnd - LayoutDirection.Rtl -> Alignment.CenterStart + TooltipPlacement.CursorPoint( + alignment = when (direction.invert) { + LayoutDirection.Ltr -> Alignment.CenterStart + LayoutDirection.Rtl -> Alignment.CenterEnd }, ) }, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt index 74860c8..4b824fd 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionPage.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -15,11 +16,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler import com.pixelized.desktop.lwa.ui.composable.confirmation.ConfirmationDialog +import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler +import com.pixelized.desktop.lwa.ui.theme.lwa import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.ic_camping_24dp +import lwacharactersheet.composeapp.generated.resources.ic_edit_note_24dp import lwacharactersheet.composeapp.generated.resources.ic_sync_24dp import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp import lwacharactersheet.composeapp.generated.resources.ic_visibility_off_24dp @@ -38,6 +41,7 @@ fun GMActionPage( val scope = rememberCoroutineScope() val scroll = rememberScrollState() val actions = viewModel.actions.collectAsStateWithLifecycle() + val editDialog = viewModel.editDialog.collectAsStateWithLifecycle() val validationDialog = viewModel.validationDialog.collectAsStateWithLifecycle() GMActionContent( @@ -48,6 +52,11 @@ fun GMActionPage( viewModel.onServerSync() } }, + onEditSession = { + scope.launch { + viewModel.onEditSession() + } + }, onPartyHeal = { scope.launch { viewModel.onPartyHeal() @@ -72,15 +81,20 @@ fun GMActionPage( ConfirmationDialog( dialog = validationDialog, ) + + GMEditDialog( + dialog = editDialog + ) } @Composable fun GMActionContent( modifier: Modifier = Modifier, scroll: ScrollState, - spacing: Dp = 8.dp, + spacing: Dp = MaterialTheme.lwa.dimen.paddingValue, actions: State, onServerSync: () -> Unit, + onEditSession: () -> Unit, onPartyHeal: () -> Unit, onPartyVisibility: () -> Unit, onNpcVisibility: () -> Unit, @@ -97,6 +111,12 @@ fun GMActionContent( label = "Synchronization du serveur", onAction = onServerSync, ) + GMAction( + modifier = Modifier.fillMaxWidth(), + icon = Res.drawable.ic_edit_note_24dp, + label = "Edition du titre de session", + onAction = onEditSession, + ) GMAction( modifier = Modifier.fillMaxWidth(), icon = Res.drawable.ic_camping_24dp, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt index 895f9a0..e630144 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt @@ -19,6 +19,16 @@ class GMActionUseCase( ) } + fun currentSceneTitle(): String = campaignRepository.campaignFlow().value.scene.name + + suspend fun updateSceneTitle( + title: String, + ) { + campaignRepository.updateSceneTitle( + title = title, + ) + } + suspend fun healPlayerParty() { campaignRepository.campaignFlow().value.characters.forEach { characterSheetId -> val sheet = characterRepository.characterDetail( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt index c80f8be..e740823 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionViewModel.kt @@ -5,6 +5,8 @@ import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.ui.composable.confirmation.ConfirmationDialogUio import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio +import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextField +import com.pixelized.desktop.lwa.ui.composable.textfield.createLwaTextFieldFlow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -15,6 +17,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.game_master__actions__campaign_scene__title import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_npc__description import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_npc__title import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_player__description @@ -27,7 +30,6 @@ import lwacharactersheet.composeapp.generated.resources.game_master__actions__sh import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_npc__title import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_player__description import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_player__title -import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.getString class GMActionViewModel( @@ -55,11 +57,14 @@ class GMActionViewModel( private val _validationDialog = MutableStateFlow(null) val validationDialog: StateFlow = _validationDialog + private val _editDialog = MutableStateFlow(null) + val editDialog: StateFlow = _editDialog + suspend fun onServerSync() { showConfirmationDialog( - title = Res.string.game_master__actions__on_server_sync__title, - description = Res.string.game_master__actions__on_server_sync__description, - onConfirmationRequest = { + title = getString(Res.string.game_master__actions__on_server_sync__title), + description = getString(Res.string.game_master__actions__on_server_sync__description), + onConfirmRequest = { try { actionUseCase.invalidateServerCache() } catch (exception: Exception) { @@ -73,11 +78,29 @@ class GMActionViewModel( ) } + suspend fun onEditSession() { + showEditDialog( + title = getString(Res.string.game_master__actions__campaign_scene__title), + value = actionUseCase.currentSceneTitle(), + onConfirmRequest = { + try { + actionUseCase.updateSceneTitle(title = it) + } catch (exception: Exception) { + val message = ErrorSnackUio.from(exception = exception) + _error.emit(message) + } + }, + onDismissRequest = { + _editDialog.value = null + }, + ) + } + suspend fun onPartyHeal() { showConfirmationDialog( - title = Res.string.game_master__actions__party_heal__title, - description = Res.string.game_master__actions__party_heal__description, - onConfirmationRequest = { + title = getString(Res.string.game_master__actions__party_heal__title), + description = getString(Res.string.game_master__actions__party_heal__description), + onConfirmRequest = { try { actionUseCase.healPlayerParty() } catch (exception: Exception) { @@ -91,14 +114,14 @@ class GMActionViewModel( suspend fun onPlayerVisibility() { showConfirmationDialog( title = when (actions.value?.party) { - true -> Res.string.game_master__actions__hide_player__title - else -> Res.string.game_master__actions__show_player__title + true -> getString(Res.string.game_master__actions__hide_player__title) + else -> getString(Res.string.game_master__actions__show_player__title) }, description = when (actions.value?.party) { - true -> Res.string.game_master__actions__hide_player__description - else -> Res.string.game_master__actions__show_player__description + true -> getString(Res.string.game_master__actions__hide_player__description) + else -> getString(Res.string.game_master__actions__show_player__description) }, - onConfirmationRequest = { + onConfirmRequest = { try { actionUseCase.togglePlayerVisibility() } catch (exception: Exception) { @@ -112,14 +135,14 @@ class GMActionViewModel( suspend fun onNpcVisibility() { showConfirmationDialog( title = when (actions.value?.npc) { - true -> Res.string.game_master__actions__hide_npc__title - else -> Res.string.game_master__actions__show_npc__title + true -> getString(Res.string.game_master__actions__hide_npc__title) + else -> getString(Res.string.game_master__actions__show_npc__title) }, description = when (actions.value?.npc) { - true -> Res.string.game_master__actions__hide_npc__description - else -> Res.string.game_master__actions__show_npc__description + true -> getString(Res.string.game_master__actions__hide_npc__description) + else -> getString(Res.string.game_master__actions__show_npc__description) }, - onConfirmationRequest = { + onConfirmRequest = { try { actionUseCase.toggleNpcVisibility() } catch (exception: Exception) { @@ -130,18 +153,42 @@ class GMActionViewModel( ) } - private suspend inline fun showConfirmationDialog( - title: StringResource, - description: StringResource, - crossinline onConfirmationRequest: suspend () -> Unit, + private inline fun showConfirmationDialog( + title: String, + description: String, + crossinline onConfirmRequest: suspend () -> Unit, crossinline onDismissRequest: () -> Unit = { _validationDialog.value = null }, ) { _validationDialog.value = ConfirmationDialogUio( - title = getString(title), - description = getString(description), + title = title, + description = description, onConfirmRequest = { viewModelScope.launch { - onConfirmationRequest() + onConfirmRequest() + onDismissRequest() + } + }, + onDismissRequest = { + onDismissRequest() + }, + ) + } + + private inline fun showEditDialog( + title: String, + value: String, + crossinline onConfirmRequest: suspend (String) -> Unit, + crossinline onDismissRequest: () -> Unit = { _editDialog.value = null }, + ) { + val edit = createLwaTextFieldFlow( + label = value, + ) + _editDialog.value = GMEditDialogUio( + title = title, + edit = edit.createLwaTextField(), + onConfirmRequest = { + viewModelScope.launch { + onConfirmRequest(edit.valueFlow.value) onDismissRequest() } }, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMEditDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMEditDialog.kt new file mode 100644 index 0000000..8b519c2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMEditDialog.kt @@ -0,0 +1,97 @@ +package com.pixelized.desktop.lwa.ui.screen.gamemaster.action + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.LocalBlurController +import com.pixelized.desktop.lwa.ui.composable.character.LwaDialog +import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField +import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.dialog__cancel_action +import lwacharactersheet.composeapp.generated.resources.dialog__confirm_action +import org.jetbrains.compose.resources.stringResource + +@Stable +data class GMEditDialogUio( + val title: String, + val edit: LwaTextFieldUio, + val onConfirmRequest: () -> Unit, + val onDismissRequest: () -> Unit, +) + +@Stable +object GMEditDialogDefault { + + @Stable + val paddings = PaddingValues(start = 16.dp, top = 16.dp, end = 16.dp) + + @Stable + val spacings: Dp = 8.dp +} + +@Composable +fun GMEditDialog( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = GMEditDialogDefault.paddings, + spacing: Dp = GMEditDialogDefault.spacings, + dialog: State, +) { + LwaDialog( + modifier = modifier, + blur = LocalBlurController.current, + state = dialog, + onDismissRequest = { dialog.value?.onDismissRequest?.invoke() }, + onConfirm = { dialog.value?.onConfirmRequest?.invoke() }, + ) { + Column( + modifier = Modifier.padding(paddingValues = paddingValues), + verticalArrangement = Arrangement.spacedBy(space = spacing), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + style = MaterialTheme.typography.caption, + text = it.title, + ) + LwaTextField( + field = it.edit, + ) + Row( + modifier = Modifier.align(alignment = Alignment.End), + horizontalArrangement = Arrangement.spacedBy( + space = spacing / 2, + alignment = Alignment.End, + ), + ) { + TextButton( + onClick = it.onDismissRequest, + ) { + Text( + color = MaterialTheme.colors.primaryVariant, + text = stringResource(Res.string.dialog__cancel_action) + ) + } + TextButton( + onClick = it.onConfirmRequest, + ) { + Text( + color = MaterialTheme.colors.primary, + text = stringResource(Res.string.dialog__confirm_action) + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/LayoutDirection+Invert.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/LayoutDirection+Invert.kt index 35ca892..7600a6b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/LayoutDirection+Invert.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/LayoutDirection+Invert.kt @@ -1,8 +1,10 @@ package com.pixelized.desktop.lwa.utils.extention +import androidx.compose.runtime.Stable import androidx.compose.ui.unit.LayoutDirection val LayoutDirection.invert: LayoutDirection + @Stable get() = when (this) { LayoutDirection.Ltr -> LayoutDirection.Rtl LayoutDirection.Rtl -> LayoutDirection.Ltr 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 index ddda468..1a4314d 100644 --- 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 @@ -29,6 +29,14 @@ class CampaignJsonFactory( // Json conversion. + fun createScene( + title: String, + ): CampaignJsonV2.SceneJsonV2 { + return CampaignJsonV2.SceneJsonV2( + name = title, + ) + } + fun convertToJson( campaign: Campaign, ): CampaignJson { From c5f4a3e46ea18d9a8c0168869dd0a2c38a77bd85 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 18 Oct 2025 00:53:40 +0200 Subject: [PATCH 2/3] Change the priority of the campain overlay. --- .../lwa/ui/screen/campaign/CampaignScreen.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 98e8f95..d786bd3 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 @@ -351,6 +351,14 @@ private fun CampaignLayout( ) { main() + Box( + modifier = Modifier + .align(alignment = Alignment.Center) + .fillMaxSize(), + ) { + overlay() + } + Row { Box( modifier = Modifier @@ -377,13 +385,6 @@ private fun CampaignLayout( rightPanel() } } - Box( - modifier = Modifier - .align(alignment = Alignment.Center) - .fillMaxSize(), - ) { - overlay() - } Box( modifier = Modifier .align(alignment = Alignment.CenterStart) From 24fe0306636a122e708b21549bebfb9b77360eee Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 18 Oct 2025 11:01:42 +0200 Subject: [PATCH 3/3] Add support for google drive share url. --- .../com/pixelized/desktop/lwa/Module.kt | 10 +++-- .../composable/image/DesaturatedAsyncImage.kt | 8 ++-- .../composable/image/ImagerModelConverter.kt | 19 ++++++++ .../lwa/ui/composable/image/LwaAsyncImage.kt | 44 +++++++++++++++++++ .../ui/overlay/portrait/PortraitOverlay.kt | 3 +- .../lwa/ui/screen/campaign/CampaignScreen.kt | 31 +++++++------ .../detail/inventory/item/InventoryItem.kt | 5 ++- .../common/CharacterRibbonAlteration.kt | 4 +- .../ribbon/common/CharacterRibbonPortrait.kt | 7 +-- .../lwa/ui/screen/levelup/LevelScreen.kt | 4 +- 10 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/ImagerModelConverter.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/LwaAsyncImage.kt 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 650a4cf..7a9467f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -31,21 +31,22 @@ import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogFa import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogFactory import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel +import com.pixelized.desktop.lwa.ui.composable.image.ImagerModelConverter import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel -import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkFactory -import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkViewModel -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory -import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.inventory.CharacterDetailInventoryFactory +import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetFactory +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.NpcRibbonViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbonViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.text.TextMessageFactory import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.links.ResourcesViewModel +import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkFactory +import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionUseCase import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel @@ -112,6 +113,7 @@ val toolsDependencies single { PathProvider(appName = "LwaClient") } + singleOf(::ImagerModelConverter) } val storeDependencies diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/DesaturatedAsyncImage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/DesaturatedAsyncImage.kt index aff7164..8d4f827 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/DesaturatedAsyncImage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/DesaturatedAsyncImage.kt @@ -6,12 +6,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush - import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.layout.ContentScale -import coil3.compose.AsyncImage import coil3.compose.AsyncImagePainter.Companion.DefaultTransform import coil3.compose.AsyncImagePainter.State import com.pixelized.desktop.lwa.utils.rememberBackgroundGradient @@ -32,8 +30,10 @@ fun DesaturatedAsyncImage( filterQuality: FilterQuality = FilterQuality.Low, clipToBounds: Boolean = true, ) { - Box(modifier = modifier) { - AsyncImage( + Box( + modifier = modifier, + ) { + LwaAsyncImage( model = model, contentDescription = contentDescription, modifier = Modifier.matchParentSize(), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/ImagerModelConverter.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/ImagerModelConverter.kt new file mode 100644 index 0000000..8172ad0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/ImagerModelConverter.kt @@ -0,0 +1,19 @@ +package com.pixelized.desktop.lwa.ui.composable.image + +class ImagerModelConverter { + + val googleDriveUrlRegex = Regex("""drive\.google\.com/file/d/([^/]*)""") + val workingGoogleDriveUri = "https://drive.google.com/uc?export=view&id=" + + fun convert( + model: Any?, + ): Any? { + return when (model) { + is String -> googleDriveUrlRegex.find(model)?.let { + val id = it.groupValues.getOrNull(1) + "$workingGoogleDriveUri$id" + } ?: model + else -> model + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/LwaAsyncImage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/LwaAsyncImage.kt new file mode 100644 index 0000000..5ea6cae --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/image/LwaAsyncImage.kt @@ -0,0 +1,44 @@ +package com.pixelized.desktop.lwa.ui.composable.image + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.DefaultAlpha +import androidx.compose.ui.graphics.FilterQuality +import androidx.compose.ui.layout.ContentScale +import coil3.compose.AsyncImage +import coil3.compose.AsyncImagePainter.Companion.DefaultTransform +import coil3.compose.AsyncImagePainter.State +import org.koin.compose.koinInject + +@Composable +fun LwaAsyncImage( + model: Any?, + modelConverter: ImagerModelConverter? = koinInject(), + contentDescription: String?, + modifier: Modifier = Modifier, + transform: (State) -> State = DefaultTransform, + onState: ((State) -> Unit)? = null, + alignment: Alignment = Alignment.Center, + contentScale: ContentScale = ContentScale.Fit, + alpha: Float = DefaultAlpha, + colorFilter: ColorFilter? = null, + filterQuality: FilterQuality = FilterQuality.Low, + clipToBounds: Boolean = true, +) { + AsyncImage( + modifier = modifier, + model = remember(modelConverter, model) { modelConverter?.convert(model) ?: model }, + contentDescription = contentDescription, + transform = transform, + onState = onState, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality, + clipToBounds = clipToBounds, + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt index 4d4f2ce..1377216 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/overlay/portrait/PortraitOverlay.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.FilterQuality import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors import com.pixelized.desktop.lwa.ui.theme.lwa import kotlinx.coroutines.launch @@ -97,7 +98,7 @@ private fun PortraitContent( else -> Box( modifier = Modifier.fillMaxSize(), ) { - AsyncImage( + LwaAsyncImage( modifier = Modifier.matchParentSize(), model = it, filterQuality = FilterQuality.High, 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 d786bd3..c608e2a 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 @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme @@ -351,14 +352,6 @@ private fun CampaignLayout( ) { main() - Box( - modifier = Modifier - .align(alignment = Alignment.Center) - .fillMaxSize(), - ) { - overlay() - } - Row { Box( modifier = Modifier @@ -368,13 +361,23 @@ private fun CampaignLayout( ) { leftPanel() } - Box( - modifier = Modifier - .align(alignment = Alignment.Bottom) - .weight(weight = 1f, fill = true) - .onSizeChanged { chatOverlayState.value = it.toDp(density) }, + Column( + modifier = Modifier.weight(weight = 1f, fill = true), ) { - chat() + Box( + modifier = Modifier + .fillMaxWidth() + .weight(weight = 1f, fill = true), + ) { + overlay() + } + Box( + modifier = Modifier + .fillMaxWidth() + .onSizeChanged { chatOverlayState.value = it.toDp(density) }, + ) { + chat() + } } Box( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt index 07ac083..7cfa99d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/item/InventoryItem.kt @@ -44,10 +44,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import coil3.PlatformContext -import coil3.compose.AsyncImage import coil3.request.ImageRequest import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.ui.composable.image.DesaturatedAsyncImage +import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.utils.extention.calculatePaddings @@ -194,7 +194,8 @@ fun InventoryItem( shape = CircleShape, ) ) - AsyncImage( + + LwaAsyncImage( modifier = Modifier .size(size = icon) .aspectRatio(ratio = 1f, matchHeightConstraintsFirst = true), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt index 000d761..0286625 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonAlteration.kt @@ -24,8 +24,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import coil3.PlatformContext -import coil3.compose.AsyncImage import coil3.request.ImageRequest +import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipLayout import com.pixelized.desktop.lwa.ui.composable.tooltip.BasicTooltipUio import com.pixelized.desktop.lwa.ui.theme.lwa @@ -74,7 +74,7 @@ fun CharacterRibbonAlteration( targetState = it.icon, transitionSpec = { fadeIn() togetherWith fadeOut() }, ) { icon -> - AsyncImage( + LwaAsyncImage( modifier = Modifier.size(24.dp), model = ImageRequest.Builder(context = PlatformContext.INSTANCE) .data(data = icon) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonPortrait.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonPortrait.kt index 983ceb0..1e74d3b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonPortrait.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/common/CharacterRibbonPortrait.kt @@ -6,7 +6,6 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.PointerMatcher import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -18,7 +17,6 @@ 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.onClick import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme @@ -30,12 +28,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.FilterQuality -import androidx.compose.ui.input.pointer.PointerButton import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import coil3.compose.AsyncImage +import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage import com.pixelized.desktop.lwa.ui.composable.shapes.ArrowShape import com.pixelized.desktop.lwa.ui.theme.lwa import lwacharactersheet.composeapp.generated.resources.Res @@ -80,7 +77,7 @@ fun CharacterRibbonPortrait( targetState = character.portrait, transitionSpec = { fadeIn() togetherWith fadeOut() }, ) { portrait -> - AsyncImage( + LwaAsyncImage( modifier = Modifier.fillMaxSize(), model = portrait, contentScale = ContentScale.Crop, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt index 799d8d4..1d5e264 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/levelup/LevelScreen.kt @@ -60,9 +60,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController -import coil3.compose.AsyncImage import com.pixelized.desktop.lwa.LocalRollHostState import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox +import com.pixelized.desktop.lwa.ui.composable.image.LwaAsyncImage 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.LevelUpDestination @@ -204,7 +204,7 @@ private fun LevelUpContent( ) }, background = { - AsyncImage( + LwaAsyncImage( modifier = Modifier.matchParentSize(), model = header.value?.portrait, contentScale = ContentScale.FillHeight,