This commit was merged in pull request #61.
This commit is contained in:
@@ -11,6 +11,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import org.shahondin1624.lib.components.UiConstants
|
||||
import org.shahondin1624.lib.components.charactermodel.attributespage.Attribute
|
||||
import org.shahondin1624.lib.components.charactermodel.attributespage.Talent
|
||||
import org.shahondin1624.lib.functions.DiceRoll
|
||||
import org.shahondin1624.model.charactermodel.ShadowrunCharacter
|
||||
import org.shahondin1624.theme.LocalWindowSizeClass
|
||||
import org.shahondin1624.theme.WindowSizeClass
|
||||
@@ -35,6 +36,24 @@ fun CharacterSheetPage(character: ShadowrunCharacter, contentPadding: Dp) {
|
||||
var selectedTab by remember { mutableStateOf(CharacterTab.Overview) }
|
||||
val spacing = UiConstants.Spacing.medium(windowSizeClass)
|
||||
|
||||
// Dice roll dialog state
|
||||
var pendingRoll by remember { mutableStateOf<DiceRoll?>(null) }
|
||||
var pendingRollLabel by remember { mutableStateOf("") }
|
||||
|
||||
val onDiceRoll: (DiceRoll, String) -> Unit = { roll, label ->
|
||||
pendingRoll = roll
|
||||
pendingRollLabel = label
|
||||
}
|
||||
|
||||
// Show dice roll result dialog
|
||||
pendingRoll?.let { roll ->
|
||||
DiceRollResultDialog(
|
||||
diceRoll = roll,
|
||||
rollLabel = pendingRollLabel,
|
||||
onDismiss = { pendingRoll = null }
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Tab row
|
||||
TabRow(
|
||||
@@ -55,16 +74,16 @@ fun CharacterSheetPage(character: ShadowrunCharacter, contentPadding: Dp) {
|
||||
// Expanded: two-column layout for Overview and Combat
|
||||
when (selectedTab) {
|
||||
CharacterTab.Overview -> ExpandedOverviewContent(character, spacing)
|
||||
CharacterTab.Attributes -> AttributesContent(character, spacing)
|
||||
CharacterTab.Talents -> TalentsContent(character, spacing)
|
||||
CharacterTab.Attributes -> AttributesContent(character, spacing, onDiceRoll)
|
||||
CharacterTab.Talents -> TalentsContent(character, spacing, onDiceRoll)
|
||||
CharacterTab.Combat -> CombatContent(character, spacing)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when (selectedTab) {
|
||||
CharacterTab.Overview -> OverviewContent(character, spacing)
|
||||
CharacterTab.Attributes -> AttributesContent(character, spacing)
|
||||
CharacterTab.Talents -> TalentsContent(character, spacing)
|
||||
CharacterTab.Attributes -> AttributesContent(character, spacing, onDiceRoll)
|
||||
CharacterTab.Talents -> TalentsContent(character, spacing, onDiceRoll)
|
||||
CharacterTab.Combat -> CombatContent(character, spacing)
|
||||
}
|
||||
}
|
||||
@@ -112,7 +131,11 @@ private fun ExpandedOverviewContent(character: ShadowrunCharacter, spacing: Dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AttributesContent(character: ShadowrunCharacter, spacing: Dp) {
|
||||
private fun AttributesContent(
|
||||
character: ShadowrunCharacter,
|
||||
spacing: Dp,
|
||||
onDiceRoll: (DiceRoll, String) -> Unit
|
||||
) {
|
||||
val windowSizeClass = LocalWindowSizeClass.current
|
||||
val columns = UiConstants.Grid.totalColumns(windowSizeClass)
|
||||
|
||||
@@ -132,7 +155,10 @@ private fun AttributesContent(character: ShadowrunCharacter, spacing: Dp) {
|
||||
) {
|
||||
for (attr in row) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
Attribute(attr)
|
||||
Attribute(
|
||||
attribute = attr,
|
||||
onRoll = { roll -> onDiceRoll(roll, attr.type.name) }
|
||||
)
|
||||
}
|
||||
}
|
||||
repeat(columns - row.size) {
|
||||
@@ -144,7 +170,11 @@ private fun AttributesContent(character: ShadowrunCharacter, spacing: Dp) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TalentsContent(character: ShadowrunCharacter, spacing: Dp) {
|
||||
private fun TalentsContent(
|
||||
character: ShadowrunCharacter,
|
||||
spacing: Dp,
|
||||
onDiceRoll: (DiceRoll, String) -> Unit
|
||||
) {
|
||||
val windowSizeClass = LocalWindowSizeClass.current
|
||||
val totalCols = UiConstants.Grid.totalColumns(windowSizeClass)
|
||||
val talentSpan = UiConstants.Grid.talentSpan(windowSizeClass)
|
||||
@@ -166,7 +196,11 @@ private fun TalentsContent(character: ShadowrunCharacter, spacing: Dp) {
|
||||
) {
|
||||
for (talent in row) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
Talent(talent, character.attributes)
|
||||
Talent(
|
||||
talent = talent,
|
||||
attributes = character.attributes,
|
||||
onRoll = { roll -> onDiceRoll(roll, talent.name) }
|
||||
)
|
||||
}
|
||||
}
|
||||
repeat(talentsPerRow - row.size) {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package org.shahondin1624.lib.components.charactermodel
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.shahondin1624.lib.functions.DiceRoll
|
||||
|
||||
/**
|
||||
* Dialog displaying dice roll results with individual die values.
|
||||
* Successes (5+) highlighted in green, ones highlighted in red.
|
||||
*/
|
||||
@Composable
|
||||
fun DiceRollResultDialog(
|
||||
diceRoll: DiceRoll,
|
||||
rollLabel: String,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val ones = diceRoll.result.count { it == 1 }
|
||||
val isGlitch = ones > diceRoll.numberOfDice / 2
|
||||
val isCriticalGlitch = isGlitch && diceRoll.numberOfSuccesses == 0
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = rollLabel,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Summary row
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "${diceRoll.numberOfDice} dice rolled",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${diceRoll.numberOfSuccesses} successes",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (diceRoll.numberOfSuccesses > 0)
|
||||
Color(0xFF4CAF50) else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
|
||||
// Individual dice results
|
||||
DiceResultGrid(diceRoll.result)
|
||||
|
||||
// Glitch detection
|
||||
if (isCriticalGlitch) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Text(
|
||||
text = "CRITICAL GLITCH!",
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
} else if (isGlitch) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Text(
|
||||
text = "Glitch! ($ones ones out of ${diceRoll.numberOfDice})",
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("OK")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun DiceResultGrid(results: List<Int>) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
for (value in results) {
|
||||
DieChip(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DieChip(value: Int) {
|
||||
val isSuccess = value >= 5
|
||||
val isOne = value == 1
|
||||
|
||||
val backgroundColor = when {
|
||||
isSuccess -> Color(0xFF4CAF50)
|
||||
isOne -> MaterialTheme.colorScheme.error
|
||||
else -> MaterialTheme.colorScheme.surfaceVariant
|
||||
}
|
||||
val textColor = when {
|
||||
isSuccess -> Color.White
|
||||
isOne -> MaterialTheme.colorScheme.onError
|
||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.background(backgroundColor, CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = value.toString(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user