Compare commits
3 commits
bf3fa8177d
...
76fc199d5e
| Author | SHA1 | Date | |
|---|---|---|---|
| 76fc199d5e | |||
| fc06e3ef95 | |||
| 651d05a7c4 |
39 changed files with 1102 additions and 474 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M480,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,546 593,593Q546,640 480,640ZM480,560Q513,560 536.5,536.5Q560,513 560,480Q560,447 536.5,423.5Q513,400 480,400Q447,400 423.5,423.5Q400,447 400,480Q400,513 423.5,536.5Q447,560 480,560ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,600L200,600L200,760Q200,760 200,760Q200,760 200,760L360,760L360,840L200,840ZM600,840L600,760L760,760Q760,760 760,760Q760,760 760,760L760,600L840,600L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840ZM120,360L120,200Q120,167 143.5,143.5Q167,120 200,120L360,120L360,200L200,200Q200,200 200,200Q200,200 200,200L200,360L120,360ZM760,360L760,200Q760,200 760,200Q760,200 760,200L600,200L600,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,360L760,360Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#5f6368">
|
||||
<path d="M200-200v-440h80v360h360v80H200Zm200-200v-440h80v360h360v80H400Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 212 B |
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M156,860L100,804L224,680L120,680L120,600L360,600L360,840L280,840L280,736L156,860ZM804,860L680,736L680,840L600,840L600,600L840,600L840,680L736,680L860,804L804,860ZM120,360L120,280L224,280L100,156L156,100L280,224L280,120L360,120L360,360L120,360ZM600,360L600,120L680,120L680,224L804,100L860,156L736,280L840,280L840,360L600,360Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M120,840L120,600L200,600L200,704L324,580L380,636L256,760L360,760L360,840L120,840ZM600,840L600,760L704,760L580,636L636,580L760,704L760,600L840,600L840,840L600,840ZM324,380L200,256L200,360L120,360L120,120L360,120L360,200L256,200L380,324L324,380ZM636,380L580,324L704,200L600,200L600,120L840,120L840,360L760,360L760,256L636,380Z" />
|
||||
</vector>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 591 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
|
|
@ -269,12 +269,6 @@
|
|||
<string name="settings__player_portrait__dyn_dice_delay_tile">Délai pour les Dés dynamiques</string>
|
||||
<string name="settings__player_portrait__dyn_dice_delay_description">Délai après lequel les dés dynamiques disparaissent.</string>
|
||||
<string name="settings__chat_log__section">Chatlog options</string>
|
||||
<string name="settings__chat_log__auto_show_title">Afficher automatiquement le chat</string>
|
||||
<string name="settings__chat_log__auto_show_description">Affiche automatiquement le chat lors de la réception d'un message</string>
|
||||
<string name="settings__chat_log__auto_hide_title">Cacher automatiquement le chat</string>
|
||||
<string name="settings__chat_log__auto_hide_description">Cache automatiquement le chat au bout d'un certain temps</string>
|
||||
<string name="settings__chat_log__auto_hide_delay_title">Délai pour cacher le chat</string>
|
||||
<string name="settings__chat_log__auto_hide_delay_description">Délai après lequel le chat disparaît</string>
|
||||
<string name="settings__chat_log__auto_scroll_title">Défilement automatique</string>
|
||||
<string name="settings__chat_log__auto_scroll_description">Défilement automatique du chat vers le dernier message reçu lors de la réception de ce dernier.</string>
|
||||
<string name="settings__chat_log__line_count_title">Nombre de lignes de textes visibles</string>
|
||||
|
|
@ -329,5 +323,17 @@
|
|||
<string name="game_master__item__edit_consumable">Consommable</string>
|
||||
<string name="game_master__item__edit_add_alteration">Ajouter une alteration</string>
|
||||
<string name="game_master__character_edit__title">Édition de personnage</string>
|
||||
<string name="game_master__actions__on_server_sync__title">Synchronisation du serveur</string>
|
||||
<string name="game_master__actions__on_server_sync__description">Demander au serveur d'invalider son cache</string>
|
||||
<string name="game_master__actions__party_heal__title">Soigner les personnages joueurs</string>
|
||||
<string name="game_master__actions__party_heal__description">Cette action réinitialisera les points de vie, de pouvoir et d'état diminué de chaque personnage joueur présent dans le groupe.</string>
|
||||
<string name="game_master__actions__hide_player__title">Cacher le groupe de personnages joueur</string>
|
||||
<string name="game_master__actions__hide_player__description">Cacher le panneau latéral gauche pour tous les joueurs.</string>
|
||||
<string name="game_master__actions__show_player__title">Montrer les personnages joueurs</string>
|
||||
<string name="game_master__actions__show_player__description">Montrer le panneau latéral gauche pour tous les joueurs.</string>
|
||||
<string name="game_master__actions__hide_npc__title">Cacher le groupe de npcs</string>
|
||||
<string name="game_master__actions__hide_npc__description">Cacher le panneau latéral droit pour tous les joueurs.</string>
|
||||
<string name="game_master__actions__show_npc__title">Montrer le groupe de npcs</string>
|
||||
<string name="game_master__actions__show_npc__description">Montrer le panneau latéral droit pour tous les joueurs.</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 <T> LwaDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
blur: BlurContentController? = LocalBlurController.current,
|
||||
paddings: PaddingValues = LwaDialogDefault.paddings,
|
||||
color: Color = MaterialTheme.colors.surface,
|
||||
state: State<T?>,
|
||||
|
|
@ -37,6 +41,16 @@ fun <T> LwaDialog(
|
|||
content: @Composable BoxScope.(T) -> Unit,
|
||||
) {
|
||||
state.value?.let { dialog ->
|
||||
|
||||
blur?.let {
|
||||
DisposableEffect("LwaDialog") {
|
||||
blur.show()
|
||||
onDispose {
|
||||
blur.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
content = {
|
||||
|
|
|
|||
|
|
@ -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<CharacterSheetAlterationDialogUio?>,
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<ConfirmationDialogUio?>,
|
||||
) {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Layout>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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<CampaignLayoutScope> {
|
|||
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,
|
||||
|
|
@ -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<DpSize>,
|
||||
val leftOverlay: State<DpSize>,
|
||||
val leftPanel: State<DpSize>,
|
||||
val rightOverlay: State<DpSize>,
|
||||
|
|
|
|||
|
|
@ -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<CharacterDetailPanelUio>.() -> 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,
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ fun CharacterDetailInventory(
|
|||
itemDetailDialogViewModel: ItemDetailDialogViewModel = koinViewModel(),
|
||||
inventory: State<CharacterDetailInventoryUio?>,
|
||||
) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -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(
|
||||
|
|
@ -32,6 +32,7 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
|
|||
|
||||
@Stable
|
||||
data class CharacterRibbonAlterationUio(
|
||||
|
||||
val icon: String,
|
||||
val tooltips: BasicTooltipUio?,
|
||||
)
|
||||
|
|
@ -44,25 +45,22 @@ fun CharacterRibbonAlteration(
|
|||
direction: LayoutDirection,
|
||||
status: List<List<CharacterRibbonAlterationUio>>,
|
||||
) {
|
||||
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),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalLayoutDirection provides currentDirection
|
||||
) {
|
||||
status.forEach { columns ->
|
||||
Column(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalLayoutDirection provides LayoutDirection.Ltr
|
||||
) {
|
||||
columns.forEach {
|
||||
BasicTooltipLayout(
|
||||
|
|
@ -71,12 +69,12 @@ fun CharacterRibbonAlteration(
|
|||
tooltipPlacement = remember(currentDirection) {
|
||||
TooltipPlacement.ComponentRect(
|
||||
anchor = when (direction) {
|
||||
LayoutDirection.Ltr -> Alignment.TopStart
|
||||
LayoutDirection.Rtl -> Alignment.TopEnd
|
||||
LayoutDirection.Ltr -> Alignment.CenterEnd
|
||||
LayoutDirection.Rtl -> Alignment.CenterStart
|
||||
},
|
||||
alignment = when (direction) {
|
||||
LayoutDirection.Ltr -> Alignment.BottomEnd
|
||||
LayoutDirection.Rtl -> Alignment.BottomStart
|
||||
LayoutDirection.Ltr -> Alignment.CenterEnd
|
||||
LayoutDirection.Rtl -> Alignment.CenterStart
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
@ -103,4 +101,3 @@ fun CharacterRibbonAlteration(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,34 +91,23 @@ 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(
|
||||
Row(
|
||||
modifier = modifier.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() }
|
||||
}
|
||||
},
|
||||
),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = settings.value.show,
|
||||
enter = fadeIn() + expandIn(),
|
||||
exit = fadeOut() + shrinkOut(),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
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,
|
||||
|
|
@ -133,6 +129,26 @@ fun CampaignChat(
|
|||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -140,24 +156,16 @@ private fun ChatScrollDownEffect(
|
|||
lazyState: LazyListState,
|
||||
messages: State<List<TextMessage>>,
|
||||
settings: State<ChatSettingsUio>,
|
||||
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,9 +175,11 @@ private fun ChatScrollDownEffect(
|
|||
private fun rememberAnimatedChatWidth(
|
||||
campaignScreenScope: CampaignLayoutScope = LocalCampaignLayoutScope.current,
|
||||
windowsState: WindowState = LocalWindowState.current,
|
||||
settings: State<ChatSettingsUio>,
|
||||
): State<Dp> {
|
||||
val chatWidth = remember(windowsState, campaignScreenScope) {
|
||||
derivedStateOf {
|
||||
if (settings.value.show) {
|
||||
val minChatWidth = 64.dp * 8
|
||||
val maxChatWidth = 64.dp * 12
|
||||
val windowWidth = windowsState.size.width
|
||||
|
|
@ -179,7 +189,12 @@ private fun rememberAnimatedChatWidth(
|
|||
} else {
|
||||
minChatWidth
|
||||
}
|
||||
} else {
|
||||
0.dp
|
||||
}
|
||||
}
|
||||
return animateDpAsState(targetValue = chatWidth.value)
|
||||
}
|
||||
return animateDpAsState(
|
||||
targetValue = chatWidth.value,
|
||||
)
|
||||
}
|
||||
|
|
@ -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() {
|
||||
fun toggleChat() {
|
||||
viewModelScope.launch {
|
||||
val settings = settingsRepository.settingsFlow().value
|
||||
chatAnimatedVisibility.animateTo(
|
||||
targetValue = 0f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 2000,
|
||||
delayMillis = settings.chat.autoHideDelay * 1000,
|
||||
settingsRepository.update(
|
||||
settings = settings.copy(
|
||||
chat = settings.chat.copy(showChat = settings.chat.showChat.not())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ErrorSnackUio>()
|
||||
|
|
@ -39,68 +52,102 @@ class GMActionViewModel(
|
|||
initialValue = null,
|
||||
)
|
||||
|
||||
private val _validationDialog = MutableStateFlow<ConfirmationDialogUio?>(null)
|
||||
val validationDialog: StateFlow<ConfirmationDialogUio?> = _validationDialog
|
||||
|
||||
suspend fun onServerSync() {
|
||||
showConfirmationDialog(
|
||||
title = Res.string.game_master__actions__on_server_sync__title,
|
||||
description = Res.string.game_master__actions__on_server_sync__description,
|
||||
onConfirmationRequest = {
|
||||
try {
|
||||
networkRepository.share(
|
||||
GameAdminEvent.ServerSynchronization(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
actionUseCase.invalidateServerCache()
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(message)
|
||||
}
|
||||
},
|
||||
onDismissRequest = {
|
||||
_validationDialog.value = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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__party_heal__title,
|
||||
description = Res.string.game_master__actions__party_heal__description,
|
||||
onConfirmationRequest = {
|
||||
try {
|
||||
characterRepository.updateCharacter(
|
||||
sheet = updated,
|
||||
create = false,
|
||||
)
|
||||
actionUseCase.healPlayerParty()
|
||||
} catch (exception: Exception) {
|
||||
val message = ErrorSnackUio.from(exception = exception)
|
||||
_error.emit(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 onPlayerVisibility() {
|
||||
showConfirmationDialog(
|
||||
title = when (actions.value?.party) {
|
||||
true -> Res.string.game_master__actions__hide_player__title
|
||||
else -> Res.string.game_master__actions__show_player__title
|
||||
},
|
||||
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 {
|
||||
networkRepository.share(
|
||||
GameMasterEvent.TogglePlayer(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
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()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<String, MutableState<Int>>.dynamicDiceDelay
|
||||
get() = getOrPut("DYNAMIC_DICE_DELAY") { mutableStateOf(settings.value.portrait.dynamicDiceDelay) }
|
||||
|
||||
private val HashMap<String, MutableState<Boolean>>.autoShowChat
|
||||
get() = getOrPut("AUTO_SHOW_CHAT") { mutableStateOf(settings.value.chat.autoShowChat) }
|
||||
|
||||
private val HashMap<String, MutableState<Boolean>>.autoHideChat
|
||||
get() = getOrPut("AUTO_HIDE_CHAT") { mutableStateOf(settings.value.chat.autoHideChat) }
|
||||
|
||||
private val HashMap<String, MutableState<Int>>.autoHideDelay
|
||||
get() = getOrPut("AUTO_HIDE_DELAY") { mutableStateOf(settings.value.chat.autoHideDelay) }
|
||||
|
||||
private val HashMap<String, MutableState<Boolean>>.autoScrollChat
|
||||
get() = getOrPut("AUTO_SCROLL_CHAT") { mutableStateOf(settings.value.chat.autoScrollChat) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue