diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 0bd91d4..0cc425a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -34,6 +34,7 @@ kotlin { // injection implementation(libs.koin.compose) implementation(libs.koin.compose.viewmodel) + implementation(libs.engawapg.zoomable) // composable component. implementation(libs.coil.compose) implementation(libs.coil.network.ktor) diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_center_focus_weak_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_center_focus_weak_24dp.xml new file mode 100644 index 0000000..a92214c --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_center_focus_weak_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_more_down_24dp.svg b/composeApp/src/commonMain/composeResources/drawable/ic_more_down_24dp.svg new file mode 100644 index 0000000..febe0eb --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_more_down_24dp.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_zoom_in_map_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_in_map_24dp.xml new file mode 100644 index 0000000..144dc6c --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_in_map_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_zoom_out_map_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_out_map_24dp.xml new file mode 100644 index 0000000..6cdc80e --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_zoom_out_map_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/image_dahome_maps.webp b/composeApp/src/commonMain/composeResources/drawable/image_dahome_maps.webp new file mode 100644 index 0000000..28f7f73 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/image_dahome_maps.webp differ diff --git a/composeApp/src/commonMain/composeResources/drawable/image_dahome_regions.webp b/composeApp/src/commonMain/composeResources/drawable/image_dahome_regions.webp new file mode 100644 index 0000000..4927211 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/image_dahome_regions.webp differ diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 4f9d158..dcb2884 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -269,12 +269,6 @@ Délai pour les Dés dynamiques Délai après lequel les dés dynamiques disparaissent. Chatlog options - Afficher automatiquement le chat - Affiche automatiquement le chat lors de la réception d'un message - Cacher automatiquement le chat - Cache automatiquement le chat au bout d'un certain temps - Délai pour cacher le chat - Délai après lequel le chat disparaît Défilement automatique Défilement automatique du chat vers le dernier message reçu lors de la réception de ce dernier. Nombre de lignes de textes visibles @@ -329,5 +323,17 @@ Consommable Ajouter une alteration Édition de personnage + Synchronisation du serveur + Demander au serveur d'invalider son cache + 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 + Cacher le panneau latéral gauche pour tous les joueurs. + Montrer les personnages joueurs + Montrer le panneau latéral gauche pour tous les joueurs. + Cacher le groupe de npcs + Cacher le panneau latéral droit pour tous les joueurs. + Montrer le groupe de npcs + Montrer le panneau latéral droit pour tous les joueurs. \ No newline at end of file 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 78e7204..650a4cf 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -40,13 +40,14 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.Characte 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.ribbon.npc.NpcRibbonViewModel -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel +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.gamemaster.GameMasterViewModel +import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionUseCase import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel @@ -198,4 +199,5 @@ val viewModelDependencies val useCaseDependencies get() = module { factoryOf(::SettingsUseCase) + factoryOf(::GMActionUseCase) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt index 8924ff2..f9b4f03 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt @@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.repository.settings import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV1 +import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV2 import com.pixelized.desktop.lwa.usecase.SettingsUseCase @@ -12,15 +13,13 @@ class SettingsFactory( fun convertToJson( settings: Settings, ): SettingsJson { - return SettingsJsonV1( + return SettingsJsonV2( host = settings.network.host, port = settings.network.port, playerName = settings.playerName, dynamicDice = settings.portrait.dynamicDice, dynamicDiceDelay = settings.portrait.dynamicDiceDelay, - autoHideChat = settings.chat.autoHideChat, - autoHideDelay = settings.chat.autoHideDelay, - autoShowChat = settings.chat.autoShowChat, + showChat = settings.chat.showChat, autoScrollChat = settings.chat.autoScrollChat, maxLineCount = settings.chat.maxLineCount, isAdmin = settings.isAdmin, @@ -33,6 +32,7 @@ class SettingsFactory( ): Settings { return when (json) { is SettingsJsonV1 -> convertFromJsonV1(json) + is SettingsJsonV2 -> convertFromJsonV2(json) } } @@ -51,9 +51,31 @@ class SettingsFactory( dynamicDiceDelay = json.dynamicDiceDelay ?: default.portrait.dynamicDiceDelay, ), chat = Settings.Chat( - autoHideChat = json.autoHideChat ?: default.chat.autoHideChat, - autoHideDelay = json.autoHideDelay ?: default.chat.autoHideDelay, - autoShowChat = json.autoShowChat ?: default.chat.autoShowChat, + showChat = default.chat.showChat, + autoScrollChat = json.autoScrollChat ?: default.chat.autoScrollChat, + maxLineCount = json.maxLineCount ?: default.chat.maxLineCount, + ), + isAdmin = json.isAdmin ?: default.isAdmin, + isGameMaster = json.isGameMaster ?: default.isGameMaster, + ) + } + + private fun convertFromJsonV2( + json: SettingsJsonV2, + ): Settings { + val default = useCase.defaultSettings() + return Settings( + playerName = json.playerName ?: default.playerName, + network = Settings.Network( + host = json.host ?: default.network.host, + port = json.port ?: default.network.port, + ), + portrait = Settings.Portrait( + dynamicDice = json.dynamicDice ?: default.portrait.dynamicDice, + dynamicDiceDelay = json.dynamicDiceDelay ?: default.portrait.dynamicDiceDelay, + ), + chat = Settings.Chat( + showChat = json.showChat ?: default.chat.showChat, autoScrollChat = json.autoScrollChat ?: default.chat.autoScrollChat, maxLineCount = json.maxLineCount ?: default.chat.maxLineCount, ), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt index 80fc9ee..2ef636a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt @@ -14,9 +14,7 @@ data class Settings( ) data class Chat( - val autoHideChat: Boolean, - val autoHideDelay: Int, - val autoShowChat: Boolean, + val showChat: Boolean, val autoScrollChat: Boolean, val maxLineCount: Int, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV2.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV2.kt new file mode 100644 index 0000000..7e54773 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/SettingsJsonV2.kt @@ -0,0 +1,17 @@ +package com.pixelized.desktop.lwa.repository.settings.model + +import kotlinx.serialization.Serializable + +@Serializable +data class SettingsJsonV2( + val host: String?, + val port: Int?, + val playerName: String?, + val dynamicDice: Boolean?, + val dynamicDiceDelay: Int?, + val showChat: Boolean?, + val autoScrollChat: Boolean?, + val maxLineCount: Int?, + val isGameMaster: Boolean?, + val isAdmin: Boolean?, +) : SettingsJson \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt index 856450d..80e8b46 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/LwaDialog.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.remember @@ -18,6 +19,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import com.pixelized.desktop.lwa.LocalBlurController +import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.utils.extention.onPreviewEscape @@ -29,6 +32,7 @@ object LwaDialogDefault { @Composable fun LwaDialog( modifier: Modifier = Modifier, + blur: BlurContentController? = LocalBlurController.current, paddings: PaddingValues = LwaDialogDefault.paddings, color: Color = MaterialTheme.colors.surface, state: State, @@ -37,6 +41,16 @@ fun LwaDialog( content: @Composable BoxScope.(T) -> Unit, ) { state.value?.let { dialog -> + + blur?.let { + DisposableEffect("LwaDialog") { + blur.show() + onDispose { + blur.hide() + } + } + } + Dialog( onDismissRequest = onDismissRequest, content = { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt index 23ead9d..8dea0f1 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/character/alterteration/CharacterSheetAlterationDialog.kt @@ -19,6 +19,7 @@ import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.minimumInteractiveComponentSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState @@ -28,13 +29,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler -import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio +import com.pixelized.desktop.lwa.LocalBlurController +import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.GMFilterHeader import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio import com.pixelized.desktop.lwa.ui.theme.lwa -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.ic_close_24dp @@ -52,12 +52,23 @@ data class CharacterSheetAlterationDialogUio( @OptIn(ExperimentalComposeUiApi::class) @Composable fun CharacterSheetAlterationDialog( + blur: BlurContentController? = LocalBlurController.current, dialog: State, onTag: (String) -> Unit, onAlteration: (characterSheetId: String, alterationId: String, active: Boolean) -> Unit, onDismissRequest: () -> Unit, ) { dialog.value?.let { + + blur?.let { + DisposableEffect("LwaDialog") { + blur.show() + onDispose { + blur.hide() + } + } + } + Dialog( properties = DialogProperties( usePlatformDefaultWidth = false, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/confirmation/ConfirmationDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/confirmation/ConfirmationDialog.kt new file mode 100644 index 0000000..7aab76c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/confirmation/ConfirmationDialog.kt @@ -0,0 +1,98 @@ +package com.pixelized.desktop.lwa.ui.composable.confirmation + +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.ExperimentalComposeUiApi +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 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 ConfirmationDialogUio( + val title: String, + val description: String, + val onConfirmRequest: () -> Unit, + val onDismissRequest: () -> Unit, +) + +@Stable +object ConfirmationDialogDefault { + + @Stable + val paddings = PaddingValues(start = 16.dp, top = 16.dp, end = 16.dp) + + @Stable + val spacings: Dp = 8.dp +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun ConfirmationDialog( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = ConfirmationDialogDefault.paddings, + spacing: Dp = ConfirmationDialogDefault.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, + ) + Text( + style = MaterialTheme.typography.body1, + text = it.description, + ) + 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/composable/scene/Camera.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Camera.kt new file mode 100644 index 0000000..c9eae2f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Camera.kt @@ -0,0 +1,101 @@ +package com.pixelized.desktop.lwa.ui.composable.scene + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.util.fastRoundToInt + +@Stable +class Camera( + private val initialZoom: Float = 2f, + private val initialOffset: IntOffset = IntOffset.Zero, +) { + private var _zoom = Animatable( + initialValue = initialZoom, + typeConverter = Float.VectorConverter, + ) + val zoom: Float get() = _zoom.value + + private var _offset = Animatable( + initialValue = initialOffset, + typeConverter = IntOffset.VectorConverter, + ) + val offset: IntOffset by derivedStateOf { + _offset.value + IntOffset( + x = (_sceneSize.width - cameraSizeZoomed.width) / 2, + y = (_sceneSize.height - cameraSizeZoomed.height) / 2, + ) + } + + private var _sceneSize: IntSize by mutableStateOf(IntSize.Zero) + private var _cameraSize: IntSize by mutableStateOf(IntSize.Zero) + val cameraSize: IntSize get() = _cameraSize + val cameraSizeZoomed: IntSize by derivedStateOf { + IntSize( + width = (cameraSize.width * zoom).fastRoundToInt(), + height = (cameraSize.height * zoom).fastRoundToInt(), + ) + } + + fun changeSizes( + sceneSize: IntSize, + cameraSize: IntSize, + ) { + _cameraSize = cameraSize + _sceneSize = sceneSize + } + + suspend fun handlePanning( + delta: Offset, + snap: Boolean, + ) { + val value = _offset.value - IntOffset( + x = (delta.x * zoom).fastRoundToInt(), + y = (delta.y * zoom).fastRoundToInt(), + ) + when { + snap -> _offset.snapTo(targetValue = value) + else -> _offset.animateTo(targetValue = value) + } + } + + suspend fun handleZoom( + zoomIn: Boolean, + power: Float, + snap: Boolean = false, + ) { + val value = _zoom.value * when { + zoomIn -> 1f - power + else -> 1f + power + } + when { + snap -> _zoom.snapTo(targetValue = value) + else -> _zoom.animateTo(targetValue = value) + } + } + + suspend fun resetPosition( + snap: Boolean = false, + ) { + when (snap) { + true -> _offset.snapTo(targetValue = initialOffset) + else -> _offset.animateTo(targetValue = initialOffset) + } + } + + suspend fun resetZoom( + snap: Boolean = false, + ) { + when (snap) { + true -> _zoom.snapTo(targetValue = initialZoom) + else -> _zoom.animateTo(targetValue = initialZoom) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt new file mode 100644 index 0000000..31e9e09 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/FogOfWar.kt @@ -0,0 +1,9 @@ +package com.pixelized.desktop.lwa.ui.composable.scene + +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color + +@Stable +data class FogOfWar( + val color: Color = Color.Black.copy(alpha = 0.5f), +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Layout.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Layout.kt new file mode 100644 index 0000000..e211690 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Layout.kt @@ -0,0 +1,41 @@ +package com.pixelized.desktop.lwa.ui.composable.scene + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize + +@Stable +class Layout( + val texture: ImageBitmap, + val offset: IntOffset = IntOffset.Zero, + val size: IntSize = IntSize(texture.width, texture.height), + private val initialAlpha: Float = 1f, +) { + private val _alpha = Animatable( + initialValue = initialAlpha, + typeConverter = Float.VectorConverter, + ) + val alpha get() = _alpha.value + + suspend fun alpha( + alpha: Float, + snap: Boolean = false, + ) { + when (snap) { + true -> _alpha.snapTo(targetValue = alpha) + else -> _alpha.animateTo(targetValue = alpha) + } + } + + suspend fun resetAlpha( + snap: Boolean = false, + ) { + when (snap) { + true -> _alpha.snapTo(targetValue = initialAlpha) + else -> _alpha.animateTo(targetValue = initialAlpha) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt new file mode 100644 index 0000000..9e2ed21 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/scene/Scene.kt @@ -0,0 +1,278 @@ +package com.pixelized.desktop.lwa.ui.composable.scene + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.isAltPressed +import androidx.compose.ui.input.pointer.isCtrlPressed +import androidx.compose.ui.input.pointer.isPrimaryPressed +import androidx.compose.ui.input.pointer.isTertiaryPressed +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.ic_center_focus_weak_24dp +import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp +import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp +import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp +import lwacharactersheet.composeapp.generated.resources.icon_d100 +import lwacharactersheet.composeapp.generated.resources.image_dahome_maps +import lwacharactersheet.composeapp.generated.resources.image_dahome_regions +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.imageResource +import org.jetbrains.compose.resources.painterResource +import kotlin.math.sign + +@Stable +data class Scene( + val camera: Camera, + val layouts: List, + val fogOfWar: FogOfWar, +) { + val size: IntSize = IntSize( + width = layouts.maxOf { it.size.width }, + height = layouts.maxOf { it.size.height }, + ) +} + +@Composable +fun Scene( + modifier: Modifier, +) { + val campaign = LocalCampaignLayoutScope.current + val scope = rememberCoroutineScope() + val scene = rememberScene( + camera = Camera( + initialZoom = 1f, + initialOffset = IntOffset(x = -150, y = -120), + ), + fogOfWar = FogOfWar(), + rememberLayoutFromResource( + resource = Res.drawable.image_dahome_maps, + ), + rememberLayoutFromResource( + resource = Res.drawable.image_dahome_regions, + ), + rememberLayoutFromResource( + resource = Res.drawable.icon_d100, + offset = IntOffset(x = 1740, y = 910), + ), + ) + Box( + modifier = modifier + .graphicsLayer { clip = true } + .onCameraControl(scope = scope, scene = scene) + .drawScene(scene = scene) + .fogOfWar(scene = scene) + ) { + Column( + modifier = Modifier + .align(alignment = Alignment.BottomEnd) + .padding(end = campaign.rightPanel.value.width) + .padding(all = 8.dp) + ) { + IconButton( + onClick = { + scope.launch { + scene.camera.handleZoom( + zoomIn = true, + power = 0.3f, + ) + } + } + ) { + Icon( + painter = painterResource(Res.drawable.ic_zoom_in_map_24dp), + contentDescription = null + ) + } + IconButton( + onClick = { + scope.launch { + scene.camera.handleZoom( + zoomIn = false, + power = 0.3f, + ) + } + } + ) { + Icon( + painter = painterResource(Res.drawable.ic_zoom_out_map_24dp), + contentDescription = null + ) + } + IconButton( + onClick = { + scope.launch { + scene.camera.resetPosition() + } + scope.launch { + scene.camera.resetZoom() + } + } + ) { + Icon( + painter = painterResource(Res.drawable.ic_center_focus_weak_24dp), + contentDescription = null + ) + } + IconButton( + onClick = { + scope.launch { + scene.layouts.getOrNull(1)?.let { + it.alpha(alpha = if (it.alpha == 0f) 1f else 0f) + } + } + } + ) { + Icon( + painter = painterResource(Res.drawable.ic_visibility_24dp), + contentDescription = null + ) + } + } + } +} + +@Composable +@Stable +fun rememberLayoutFromResource( + resource: DrawableResource, + offset: IntOffset = IntOffset.Zero, +): Layout { + val texture = imageResource( + resource = resource, + ) + return remember(resource) { + Layout( + texture = texture, + offset = offset, + ) + } +} + +@Composable +@Stable +fun rememberScene( + camera: Camera, + fogOfWar: FogOfWar, + vararg layouts: Layout, +): Scene { + return remember { + Scene( + camera = camera, + layouts = layouts.toList(), + fogOfWar = fogOfWar, + ) + } +} + +@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) +fun Modifier.onCameraControl( + scope: CoroutineScope, + scene: Scene, +): Modifier { + val offsetDelta = CursorDelta() + return this + .onSizeChanged { + scene.camera.changeSizes( + sceneSize = scene.size, + cameraSize = it, + ) + } + .onPointerEvent(PointerEventType.Move) { event: PointerEvent -> + scope.launch { + offsetDelta.handlePositionChange( + event = event, + ) { delta -> + when { + event.buttons.isTertiaryPressed || (event.keyboardModifiers.isCtrlPressed && event.buttons.isPrimaryPressed) -> scene.camera.handlePanning( + delta = delta, + snap = true, + ) + + event.keyboardModifiers.isAltPressed -> scene.camera.handleZoom( + zoomIn = delta.y.sign < 0f, + power = 0.025f, + snap = true, + ) + } + } + } + } + .onPointerEvent(PointerEventType.Scroll) { event: PointerEvent -> + scope.launch { + scene.camera.handleZoom( + zoomIn = event.changes.first().scrollDelta.y.sign < 0f, + power = 0.15f, + snap = false, + ) + } + } +} + +fun Modifier.drawScene( + scene: Scene, +): Modifier = this.drawWithCache { + onDrawBehind { + scene.layouts.forEach { layout -> + drawImage( + image = layout.texture, + srcOffset = scene.camera.offset - layout.offset, + srcSize = scene.camera.cameraSizeZoomed, + dstSize = scene.camera.cameraSize, + alpha = layout.alpha, + ) + } + } +} + +fun Modifier.fogOfWar( + scene: Scene, +): Modifier = this.drawWithCache { + onDrawBehind { + drawRect(color = scene.fogOfWar.color) + } +} + +private data class CursorDelta( + var lastDeltaTimestamp: Long = System.currentTimeMillis(), + var previousPosition: Offset = Offset.Zero, + var currentPosition: Offset = Offset.Zero, +) { + suspend inline fun handlePositionChange( + event: PointerEvent, + delay: Float = 10f, + crossinline block: suspend (delta: Offset) -> Unit, + ) { + val currentTimestamp = System.currentTimeMillis() + if (currentTimestamp - lastDeltaTimestamp > delay) { + lastDeltaTimestamp = currentTimestamp + previousPosition = currentPosition + currentPosition = event.changes.first().position + block(currentPosition - previousPosition) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt index f5da12e..f5e6fee 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/tooltip/BasicTooltipLayout.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.TooltipPlacement import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -56,7 +56,7 @@ fun BasicTooltipLayout( tips = tooltip, tooltip = { BasicTooltip( - modifier = Modifier.width(width = 448.dp), + modifier = Modifier.widthIn(max = 448.dp), elevation = elevation, tooltip = it, ) @@ -72,7 +72,9 @@ private fun BasicTooltip( tooltip: BasicTooltipUio, ) { Surface( - modifier = Modifier.padding(16.dp).then(other = modifier), + modifier = Modifier + .padding(16.dp) + .then(other = modifier), color = MaterialTheme.colors.surface, elevation = elevation, shape = remember { RoundedCornerShape(4.dp) } 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 c18e99b..bd773ae 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 @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -19,6 +20,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.isAltPressed import androidx.compose.ui.input.key.isCtrlPressed @@ -41,6 +43,7 @@ import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterShe import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogViewModel import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler +import com.pixelized.desktop.lwa.ui.composable.scene.Scene import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToLevelScreen import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlay @@ -48,13 +51,14 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetai import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanelViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.DetailPanelUio import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.rememberTransitionAnimation -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc.NpcRibbon -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbon -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player.PlayerRibbonViewModel +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbon +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.CampaignChat import com.pixelized.desktop.lwa.ui.screen.campaign.text.CampaignChatViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbarViewModel +import com.pixelized.desktop.lwa.ui.theme.lwa import kotlinx.coroutines.launch import org.koin.compose.viewmodel.koinViewModel @@ -66,6 +70,7 @@ val LocalCampaignLayoutScope = compositionLocalOf { fun CampaignScreen( playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(), playerDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "player"), + npcRibbonViewModel: NpcRibbonViewModel = koinViewModel(), npcDetailViewModel: CharacterDetailPanelViewModel = koinViewModel(key = "npc"), characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel = koinViewModel(), dismissedViewModel: CharacterSheetDiminishedDialogViewModel = koinViewModel(), @@ -95,7 +100,9 @@ fun CampaignScreen( }, main = { - + Scene( + modifier = Modifier.matchParentSize(), + ) }, chat = { CampaignChat( @@ -114,9 +121,10 @@ fun CampaignScreen( } }, leftPanel = { - PlayerRibbon( + CharacterRibbon( modifier = Modifier.fillMaxHeight(), viewModel = playerRibbonViewModel, + layoutDirection = LayoutDirection.Ltr, onCharacterLeftClick = { scope.launch { playerDetailViewModel.showCharacter( @@ -139,8 +147,10 @@ fun CampaignScreen( ) }, rightPanel = { - NpcRibbon( + CharacterRibbon( modifier = Modifier.fillMaxHeight(), + viewModel = npcRibbonViewModel, + layoutDirection = LayoutDirection.Rtl, onCharacterLeftClick = { scope.launch { npcDetailViewModel.showCharacter( @@ -166,11 +176,11 @@ fun CampaignScreen( CharacterDetailPanel( modifier = Modifier .padding(all = 8.dp) + .padding(start = MaterialTheme.lwa.size.portrait.minimized.width + 8.dp) .width(width = 128.dp * 4) .fillMaxHeight(), transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Ltr), - blurController = blurController, - detailPanelViewModel = npcDetailViewModel, + detailPanelViewModel = playerDetailViewModel, characterDiminishedViewModel = dismissedViewModel, characteristicDialogViewModel = characteristicDialogViewModel, alterationViewModel = alterationViewModel, @@ -183,11 +193,11 @@ fun CampaignScreen( CharacterDetailPanel( modifier = Modifier .padding(all = 8.dp) + .padding(end = MaterialTheme.lwa.size.portrait.minimized.width + 8.dp) .width(width = 128.dp * 4) .fillMaxHeight(), transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl), - blurController = blurController, - detailPanelViewModel = playerDetailViewModel, + detailPanelViewModel = npcDetailViewModel, characterDiminishedViewModel = dismissedViewModel, characteristicDialogViewModel = characteristicDialogViewModel, alterationViewModel = alterationViewModel, @@ -206,7 +216,7 @@ fun CampaignScreen( characteristicDialogViewModel.changeSubCharacteristic( characterSheetId = dialog.characterSheetId, characteristic = dialog.characteristic, - useArmor= dialog.enableArmor?.checked?.value == true, + useArmor = dialog.enableArmor?.checked?.value == true, value = dialog.value.valueFlow.value.toIntOrNull() ?: 0, ) characteristicDialogViewModel.hideSubCharacteristicDialog() @@ -308,6 +318,7 @@ private fun CampaignLayout( ) { val density = LocalDensity.current + val mainState = remember { mutableStateOf(DpSize.Unspecified) } val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) } val leftPanelState = remember { mutableStateOf(DpSize.Unspecified) } val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) } @@ -315,6 +326,7 @@ private fun CampaignLayout( val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) } val scope = remember { CampaignLayoutScope( + main = mainState, leftOverlay = leftOverlayState, leftPanel = leftPanelState, rightOverlay = rightOverlayState, @@ -335,14 +347,18 @@ private fun CampaignLayout( ) { Box( modifier = Modifier - .align(alignment = Alignment.Center) - .fillMaxSize(), + .onSizeChanged { mainState.value = it.toDp(density) } + .matchParentSize(), ) { main() } Box( modifier = Modifier - .align(alignment = Alignment.BottomEnd) + .align(alignment = Alignment.BottomStart) + .padding( + start = MaterialTheme.lwa.size.portrait.minimized.width * 2 + 20.dp, + end = MaterialTheme.lwa.size.portrait.minimized.width * 2 + 20.dp + 56.dp, + ) .onSizeChanged { chatOverlayState.value = it.toDp(density) }, ) { chat() @@ -394,24 +410,31 @@ private fun CampaignKeyHandler( onPlayerNumber: (index: Int) -> Unit, onAltPLayerNumber: (index: Int) -> Unit, ) { + fun KeyEvent.callback(index: Int) { + if (isAltPressed) onAltPLayerNumber(index) else onPlayerNumber(index) + } KeyHandler { - if (it.type != KeyEventType.KeyDown) return@KeyHandler false + if (it.type != KeyEventType.KeyDown) { + return@KeyHandler false + } if (it.key == Key.Escape) { onDismissRequest() return@KeyHandler true } - if (it.isCtrlPressed.not()) return@KeyHandler false + if (it.isCtrlPressed.not()) { + return@KeyHandler false + } when (it.key) { Key.Escape -> onDismissRequest() - Key.One, Key.NumPad1 -> if (it.isAltPressed) onAltPLayerNumber(0) else onPlayerNumber(0) - Key.Two, Key.NumPad2 -> if (it.isAltPressed) onAltPLayerNumber(1) else onPlayerNumber(1) - Key.Three, Key.NumPad3 -> if (it.isAltPressed) onAltPLayerNumber(2) else onPlayerNumber(2) - Key.Four, Key.NumPad4 -> if (it.isAltPressed) onAltPLayerNumber(3) else onPlayerNumber(3) - Key.Five, Key.NumPad5 -> if (it.isAltPressed) onAltPLayerNumber(4) else onPlayerNumber(4) - Key.Six, Key.NumPad6 -> if (it.isAltPressed) onAltPLayerNumber(5) else onPlayerNumber(5) - Key.Seven, Key.NumPad7 -> if (it.isAltPressed) onAltPLayerNumber(6) else onPlayerNumber(6) - Key.Eight, Key.NumPad8 -> if (it.isAltPressed) onAltPLayerNumber(7) else onPlayerNumber(7) - Key.Nine, Key.NumPad9 -> if (it.isAltPressed) onAltPLayerNumber(8) else onPlayerNumber(8) + Key.One, Key.NumPad1 -> it.callback(index = 0) + Key.Two, Key.NumPad2 -> it.callback(index = 1) + Key.Three, Key.NumPad3 -> it.callback(index = 2) + Key.Four, Key.NumPad4 -> it.callback(index = 3) + Key.Five, Key.NumPad5 -> it.callback(index = 4) + Key.Six, Key.NumPad6 -> it.callback(index = 5) + Key.Seven, Key.NumPad7 -> it.callback(index = 6) + Key.Eight, Key.NumPad8 -> it.callback(index = 7) + Key.Nine, Key.NumPad9 -> it.callback(index = 8) else -> return@KeyHandler false } return@KeyHandler true @@ -427,6 +450,7 @@ private fun IntSize.toDp(density: Density) = with(density) { @Stable data class CampaignLayoutScope( + val main: State, val leftOverlay: State, val leftPanel: State, val rightOverlay: State, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt index ccf2552..6119193 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetailPanel.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState @@ -31,7 +30,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp import com.pixelized.desktop.lwa.LocalRollHostState import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController import com.pixelized.desktop.lwa.ui.composable.character.alterteration.CharacterSheetAlterationDialogViewModel @@ -74,7 +72,6 @@ enum class DetailPanelUio { @Composable fun CharacterDetailPanel( modifier: Modifier = Modifier, - blurController: BlurContentController, transitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = rememberTransitionAnimation(), detailPanelViewModel: CharacterDetailPanelViewModel, characteristicDialogViewModel: CharacterSheetCharacteristicDialogViewModel, @@ -121,12 +118,10 @@ fun CharacterDetailPanel( } }, onAlteration = { - blurController.show() alterationViewModel.show(characterSheetId = it) }, onDiminished = { scope.launch { - blurController.show() characterDiminishedViewModel.showDiminishedDialog( characterSheetId = it ) @@ -134,7 +129,6 @@ fun CharacterDetailPanel( }, onHp = { scope.launch { - blurController.show() characteristicDialogViewModel.showSubCharacteristicDialog( characterSheetId = it, characteristic = CharacterSheetCharacteristicDialogUio.Characteristic.Damage, @@ -143,7 +137,6 @@ fun CharacterDetailPanel( }, onPp = { scope.launch { - blurController.show() characteristicDialogViewModel.showSubCharacteristicDialog( characterSheetId = it, characteristic = CharacterSheetCharacteristicDialogUio.Characteristic.Fatigue, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt index 714593f..0221bef 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/inventory/CharacterDetailInventory.kt @@ -107,7 +107,6 @@ fun CharacterDetailInventory( itemDetailDialogViewModel: ItemDetailDialogViewModel = koinViewModel(), inventory: State, ) { - val blur = LocalBlurController.current val focus = LocalFocusManager.current val scope = rememberCoroutineScope() @@ -120,14 +119,12 @@ fun CharacterDetailInventory( spacing = spacing, inventory = unWrap, onPurse = { - blur.show() purseViewModel.showPurseDialog( characterSheetId = it, ) focus.clearFocus(force = true) }, onItem = { item -> - blur.show() itemDetailDialogViewModel.showItemDialog( characterSheetId = item.characterSheetId, inventoryId = item.inventoryId, @@ -136,7 +133,6 @@ fun CharacterDetailInventory( focus.clearFocus(force = true) }, onAddItem = { - blur.show() inventoryDialogViewModel.showInventoryDialog( characterSheetId = it, ) @@ -166,7 +162,6 @@ fun CharacterDetailInventory( PurseDialog( dialog = purseViewModel.purseDialog.collectAsState(), onDismissRequest = { - blur.hide() purseViewModel.hidePurseDialog() }, onSwapSign = { @@ -175,7 +170,6 @@ fun CharacterDetailInventory( onConfirm = { scope.launch { if (purseViewModel.confirmPurse(dialog = it)) { - blur.hide() purseViewModel.hidePurseDialog() } } @@ -185,11 +179,9 @@ fun CharacterDetailInventory( InventoryDialog( dialog = inventoryDialogViewModel.inventoryDialog.collectAsState(), onDismissRequest = { - blur.hide() inventoryDialogViewModel.hideInventoryDialog() }, onItem = { dialog, itemId -> - blur.show() itemDetailDialogViewModel.showItemDialog( characterSheetId = dialog.characterSheetId, inventoryId = null, @@ -201,7 +193,6 @@ fun CharacterDetailInventory( ItemDetailDialog( dialog = itemDetailDialogViewModel.itemDialog.collectAsState(), onDismissRequest = { - blur.hide() itemDetailDialogViewModel.hideItemDialog() }, onAddItem = { dialog -> @@ -210,7 +201,6 @@ fun CharacterDetailInventory( dialog = dialog, ) if (result) { - blur.hide() itemDetailDialogViewModel.hideItemDialog() } } @@ -221,7 +211,6 @@ fun CharacterDetailInventory( dialog = dialog, ) if (result) { - blur.hide() itemDetailDialogViewModel.hideItemDialog() } } @@ -233,7 +222,6 @@ fun CharacterDetailInventory( inventoryId = dialog.inventoryId, ) if (result) { - blur.hide() itemDetailDialogViewModel.hideItemDialog() } } @@ -245,7 +233,6 @@ fun CharacterDetailInventory( inventoryId = dialog.inventoryId, ) if (result) { - blur.hide() itemDetailDialogViewModel.hideItemDialog() } } @@ -257,7 +244,6 @@ fun CharacterDetailInventory( inventoryId = dialog.inventoryId, ) if (result) { - blur.hide() itemDetailDialogViewModel.hideItemDialog() } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt new file mode 100644 index 0000000..c67da46 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/CharacterRibbon.kt @@ -0,0 +1,70 @@ +package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll +import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration + +@Composable +fun CharacterRibbon( + modifier: Modifier = Modifier, + layoutDirection: LayoutDirection, + viewModel: CharacterRibbonViewModel, + padding: PaddingValues = PaddingValues(all = 8.dp), + onCharacterLeftClick: (characterSheetId: String) -> Unit, + onCharacterRightClick: (characterSheetId: String) -> Unit, + onLevelUp: (characterSheetId: String) -> Unit, +) { + val characters = viewModel.characters.collectAsState() + + CompositionLocalProvider( + LocalLayoutDirection provides layoutDirection + ) { + LazyColumn( + modifier = modifier, + contentPadding = padding, + verticalArrangement = Arrangement.spacedBy(space = 8.dp) + ) { + items( + items = characters.value, + key = { it.characterSheetId }, + ) { + Row( + modifier = Modifier + .animateItem() + .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f }, + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Box { + CharacterRibbonPortrait( + character = it.portrait, + onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) }, + onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) }, + onLevelUp = { onLevelUp(it.characterSheetId) }, + ) + CharacterRibbonRoll( + value = viewModel.roll(characterSheetId = it.characterSheetId).value, + ) + } + CharacterRibbonAlteration( + status = it.status, + direction = LayoutDirection.Ltr, + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/NpcRibbonViewModel.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/NpcRibbonViewModel.kt index 3dd6c5b..e029293 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbonViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/NpcRibbonViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc +package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository @@ -6,8 +6,6 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.model.Settings -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel import com.pixelized.shared.lwa.model.campaign.Campaign class NpcRibbonViewModel( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt index e80b095..df81d36 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbonViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerRibbonViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player +package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository @@ -6,8 +6,6 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetReposit import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.model.Settings -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonFactory -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.CharacterRibbonViewModel import com.pixelized.shared.lwa.model.campaign.Campaign class PlayerRibbonViewModel( 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 838967d..bdb8729 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 @@ -32,6 +32,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa @Stable data class CharacterRibbonAlterationUio( + val icon: String, val tooltips: BasicTooltipUio?, ) @@ -44,60 +45,56 @@ fun CharacterRibbonAlteration( direction: LayoutDirection, status: List>, ) { - val currentDirection: LayoutDirection = LocalLayoutDirection.current + val currentDirection = LocalLayoutDirection.current - CompositionLocalProvider( - LocalLayoutDirection provides direction + Row( + modifier = Modifier + .animateContentSize() + .size(size = size) + .then(other = modifier), + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), ) { - Row( - modifier = Modifier - .animateContentSize() - .size(size = size) - .then(other = modifier), - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), - ) { - CompositionLocalProvider( - LocalLayoutDirection provides currentDirection + status.forEach { columns -> + Column( + modifier = Modifier.animateContentSize(), + verticalArrangement = Arrangement.spacedBy(space = 2.dp), ) { - status.forEach { columns -> - Column( - modifier = Modifier.animateContentSize(), - verticalArrangement = Arrangement.spacedBy(space = 2.dp), - ) { - columns.forEach { - BasicTooltipLayout( - delayMillis = 0, - tooltip = it.tooltips, - tooltipPlacement = remember(currentDirection) { - TooltipPlacement.ComponentRect( - anchor = when (direction) { - LayoutDirection.Ltr -> Alignment.TopStart - LayoutDirection.Rtl -> Alignment.TopEnd - }, - alignment = when (direction) { - LayoutDirection.Ltr -> Alignment.BottomEnd - LayoutDirection.Rtl -> Alignment.BottomStart - }, + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr + ) { + columns.forEach { + BasicTooltipLayout( + delayMillis = 0, + tooltip = it.tooltips, + tooltipPlacement = remember(currentDirection) { + TooltipPlacement.ComponentRect( + anchor = when (direction) { + LayoutDirection.Ltr -> Alignment.CenterEnd + LayoutDirection.Rtl -> Alignment.CenterStart + }, + alignment = when (direction) { + LayoutDirection.Ltr -> Alignment.CenterEnd + LayoutDirection.Rtl -> Alignment.CenterStart + }, + ) + }, + content = { + AnimatedContent( + targetState = it.icon, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + ) { icon -> + AsyncImage( + modifier = Modifier.size(24.dp), + model = ImageRequest.Builder(context = PlatformContext.INSTANCE) + .data(data = icon) + .size(size = 48) + .build(), + filterQuality = FilterQuality.High, + contentDescription = null, ) - }, - content = { - AnimatedContent( - targetState = it.icon, - transitionSpec = { fadeIn() togetherWith fadeOut() }, - ) { icon -> - AsyncImage( - modifier = Modifier.size(24.dp), - model = ImageRequest.Builder(context = PlatformContext.INSTANCE) - .data(data = icon) - .size(size = 48) - .build(), - filterQuality = FilterQuality.High, - contentDescription = null, - ) - } } - ) - } + } + ) } } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbon.kt deleted file mode 100644 index a9fbfcb..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/npc/NpcRibbon.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.npc - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration -import org.koin.compose.viewmodel.koinViewModel - -@Composable -fun NpcRibbon( - modifier: Modifier = Modifier, - viewModel: NpcRibbonViewModel = koinViewModel(), - padding: PaddingValues = PaddingValues(all = 8.dp), - onCharacterLeftClick: (characterSheetId: String) -> Unit, - onCharacterRightClick: (characterSheetId: String) -> Unit, - onLevelUp: (characterSheetId: String) -> Unit, -) { - val characters = viewModel.characters.collectAsState() - - LazyColumn( - modifier = modifier, - contentPadding = padding, - verticalArrangement = Arrangement.spacedBy(space = 8.dp) - ) { - items( - items = characters.value, - key = { it.characterSheetId }, - ) { - Row( - modifier = Modifier - .animateItem() - .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f }, - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), - ) { - CharacterRibbonAlteration( - status = it.status, - direction = LayoutDirection.Rtl, - ) - Box { - CharacterRibbonPortrait( - character = it.portrait, - onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) }, - onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) }, - onLevelUp = { onLevelUp(it.characterSheetId) }, - ) - CharacterRibbonRoll( - value = viewModel.roll(characterSheetId = it.characterSheetId).value, - ) - } - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbon.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbon.kt deleted file mode 100644 index 37554ad..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/player/PlayerRibbon.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.player - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonPortrait -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRoll -import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonAlteration -import org.koin.compose.viewmodel.koinViewModel - -@Composable -fun PlayerRibbon( - modifier: Modifier = Modifier, - viewModel: PlayerRibbonViewModel = koinViewModel(), - padding: PaddingValues = PaddingValues(all = 8.dp), - onCharacterLeftClick: (characterSheetId: String) -> Unit, - onCharacterRightClick: (characterSheetId: String) -> Unit, - onLevelUp: (characterSheetId: String) -> Unit, -) { - val characters = viewModel.characters.collectAsState() - - LazyColumn( - modifier = modifier, - contentPadding = padding, - verticalArrangement = Arrangement.spacedBy(space = 8.dp) - ) { - items( - items = characters.value, - key = { it.characterSheetId }, - ) { - Row( - modifier = Modifier - .animateItem() - .graphicsLayer { if (it.hideOverruled) this.alpha = 0.3f }, - horizontalArrangement = Arrangement.spacedBy(space = 4.dp), - ) { - Box { - CharacterRibbonPortrait( - character = it.portrait, - onCharacterLeftClick = { onCharacterLeftClick(it.characterSheetId) }, - onCharacterRightClick = { onCharacterRightClick(it.characterSheetId) }, - onLevelUp = { onLevelUp(it.characterSheetId) }, - ) - CharacterRibbonRoll( - value = viewModel.roll(characterSheetId = it.characterSheetId).value, - ) - } - CharacterRibbonAlteration( - status = it.status, - direction = LayoutDirection.Ltr, - ) - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt index c3a1453..9af413a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChat.kt @@ -1,18 +1,28 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.text +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.expandIn +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -21,13 +31,10 @@ import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max @@ -47,21 +54,21 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.RollTextMessag import com.pixelized.desktop.lwa.ui.screen.campaign.text.messages.TextMessage import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.usecase.SettingsUseCase -import kotlinx.coroutines.launch +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.ic_more_down_24dp +import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel @Stable data class ChatSettingsUio( - val autoShowChat: Boolean, - val autoScrollChat: Boolean, - val autoHideChat: Boolean, + val show: Boolean, + val autoScroll: Boolean, ) { companion object { fun default() = with(SettingsUseCase().defaultSettings()) { ChatSettingsUio( - autoShowChat = chat.autoShowChat, - autoScrollChat = chat.autoScrollChat, - autoHideChat = chat.autoHideChat, + show = chat.showChat, + autoScroll = chat.autoScrollChat, ) } } @@ -73,9 +80,9 @@ fun CampaignChat( modifier: Modifier = Modifier, chatViewModel: CampaignChatViewModel = koinViewModel(), ) { - val scope = rememberCoroutineScope() + val lazyState = rememberLazyListState() - val animatedChatWidth = rememberAnimatedChatWidth() + val campaignLayoutScope = LocalCampaignLayoutScope.current val colorScheme = MaterialTheme.lwa.colorScheme val messages = chatViewModel.messages.collectAsState() val settings = chatViewModel.settings.collectAsState() @@ -84,54 +91,63 @@ fun CampaignChat( lazyState = lazyState, messages = messages, settings = settings, - displayChat = chatViewModel::displayChat, - hideChat = chatViewModel::hideChat, ) - Box( - modifier = modifier - .size( - width = animatedChatWidth.value, - height = MaterialTheme.lwa.size.portrait.minimized.height * 2 + 8.dp, - ) - .graphicsLayer { - alpha = chatViewModel.chatAnimatedVisibility.value - } - .background( - shape = remember { RoundedCornerShape(8.dp) }, - color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) }, - ) - .onPointerEvent(eventType = PointerEventType.Enter) { - scope.launch { chatViewModel.displayChat() } - } - .onPointerEvent(eventType = PointerEventType.Exit) { - if (settings.value.autoHideChat) { - scope.launch { chatViewModel.hideChat() } - } - }, + Row( + modifier = modifier.background( + shape = remember { RoundedCornerShape(8.dp) }, + color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) }, + ), ) { - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = lazyState, - verticalArrangement = Arrangement.spacedBy( - space = 4.dp, - alignment = Alignment.Bottom, - ), - contentPadding = remember { PaddingValues(all = 8.dp) }, + AnimatedVisibility( + visible = settings.value.show, + enter = fadeIn() + expandIn(), + exit = fadeOut() + shrinkOut(), ) { - items( - items = messages.value, - key = { it.id }, - contentType = { it.javaClass.simpleName } + LazyColumn( + modifier = Modifier + .width(width = campaignLayoutScope.chatOverlay.value.width - (32.dp + 8.dp)) + .heightIn(min = MaterialTheme.lwa.size.portrait.minimized.height * 2 + 8.dp), + state = lazyState, + verticalArrangement = Arrangement.spacedBy( + space = 4.dp, + alignment = Alignment.Bottom, + ), + contentPadding = remember { PaddingValues(all = 8.dp) }, ) { - when (it) { - is RollTextMessageUio -> RollTextMessage(message = it) - is PurseTextMessageUio -> PurseTextMessage(message = it) - is DiminishedTextMessageUio -> DiminishedTextMessage(message = it) - is CharacteristicTextMessageUio -> CharacteristicTextMessage(message = it) + items( + items = messages.value, + key = { it.id }, + contentType = { it.javaClass.simpleName } + ) { + when (it) { + is RollTextMessageUio -> RollTextMessage(message = it) + is PurseTextMessageUio -> PurseTextMessage(message = it) + is DiminishedTextMessageUio -> DiminishedTextMessage(message = it) + is CharacteristicTextMessageUio -> CharacteristicTextMessage(message = it) + } } } } + Column { + IconButton( + modifier = Modifier.size(size = 32.dp), + onClick = chatViewModel::toggleChat + ) { + val rotation = animateFloatAsState( + targetValue = if (settings.value.show) 0f else 180f, + ) + Icon( + modifier = Modifier + .size(size = 16.dp) + .graphicsLayer { + this.rotationZ = rotation.value + }, + painter = painterResource(Res.drawable.ic_more_down_24dp), + contentDescription = null, + ) + } + } } } @@ -140,24 +156,16 @@ private fun ChatScrollDownEffect( lazyState: LazyListState, messages: State>, settings: State, - displayChat: suspend () -> Unit, - hideChat: suspend () -> Unit, ) { LaunchedEffect( key1 = messages.value.lastOrNull()?.id, ) { if (messages.value.isNotEmpty()) { - if (settings.value.autoShowChat) { - displayChat() - } - if (settings.value.autoScrollChat) { + if (settings.value.autoScroll) { lazyState.animateScrollToItem( index = messages.value.lastIndex + 1, ) } - if (settings.value.autoHideChat) { - hideChat() - } } } } @@ -167,19 +175,26 @@ private fun ChatScrollDownEffect( private fun rememberAnimatedChatWidth( campaignScreenScope: CampaignLayoutScope = LocalCampaignLayoutScope.current, windowsState: WindowState = LocalWindowState.current, + settings: State, ): State { val chatWidth = remember(windowsState, campaignScreenScope) { derivedStateOf { - val minChatWidth = 64.dp * 8 - val maxChatWidth = 64.dp * 12 - val windowWidth = windowsState.size.width - if (windowWidth != Dp.Unspecified) { - val width = windowWidth - campaignScreenScope.leftPanel.value.width - 16.dp - min(max(width, minChatWidth), maxChatWidth) + if (settings.value.show) { + val minChatWidth = 64.dp * 8 + val maxChatWidth = 64.dp * 12 + val windowWidth = windowsState.size.width + if (windowWidth != Dp.Unspecified) { + val width = windowWidth - campaignScreenScope.leftPanel.value.width - 16.dp + min(max(width, minChatWidth), maxChatWidth) + } else { + minChatWidth + } } else { - minChatWidth + 0.dp } } } - return animateDpAsState(targetValue = chatWidth.value) + return animateDpAsState( + targetValue = chatWidth.value, + ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt index 21baec1..a2fab42 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/text/CampaignChatViewModel.kt @@ -1,7 +1,5 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.text -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.tween import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository @@ -13,22 +11,20 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch class CampaignChatViewModel( private val settingsRepository: SettingsRepository, - private val campaignRepository: CampaignRepository, + campaignRepository: CampaignRepository, networkRepository: NetworkRepository, textMessageFactory: TextMessageFactory, ) : ViewModel() { - val chatAnimatedVisibility = Animatable(0f) - val settings = settingsRepository.settingsFlow().map { ChatSettingsUio( - autoShowChat = it.chat.autoShowChat, - autoScrollChat = it.chat.autoScrollChat, - autoHideChat = it.chat.autoHideChat, + show = it.chat.showChat, + autoScroll = it.chat.autoScrollChat, ) }.stateIn( scope = viewModelScope, @@ -59,20 +55,14 @@ class CampaignChatViewModel( initialValue = emptyList(), ) - suspend fun displayChat() { - chatAnimatedVisibility.animateTo( - targetValue = 1f, - ) - } - - suspend fun hideChat() { - val settings = settingsRepository.settingsFlow().value - chatAnimatedVisibility.animateTo( - targetValue = 0f, - animationSpec = tween( - durationMillis = 2000, - delayMillis = settings.chat.autoHideDelay * 1000, + fun toggleChat() { + viewModelScope.launch { + val settings = settingsRepository.settingsFlow().value + settingsRepository.update( + settings = settings.copy( + chat = settings.chat.copy(showChat = settings.chat.showChat.not()) + ) ) - ) + } } } \ No newline at end of file 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 e7a5dcb..74860c8 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 @@ -10,12 +10,13 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope 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 kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.ic_camping_24dp @@ -36,7 +37,8 @@ fun GMActionPage( ) { val scope = rememberCoroutineScope() val scroll = rememberScrollState() - val actions = viewModel.actions.collectAsState() + val actions = viewModel.actions.collectAsStateWithLifecycle() + val validationDialog = viewModel.validationDialog.collectAsStateWithLifecycle() GMActionContent( actions = actions, @@ -66,6 +68,10 @@ fun GMActionPage( ErrorSnackHandler( error = viewModel.error, ) + + ConfirmationDialog( + dialog = validationDialog, + ) } @Composable 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 new file mode 100644 index 0000000..895f9a0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/action/GMActionUseCase.kt @@ -0,0 +1,58 @@ +package com.pixelized.desktop.lwa.ui.screen.gamemaster.action + +import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository +import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.shared.lwa.protocol.websocket.GameAdminEvent +import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent + +class GMActionUseCase( + private val characterRepository: CharacterSheetRepository, + private val networkRepository: NetworkRepository, + private val campaignRepository: CampaignRepository, +) { + suspend fun invalidateServerCache() { + networkRepository.share( + GameAdminEvent.ServerSynchronization( + timestamp = System.currentTimeMillis(), + ) + ) + } + + suspend fun healPlayerParty() { + campaignRepository.campaignFlow().value.characters.forEach { characterSheetId -> + val sheet = characterRepository.characterDetail( + characterSheetId = characterSheetId, + ) ?: return@forEach + + val updated = sheet.copy( + damage = 0, + fatigue = 0, + diminished = 0, + ) + + if (sheet != updated) { + characterRepository.updateCharacter( + sheet = updated, + create = false, + ) + } + } + } + + suspend fun toggleNpcVisibility() { + networkRepository.share( + GameMasterEvent.ToggleNpc( + timestamp = System.currentTimeMillis(), + ) + ) + } + + suspend fun togglePlayerVisibility() { + networkRepository.share( + GameMasterEvent.TogglePlayer( + timestamp = System.currentTimeMillis(), + ) + ) + } +} \ No newline at end of file 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 3a980c3..c80f8be 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 @@ -3,23 +3,36 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.action import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository -import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.ui.composable.confirmation.ConfirmationDialogUio import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio -import com.pixelized.shared.lwa.protocol.websocket.GameAdminEvent -import com.pixelized.shared.lwa.protocol.websocket.GameMasterEvent import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged 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__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 +import lwacharactersheet.composeapp.generated.resources.game_master__actions__hide_player__title +import lwacharactersheet.composeapp.generated.resources.game_master__actions__on_server_sync__description +import lwacharactersheet.composeapp.generated.resources.game_master__actions__on_server_sync__title +import lwacharactersheet.composeapp.generated.resources.game_master__actions__party_heal__description +import lwacharactersheet.composeapp.generated.resources.game_master__actions__party_heal__title +import lwacharactersheet.composeapp.generated.resources.game_master__actions__show_npc__description +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( - private val characterRepository: CharacterSheetRepository, - private val networkRepository: NetworkRepository, - private val campaignRepository: CampaignRepository, + private val actionUseCase: GMActionUseCase, + campaignRepository: CampaignRepository, ) : ViewModel() { private val _error = MutableSharedFlow() @@ -39,68 +52,102 @@ class GMActionViewModel( initialValue = null, ) + private val _validationDialog = MutableStateFlow(null) + val validationDialog: StateFlow = _validationDialog + suspend fun onServerSync() { - try { - networkRepository.share( - GameAdminEvent.ServerSynchronization( - timestamp = System.currentTimeMillis(), - ) - ) - } catch (exception: Exception) { - val message = ErrorSnackUio.from(exception = exception) - _error.emit(message) - } - } - - suspend fun onPartyHeal() { - campaignRepository.campaignFlow().value.characters.forEach { characterSheetId -> - val sheet = characterRepository.characterDetail( - characterSheetId = characterSheetId, - ) ?: return@forEach - - val updated = sheet.copy( - damage = 0, - fatigue = 0, - diminished = 0, - ) - - if (sheet != updated) { + showConfirmationDialog( + title = Res.string.game_master__actions__on_server_sync__title, + description = Res.string.game_master__actions__on_server_sync__description, + onConfirmationRequest = { try { - characterRepository.updateCharacter( - sheet = updated, - create = false, - ) + actionUseCase.invalidateServerCache() } catch (exception: Exception) { val message = ErrorSnackUio.from(exception = exception) _error.emit(message) } + }, + onDismissRequest = { + _validationDialog.value = null } - } + ) } - suspend fun onNpcVisibility() { - try { - networkRepository.share( - GameMasterEvent.ToggleNpc( - timestamp = System.currentTimeMillis(), - ) - ) - } catch (exception: Exception) { - val message = ErrorSnackUio.from(exception = exception) - _error.emit(message) - } + suspend fun onPartyHeal() { + showConfirmationDialog( + title = Res.string.game_master__actions__party_heal__title, + description = Res.string.game_master__actions__party_heal__description, + onConfirmationRequest = { + try { + actionUseCase.healPlayerParty() + } catch (exception: Exception) { + val message = ErrorSnackUio.from(exception = exception) + _error.emit(message) + } + }, + ) } suspend fun onPlayerVisibility() { - try { - networkRepository.share( - GameMasterEvent.TogglePlayer( - timestamp = System.currentTimeMillis(), - ) - ) - } catch (exception: Exception) { - val message = ErrorSnackUio.from(exception = exception) - _error.emit(message) - } + showConfirmationDialog( + title = when (actions.value?.party) { + true -> Res.string.game_master__actions__hide_player__title + else -> 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 + }, + onConfirmationRequest = { + try { + actionUseCase.togglePlayerVisibility() + } catch (exception: Exception) { + val message = ErrorSnackUio.from(exception = exception) + _error.emit(message) + } + }, + ) + } + + 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 + }, + description = when (actions.value?.npc) { + true -> Res.string.game_master__actions__hide_npc__description + else -> Res.string.game_master__actions__show_npc__description + }, + onConfirmationRequest = { + try { + actionUseCase.toggleNpcVisibility() + } catch (exception: Exception) { + val message = ErrorSnackUio.from(exception = exception) + _error.emit(message) + } + }, + ) + } + + private suspend inline fun showConfirmationDialog( + title: StringResource, + description: StringResource, + crossinline onConfirmationRequest: suspend () -> Unit, + crossinline onDismissRequest: () -> Unit = { _validationDialog.value = null }, + ) { + _validationDialog.value = ConfirmationDialogUio( + title = getString(title), + description = getString(description), + onConfirmRequest = { + viewModelScope.launch { + onConfirmationRequest() + onDismissRequest() + } + }, + onDismissRequest = { + onDismissRequest() + }, + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt index 79899c8..b5e8c3a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterPage.kt @@ -109,7 +109,6 @@ fun GMCharacterPage( .width(width = 128.dp * 4) .fillMaxHeight(), transitionSpec = rememberTransitionAnimation(direction = LayoutDirection.Rtl), - blurController = blurController, detailPanelViewModel = characterDetailViewModel, characterDiminishedViewModel = dismissedViewModel, characteristicDialogViewModel = characteristicDialogViewModel, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt index 6560bb1..42fd420 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsViewModel.kt @@ -15,16 +15,8 @@ import lwacharactersheet.composeapp.generated.resources.ic_fan_focus_24dp import lwacharactersheet.composeapp.generated.resources.ic_format_list_numbered_24dp import lwacharactersheet.composeapp.generated.resources.ic_ifl_24dp import lwacharactersheet.composeapp.generated.resources.ic_timer_24dp -import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp -import lwacharactersheet.composeapp.generated.resources.ic_visibility_off_24dp -import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_delay_description -import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_delay_title -import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_description -import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_hide_title import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_scroll_description import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_scroll_title -import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_show_description -import lwacharactersheet.composeapp.generated.resources.settings__chat_log__auto_show_title import lwacharactersheet.composeapp.generated.resources.settings__chat_log__line_count_description import lwacharactersheet.composeapp.generated.resources.settings__chat_log__line_count_title import lwacharactersheet.composeapp.generated.resources.settings__chat_log__section @@ -92,48 +84,6 @@ class SettingsViewModel( SettingSectionUio( title = Res.string.settings__chat_log__section, ), - SettingToggleItemUio( - icon = Res.drawable.ic_visibility_24dp, - title = Res.string.settings__chat_log__auto_show_title, - description = Res.string.settings__chat_log__auto_show_description, - checked = booleanStates.autoShowChat, - onToggle = { - settingsRepository.update( - settings = settings.value.copy( - chat = settings.value.chat.copy(autoShowChat = it) - ) - ) - }, - ), - SettingToggleItemUio( - icon = Res.drawable.ic_visibility_off_24dp, - title = Res.string.settings__chat_log__auto_hide_title, - description = Res.string.settings__chat_log__auto_hide_description, - checked = booleanStates.autoHideChat, - onToggle = { - settingsRepository.update( - settings = settings.value.copy( - chat = settings.value.chat.copy(autoHideChat = it) - ) - ) - }, - ), - SettingNumberItemUio( - icon = Res.drawable.ic_timer_24dp, - title = Res.string.settings__chat_log__auto_hide_delay_title, - description = Res.string.settings__chat_log__auto_hide_delay_description, - enable = booleanStates.autoHideChat, - value = intStates.autoHideDelay, - onValueChange = { - if (it in 0..999) { - settingsRepository.update( - settings = settings.value.copy( - chat = settings.value.chat.copy(autoHideDelay = it) - ) - ) - } - } - ), SettingToggleItemUio( icon = Res.drawable.ic_fan_focus_24dp, title = Res.string.settings__chat_log__auto_scroll_title, @@ -170,9 +120,6 @@ class SettingsViewModel( settingsRepository.settingsFlow().collect { settings -> booleanStates.dynamicDice.value = settings.portrait.dynamicDice intStates.dynamicDiceDelay.value = settings.portrait.dynamicDiceDelay - booleanStates.autoShowChat.value = settings.chat.autoShowChat - booleanStates.autoHideChat.value = settings.chat.autoHideChat - intStates.autoHideDelay.value = settings.chat.autoHideDelay booleanStates.autoScrollChat.value = settings.chat.autoScrollChat intStates.maxLineCount.value = settings.chat.maxLineCount } @@ -197,15 +144,6 @@ class SettingsViewModel( private val HashMap>.dynamicDiceDelay get() = getOrPut("DYNAMIC_DICE_DELAY") { mutableStateOf(settings.value.portrait.dynamicDiceDelay) } - private val HashMap>.autoShowChat - get() = getOrPut("AUTO_SHOW_CHAT") { mutableStateOf(settings.value.chat.autoShowChat) } - - private val HashMap>.autoHideChat - get() = getOrPut("AUTO_HIDE_CHAT") { mutableStateOf(settings.value.chat.autoHideChat) } - - private val HashMap>.autoHideDelay - get() = getOrPut("AUTO_HIDE_DELAY") { mutableStateOf(settings.value.chat.autoHideDelay) } - private val HashMap>.autoScrollChat get() = getOrPut("AUTO_SCROLL_CHAT") { mutableStateOf(settings.value.chat.autoScrollChat) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt index 98bfedc..968d573 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt @@ -55,7 +55,10 @@ data class LwaColors( @Composable @Stable fun darkLwaColorTheme( - base: Colors = darkColors(), + base: Colors = darkColors( + primary = Color(0xFFBB86FC), + primaryVariant = Color(0xB2BB86FC), + ), elevated: LwaColors.Elevated = LwaColors.Elevated( base1dp = base.calculateElevatedColor( color = base.surface, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt index 357b6ab..d4e325b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/usecase/SettingsUseCase.kt @@ -8,12 +8,10 @@ class SettingsUseCase { playerName = "", portrait = Settings.Portrait( dynamicDice = true, - dynamicDiceDelay = 5000, + dynamicDiceDelay = 8000, ), chat = Settings.Chat( - autoHideChat = true, - autoHideDelay = 8, - autoShowChat = true, + showChat = true, autoScrollChat = true, maxLineCount = 200, ), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6d32363..fb24483 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ koin = "4.0.0" turtle = "0.10.0" logback = "1.5.17" coil = "3.1.0" +zoomable = "2.7.0" ui-graphics-android = "1.7.8" buildkonfig = "0.17.0" @@ -35,6 +36,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- # UI. coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } +engawapg-zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" } # Injection with Koin koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-ktor = { module = "io.insert-koin:koin-ktor", version.ref = "koin" }