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/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/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/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 98e8f95..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
@@ -360,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
@@ -377,13 +388,6 @@ private fun CampaignLayout(
rightPanel()
}
}
- Box(
- modifier = Modifier
- .align(alignment = Alignment.Center)
- .fillMaxSize(),
- ) {
- overlay()
- }
Box(
modifier = Modifier
.align(alignment = Alignment.CenterStart)
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 5da895b..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
@@ -21,19 +21,18 @@ 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
-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
+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
},
)
},
@@ -79,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/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/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,
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 {