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,30 +83,67 @@ fun CharacterDetailSheet(
 | 
			
		|||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            DecoratedBox(
 | 
			
		||||
                modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
            AnimatedVisibility(
 | 
			
		||||
                visible = sheet.value?.specialSkills?.isNotEmpty() ?: false,
 | 
			
		||||
            ) {
 | 
			
		||||
                Column {
 | 
			
		||||
                    sheet.value?.specialSkill?.forEach { skill ->
 | 
			
		||||
                        CharacterDetailSheetSkill(
 | 
			
		||||
                            modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                            skill = skill,
 | 
			
		||||
                            onSkill = onSkill,
 | 
			
		||||
                            onUse = onUseSkill,
 | 
			
		||||
                DecoratedBox(
 | 
			
		||||
                    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__special_title),
 | 
			
		||||
                        )
 | 
			
		||||
                        sheet.value?.specialSkills?.forEach { skill ->
 | 
			
		||||
                            CharacterDetailSheetSkill(
 | 
			
		||||
                                modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                                skill = skill,
 | 
			
		||||
                                onSkill = onSkill,
 | 
			
		||||
                                onUse = onUseSkill,
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            DecoratedBox(
 | 
			
		||||
                modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
            AnimatedVisibility(
 | 
			
		||||
                visible = sheet.value?.magicSkills?.isNotEmpty() ?: false,
 | 
			
		||||
            ) {
 | 
			
		||||
                DecoratedBox(
 | 
			
		||||
                    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__magic_title),
 | 
			
		||||
                        )
 | 
			
		||||
                        sheet.value?.magicSkills?.forEach { skill ->
 | 
			
		||||
                            CharacterDetailSheetSkill(
 | 
			
		||||
                                modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                                skill = skill,
 | 
			
		||||
                                onSkill = onSkill,
 | 
			
		||||
                                onUse = onUseSkill,
 | 
			
		||||
                            )
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            AnimatedVisibility(
 | 
			
		||||
                visible = sheet.value?.actions?.isNotEmpty() ?: false,
 | 
			
		||||
            ) {
 | 
			
		||||
                Column {
 | 
			
		||||
                    sheet.value?.magicSkill?.forEach { skill ->
 | 
			
		||||
                        CharacterDetailSheetSkill(
 | 
			
		||||
                    sheet.value?.actions?.forEach { action ->
 | 
			
		||||
                        CharacterDetailSheetAction(
 | 
			
		||||
                            modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                            skill = skill,
 | 
			
		||||
                            onSkill = onSkill,
 | 
			
		||||
                            onUse = onUseSkill,
 | 
			
		||||
                            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
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue