Add action to the character sheet detail panel.
This commit is contained in:
parent
b6d02c21be
commit
1fe75062b7
14 changed files with 469 additions and 56 deletions
|
@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class RollHistoryRepository(
|
||||
private val network: NetworkRepository,
|
||||
|
@ -21,23 +20,4 @@ class RollHistoryRepository(
|
|||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
)
|
||||
|
||||
suspend fun share(
|
||||
characterId: String,
|
||||
skillLabel: String,
|
||||
rollDifficulty: String?,
|
||||
rollValue: Int,
|
||||
resultLabel: String?,
|
||||
rollSuccessLimit: Int?,
|
||||
) {
|
||||
val content = RollMessage(
|
||||
characterId = characterId,
|
||||
skillLabel = skillLabel,
|
||||
rollDifficulty = rollDifficulty,
|
||||
rollValue = rollValue,
|
||||
resultLabel = resultLabel,
|
||||
rollSuccessLimit = rollSuccessLimit,
|
||||
)
|
||||
network.share(payload = content)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.circle
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||
import com.pixelized.desktop.lwa.utils.extention.dashedBorder
|
||||
|
||||
@Composable
|
||||
fun MasteryShape(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = CircleShape,
|
||||
color: Color = MaterialTheme.lwa.colorScheme.base.onSurface,
|
||||
borderWidth: Dp = 1.dp,
|
||||
size: Dp = 12.dp,
|
||||
multiplier: Int,
|
||||
) {
|
||||
when (multiplier) {
|
||||
0 -> Box(
|
||||
modifier = modifier
|
||||
.size(size = size)
|
||||
.dashedBorder(
|
||||
width = borderWidth,
|
||||
color = color,
|
||||
shape = shape,
|
||||
on = 3.dp,
|
||||
off = 2.dp
|
||||
)
|
||||
)
|
||||
|
||||
1 -> Box(
|
||||
modifier = modifier
|
||||
.size(size = size)
|
||||
.background(
|
||||
color = color,
|
||||
shape = shape,
|
||||
)
|
||||
)
|
||||
|
||||
else -> Box(
|
||||
modifier = modifier
|
||||
.size(size = size)
|
||||
.border(
|
||||
width = borderWidth,
|
||||
color = color,
|
||||
shape = shape
|
||||
)
|
||||
.padding(all = 2.dp)
|
||||
) {
|
||||
MasteryShape(
|
||||
shape = shape,
|
||||
color = color,
|
||||
borderWidth = borderWidth,
|
||||
size = size - borderWidth * 2,
|
||||
multiplier = multiplier - 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import com.pixelized.desktop.lwa.ui.composable.character.characteristic.Characte
|
|||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeader
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheet
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
|
||||
|
@ -108,6 +109,11 @@ fun CharacterDetailPanel(
|
|||
)
|
||||
}
|
||||
},
|
||||
onAction = {
|
||||
rollViewModel.prepareRoll(roll = it.roll)
|
||||
rollViewModel.showOverlay()
|
||||
blurController.show()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -122,6 +128,7 @@ fun CharacterDetailAnimatedPanel(
|
|||
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
|
||||
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onUseSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onAction: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
|
@ -159,6 +166,7 @@ fun CharacterDetailAnimatedPanel(
|
|||
onCharacteristic = onCharacteristic,
|
||||
onSkill = onSkill,
|
||||
onUseSkill = onUseSkill,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +187,7 @@ fun CharacterDetailContent(
|
|||
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
|
||||
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onUseSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onAction: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
|
@ -206,6 +215,7 @@ fun CharacterDetailContent(
|
|||
onCharacteristic = onCharacteristic,
|
||||
onSkill = onSkill,
|
||||
onUseSkill = onUseSkill,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail
|
|||
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.header.CharacterDetailHeaderUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetActionUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetCharacteristicUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetSkillUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet.CharacterDetailSheetUio
|
||||
|
@ -192,6 +193,7 @@ class CharacterDetailFactory(
|
|||
label = skill.label,
|
||||
value = "$value",
|
||||
used = skill.used,
|
||||
occupation = skill.occupation,
|
||||
tooltips = skill.description?.let {
|
||||
TooltipUio(
|
||||
title = skill.label,
|
||||
|
@ -217,6 +219,7 @@ class CharacterDetailFactory(
|
|||
label = skill.label,
|
||||
value = "$value",
|
||||
used = skill.used,
|
||||
occupation = skill.occupation,
|
||||
tooltips = skill.description?.let {
|
||||
TooltipUio(
|
||||
title = skill.label,
|
||||
|
@ -231,7 +234,7 @@ class CharacterDetailFactory(
|
|||
),
|
||||
)
|
||||
}.sortedWith(compareBy(Collator.getInstance()) { it.label }),
|
||||
magicSkill = characterSheet.magicSkills.map { skill ->
|
||||
magicSkills = characterSheet.magicSkills.map { skill ->
|
||||
val value = expressionUseCase.computeSkillValue(
|
||||
sheet = characterSheet,
|
||||
skill = skill,
|
||||
|
@ -242,6 +245,7 @@ class CharacterDetailFactory(
|
|||
label = skill.label,
|
||||
value = "$value",
|
||||
used = skill.used,
|
||||
occupation = skill.occupation,
|
||||
tooltips = skill.description?.let {
|
||||
TooltipUio(
|
||||
title = skill.label,
|
||||
|
@ -256,6 +260,24 @@ class CharacterDetailFactory(
|
|||
),
|
||||
)
|
||||
}.sortedWith(compareBy(Collator.getInstance()) { it.label }),
|
||||
actions = characterSheet.actions.map { action ->
|
||||
CharacterDetailSheetActionUio(
|
||||
actionId = action.id,
|
||||
label = action.label,
|
||||
tooltips = action.description?.let {
|
||||
TooltipUio(
|
||||
title = action.label,
|
||||
description = it,
|
||||
)
|
||||
},
|
||||
roll = RollActionUio(
|
||||
characterSheetId = characterInstanceId.characterSheetId,
|
||||
label = action.label,
|
||||
rollAction = action.roll,
|
||||
rollSuccessValue = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,25 +1,36 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__common_title
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__magic_title
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet__skills__special_title
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@Stable
|
||||
data class CharacterDetailSheetUio(
|
||||
val characterInstanceId: Campaign.CharacterInstance.Id,
|
||||
val characteristics: List<CharacterDetailSheetCharacteristicUio>,
|
||||
val commonSkills: List<CharacterDetailSheetSkillUio>,
|
||||
val specialSkill: List<CharacterDetailSheetSkillUio>,
|
||||
val magicSkill: List<CharacterDetailSheetSkillUio>,
|
||||
val specialSkills: List<CharacterDetailSheetSkillUio>,
|
||||
val magicSkills: List<CharacterDetailSheetSkillUio>,
|
||||
val actions: List<CharacterDetailSheetActionUio>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
@ -29,6 +40,7 @@ fun CharacterDetailSheet(
|
|||
onCharacteristic: (CharacterDetailSheetCharacteristicUio) -> Unit,
|
||||
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onUseSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onAction: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
|
@ -39,7 +51,7 @@ fun CharacterDetailSheet(
|
|||
) {
|
||||
sheet.value?.characteristics?.forEach {
|
||||
CharacterDetailSheetCharacteristic(
|
||||
modifier = Modifier.size(width = 80.dp, height = 120.dp),
|
||||
modifier = Modifier.size(width = 76.dp, height = 110.dp),
|
||||
characteristic = it,
|
||||
onClick = { onCharacteristic(it) },
|
||||
)
|
||||
|
@ -47,12 +59,20 @@ fun CharacterDetailSheet(
|
|||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
|
||||
) {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp, top = 4.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(Res.string.character_sheet__skills__common_title),
|
||||
)
|
||||
sheet.value?.commonSkills?.forEach { skill ->
|
||||
CharacterDetailSheetSkill(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
@ -63,11 +83,22 @@ fun CharacterDetailSheet(
|
|||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = sheet.value?.specialSkills?.isNotEmpty() ?: false,
|
||||
) {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
sheet.value?.specialSkill?.forEach { skill ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp, top = 4.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(Res.string.character_sheet__skills__special_title),
|
||||
)
|
||||
sheet.value?.specialSkills?.forEach { skill ->
|
||||
CharacterDetailSheetSkill(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
skill = skill,
|
||||
|
@ -77,11 +108,23 @@ fun CharacterDetailSheet(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = sheet.value?.magicSkills?.isNotEmpty() ?: false,
|
||||
) {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
sheet.value?.magicSkill?.forEach { skill ->
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp, top = 4.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(Res.string.character_sheet__skills__magic_title),
|
||||
)
|
||||
sheet.value?.magicSkills?.forEach { skill ->
|
||||
CharacterDetailSheetSkill(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
skill = skill,
|
||||
|
@ -92,6 +135,20 @@ fun CharacterDetailSheet(
|
|||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = sheet.value?.actions?.isNotEmpty() ?: false,
|
||||
) {
|
||||
Column {
|
||||
sheet.value?.actions?.forEach { action ->
|
||||
CharacterDetailSheetAction(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
action = action,
|
||||
onClick = onAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.sheet
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.roll.RollActionUio
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Stable
|
||||
data class CharacterDetailSheetActionUio(
|
||||
val actionId: String,
|
||||
val label: String,
|
||||
val tooltips: TooltipUio?,
|
||||
val roll: RollActionUio,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun CharacterDetailSheetAction(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(start = 28.dp, end = 9.dp, top = 6.dp, bottom = 6.dp),
|
||||
action: CharacterDetailSheetActionUio,
|
||||
onClick: (CharacterDetailSheetActionUio) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = { onClick(action) })
|
||||
.padding(paddingValues = paddingValues)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.body1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = action.label,
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.size(size = 24.dp),
|
||||
painter = painterResource(Res.drawable.ic_d20_24dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -5,8 +5,11 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.CheckboxDefaults
|
||||
import androidx.compose.material.MaterialTheme
|
||||
|
@ -18,6 +21,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.composable.circle.MasteryShape
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipLayout
|
||||
import com.pixelized.desktop.lwa.ui.composable.tooltip.TooltipUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.roll.RollActionUio
|
||||
|
@ -28,6 +32,7 @@ data class CharacterDetailSheetSkillUio(
|
|||
val label: String,
|
||||
val value: String,
|
||||
val used: Boolean,
|
||||
val occupation: Boolean,
|
||||
val tooltips: TooltipUio?,
|
||||
val roll: RollActionUio,
|
||||
)
|
||||
|
@ -36,7 +41,12 @@ data class CharacterDetailSheetSkillUio(
|
|||
@Composable
|
||||
fun CharacterDetailSheetSkill(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(start = 8.dp),
|
||||
paddingValues: PaddingValues = PaddingValues(
|
||||
start = 8.dp,
|
||||
end = 4.dp,
|
||||
top = 1.dp,
|
||||
bottom = 1.dp
|
||||
),
|
||||
skill: CharacterDetailSheetSkillUio,
|
||||
onSkill: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
onUse: (CharacterDetailSheetSkillUio) -> Unit,
|
||||
|
@ -52,6 +62,10 @@ fun CharacterDetailSheetSkill(
|
|||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
MasteryShape(
|
||||
modifier = Modifier.padding(top = 4.dp, end = 2.dp),
|
||||
multiplier = if (skill.occupation) 1 else 0,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.body1,
|
||||
|
|
|
@ -7,10 +7,11 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio
|
||||
import com.pixelized.desktop.lwa.ui.screen.roll.DifficultyUio.Difficulty
|
||||
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
|
||||
import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
|
||||
import com.pixelized.shared.lwa.usecase.ExpressionUseCase
|
||||
import com.pixelized.shared.lwa.usecase.SkillStepUseCase
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -32,9 +33,9 @@ import org.jetbrains.compose.resources.getString
|
|||
|
||||
class RollViewModel(
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val rollHistoryRepository: RollHistoryRepository,
|
||||
private val skillComputation: ExpressionUseCase,
|
||||
private val skillStepUseCase: SkillStepUseCase,
|
||||
private val networkRepository: NetworkRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private lateinit var sheet: CharacterSheet
|
||||
|
@ -205,7 +206,7 @@ class RollViewModel(
|
|||
value = roll,
|
||||
)
|
||||
launch {
|
||||
rollHistoryRepository.share(
|
||||
val payload = RollMessage(
|
||||
characterId = sheet.id,
|
||||
skillLabel = _rollTitle.value.label,
|
||||
rollDifficulty = when (_rollDifficulty.value?.difficulty) {
|
||||
|
@ -219,6 +220,9 @@ class RollViewModel(
|
|||
rollSuccessLimit = rollStep?.success?.last,
|
||||
resultLabel = success,
|
||||
)
|
||||
networkRepository.share(
|
||||
payload = payload,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import com.pixelized.shared.lwa.protocol.websocket.Message
|
|||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readText
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
|
||||
fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
||||
val json = frame.readText()
|
||||
|
@ -12,6 +11,6 @@ fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
|||
}
|
||||
|
||||
fun Json.encodeToFrame(message: Message): Frame {
|
||||
val json = encodeToJsonElement(message)
|
||||
return Frame.Text(text = json.toString())
|
||||
val json = encodeToString(message)
|
||||
return Frame.Text(text = json)
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.pixelized.desktop.lwa.utils.extention
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.geometry.isSimple
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
|
||||
/**
|
||||
* Modify element to add border with appearance specified with a [border] and a [shape], pad the
|
||||
* content by the [BorderStroke.width] and clip it.
|
||||
*
|
||||
* @sample androidx.compose.foundation.samples.BorderSample()
|
||||
*
|
||||
* @param border [BorderStroke] class that specifies border appearance, such as size and color
|
||||
* @param shape shape of the border
|
||||
*/
|
||||
fun Modifier.dashedBorder(border: BorderStroke, shape: Shape = RectangleShape, on: Dp, off: Dp) =
|
||||
dashedBorder(width = border.width, brush = border.brush, shape = shape, on, off)
|
||||
|
||||
/**
|
||||
* Returns a [Modifier] that adds border with appearance specified with [width], [color] and a
|
||||
* [shape], pads the content by the [width] and clips it.
|
||||
*
|
||||
* @sample androidx.compose.foundation.samples.BorderSampleWithDataClass()
|
||||
*
|
||||
* @param width width of the border. Use [Dp.Hairline] for a hairline border.
|
||||
* @param color color to paint the border with
|
||||
* @param shape shape of the border
|
||||
* @param on the size of the solid part of the dashes
|
||||
* @param off the size of the space between dashes
|
||||
*/
|
||||
fun Modifier.dashedBorder(width: Dp, color: Color, shape: Shape = RectangleShape, on: Dp, off: Dp) =
|
||||
dashedBorder(width, SolidColor(color), shape, on, off)
|
||||
|
||||
/**
|
||||
* Returns a [Modifier] that adds border with appearance specified with [width], [brush] and a
|
||||
* [shape], pads the content by the [width] and clips it.
|
||||
*
|
||||
* @sample androidx.compose.foundation.samples.BorderSampleWithBrush()
|
||||
*
|
||||
* @param width width of the border. Use [Dp.Hairline] for a hairline border.
|
||||
* @param brush brush to paint the border with
|
||||
* @param shape shape of the border
|
||||
*/
|
||||
fun Modifier.dashedBorder(width: Dp, brush: Brush, shape: Shape, on: Dp, off: Dp): Modifier =
|
||||
this.then(
|
||||
Modifier.drawWithCache {
|
||||
val outline: Outline = shape.createOutline(size, layoutDirection, this)
|
||||
val borderSize = if (width == Dp.Hairline) 1f else width.toPx()
|
||||
|
||||
var insetOutline: Outline? = null // outline used for roundrect/generic shapes
|
||||
var stroke: Stroke? = null // stroke to draw border for all outline types
|
||||
var pathClip: Path? = null // path to clip roundrect/generic shapes
|
||||
var inset = 0f // inset to translate before drawing the inset outline
|
||||
// path to draw generic shapes or roundrects with different corner radii
|
||||
var insetPath: Path? = null
|
||||
if (borderSize > 0 && size.minDimension > 0f) {
|
||||
if (outline is Outline.Rectangle) {
|
||||
stroke = Stroke(
|
||||
borderSize, pathEffect = PathEffect.dashPathEffect(
|
||||
floatArrayOf(on.toPx(), off.toPx())
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// Multiplier to apply to the border size to get a stroke width that is
|
||||
// large enough to cover the corners while not being too large to overly
|
||||
// square off the internal shape. The resultant shape will be
|
||||
// clipped to the desired shape. Any value lower will show artifacts in
|
||||
// the corners of shapes. A value too large will always square off
|
||||
// the internal shape corners. For example, for a rounded rect border
|
||||
// a large multiplier will always have squared off edges within the
|
||||
// inner section of the stroke, however, having a smaller multiplier
|
||||
// will still keep the rounded effect for the inner section of the
|
||||
// border
|
||||
val strokeWidth = 1.2f * borderSize
|
||||
inset = borderSize - strokeWidth / 2
|
||||
val insetSize = Size(
|
||||
size.width - inset * 2,
|
||||
size.height - inset * 2
|
||||
)
|
||||
insetOutline = shape.createOutline(insetSize, layoutDirection, this)
|
||||
stroke = Stroke(
|
||||
strokeWidth, pathEffect = PathEffect.dashPathEffect(
|
||||
floatArrayOf(on.toPx(), off.toPx())
|
||||
)
|
||||
)
|
||||
pathClip = when (outline) {
|
||||
is Outline.Rounded -> {
|
||||
Path().apply { addRoundRect(outline.roundRect) }
|
||||
}
|
||||
|
||||
is Outline.Generic -> {
|
||||
outline.path
|
||||
}
|
||||
// should not get here because we check for Outline.Rectangle above
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
insetPath = when {
|
||||
// Rounded rect with non equal corner radii needs a path to be pre-translated
|
||||
insetOutline is Outline.Rounded && !insetOutline.roundRect.isSimple -> {
|
||||
Path().apply {
|
||||
addRoundRect(insetOutline.roundRect)
|
||||
translate(Offset(inset, inset))
|
||||
}
|
||||
}
|
||||
// Generic paths must be created and pre-translated
|
||||
insetOutline is Outline.Generic -> {
|
||||
|
||||
Path().apply {
|
||||
addPath(insetOutline.path, Offset(inset, inset))
|
||||
}
|
||||
}
|
||||
// Drawing a round rect with equal corner radii without usage of a path
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
// Only draw the border if a have a valid stroke parameter. If we have
|
||||
// an invalid border size we will just draw the content
|
||||
if (stroke != null) {
|
||||
if (insetOutline != null && pathClip != null) {
|
||||
val isSimpleRoundRect =
|
||||
insetOutline is Outline.Rounded && insetOutline.roundRect.isSimple
|
||||
withTransform({
|
||||
clipPath(pathClip)
|
||||
// we are drawing the round rect not as a path so we must
|
||||
// translate ourselves othe
|
||||
if (isSimpleRoundRect) {
|
||||
translate(inset, inset)
|
||||
}
|
||||
}) {
|
||||
if (isSimpleRoundRect) {
|
||||
// If we don't have an insetPath then we are drawing
|
||||
// a simple round rect with the corner radii all identical
|
||||
val rrect = (insetOutline as Outline.Rounded).roundRect
|
||||
drawRoundRect(
|
||||
brush = brush,
|
||||
topLeft = Offset(rrect.left, rrect.top),
|
||||
size = Size(rrect.width, rrect.height),
|
||||
cornerRadius = rrect.topLeftCornerRadius,
|
||||
style = stroke
|
||||
)
|
||||
} else if (insetPath != null) {
|
||||
drawPath(insetPath, brush, style = stroke)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Rectangular border fast path
|
||||
val strokeWidth = stroke.width
|
||||
val halfStrokeWidth = strokeWidth / 2
|
||||
drawRect(
|
||||
brush = brush,
|
||||
topLeft = Offset(halfStrokeWidth, halfStrokeWidth),
|
||||
size = Size(
|
||||
size.width - strokeWidth,
|
||||
size.height - strokeWidth
|
||||
),
|
||||
style = stroke
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(projects.shared)
|
||||
implementation(libs.logback)
|
||||
implementation(libs.koin.ktor)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.ktor.server.core)
|
||||
implementation(libs.ktor.server.netty)
|
||||
implementation(libs.ktor.server.websockets)
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.pixelized.shared.lwa.protocol.websocket.Message
|
|||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readText
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
|
||||
|
||||
fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
||||
val json = frame.readText()
|
||||
|
@ -12,6 +12,6 @@ fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
|||
}
|
||||
|
||||
fun Json.encodeToFrame(message: Message): Frame {
|
||||
val json = encodeToJsonElement(message)
|
||||
return Frame.Text(text = json.toString())
|
||||
val json = encodeToString(message)
|
||||
return Frame.Text(text = json)
|
||||
}
|
|
@ -81,7 +81,8 @@ class LocalServer {
|
|||
val job = launch {
|
||||
// send local message to the clients
|
||||
engine.webSocket.collect { message ->
|
||||
send(json.encodeToFrame(message))
|
||||
val frame = json.encodeToFrame(message)
|
||||
send(frame)
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
|
|
|
@ -6,8 +6,8 @@ import kotlinx.serialization.Serializable
|
|||
data class RollMessage(
|
||||
val characterId: String,
|
||||
val skillLabel: String,
|
||||
val resultLabel: String?,
|
||||
val rollDifficulty: String?,
|
||||
val resultLabel: String? = null,
|
||||
val rollDifficulty: String? = null,
|
||||
val rollValue: Int,
|
||||
val rollSuccessLimit: Int?,
|
||||
val rollSuccessLimit: Int? = null,
|
||||
) : MessagePayload
|
Loading…
Add table
Add a link
Reference in a new issue