feat: resource trackers for karma, nuyen, essence, edge (Closes #10)
Add ResourcePanel composable showing current/total karma, nuyen, essence (1 decimal), and Edge. Compact uses 2x2 grid, Medium/Expanded uses single row. Edge uses tertiary container color with star icon for visual distinction. Placed below character header. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
|
|||||||
import org.shahondin1624.lib.components.TestTags
|
import org.shahondin1624.lib.components.TestTags
|
||||||
import org.shahondin1624.lib.components.UiConstants
|
import org.shahondin1624.lib.components.UiConstants
|
||||||
import org.shahondin1624.lib.components.charactermodel.CharacterHeader
|
import org.shahondin1624.lib.components.charactermodel.CharacterHeader
|
||||||
|
import org.shahondin1624.lib.components.charactermodel.ResourcePanel
|
||||||
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
|
import org.shahondin1624.lib.components.charactermodel.attributespage.AttributesPage
|
||||||
import org.shahondin1624.lib.components.settings.SettingsPage
|
import org.shahondin1624.lib.components.settings.SettingsPage
|
||||||
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
import org.shahondin1624.model.EXAMPLE_CHARACTER
|
||||||
@@ -308,6 +309,8 @@ private fun MainScaffold(
|
|||||||
) {
|
) {
|
||||||
CharacterHeader(EXAMPLE_CHARACTER.characterData)
|
CharacterHeader(EXAMPLE_CHARACTER.characterData)
|
||||||
Spacer(Modifier.height(contentPadding))
|
Spacer(Modifier.height(contentPadding))
|
||||||
|
ResourcePanel(EXAMPLE_CHARACTER.characterData, EXAMPLE_CHARACTER.attributes.edge)
|
||||||
|
Spacer(Modifier.height(contentPadding))
|
||||||
AttributesPage(EXAMPLE_CHARACTER)
|
AttributesPage(EXAMPLE_CHARACTER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package org.shahondin1624.lib.components.charactermodel
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.shahondin1624.lib.components.TestTags
|
||||||
|
import org.shahondin1624.lib.components.UiConstants
|
||||||
|
import org.shahondin1624.model.characterdata.CharacterData
|
||||||
|
import org.shahondin1624.theme.LocalWindowSizeClass
|
||||||
|
import org.shahondin1624.theme.WindowSizeClass
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a float to one decimal place without platform-specific String.format.
|
||||||
|
*/
|
||||||
|
private fun formatEssence(value: Float): String {
|
||||||
|
val rounded = (value * 10).roundToInt() / 10.0
|
||||||
|
val intPart = rounded.toInt()
|
||||||
|
val decPart = ((rounded - intPart) * 10).roundToInt()
|
||||||
|
return "$intPart.$decPart"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays resource trackers: Karma (current/total), Nuyen, Essence, and Edge.
|
||||||
|
* Compact: 2x2 grid layout. Expanded: single row.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ResourcePanel(characterData: CharacterData, edge: Int) {
|
||||||
|
val windowSizeClass = LocalWindowSizeClass.current
|
||||||
|
val spacing = UiConstants.Spacing.small(windowSizeClass)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.testTag(TestTags.PANEL_RESOURCES)
|
||||||
|
) {
|
||||||
|
when (windowSizeClass) {
|
||||||
|
WindowSizeClass.Compact -> CompactResources(characterData, edge, spacing)
|
||||||
|
WindowSizeClass.Medium, WindowSizeClass.Expanded -> ExpandedResources(characterData, edge, spacing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CompactResources(characterData: CharacterData, edge: Int, spacing: androidx.compose.ui.unit.Dp) {
|
||||||
|
val padding = UiConstants.Spacing.medium(LocalWindowSizeClass.current)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(padding),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(spacing)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(spacing)
|
||||||
|
) {
|
||||||
|
ResourceItem(
|
||||||
|
label = "Karma",
|
||||||
|
value = "${characterData.currentKarma} / ${characterData.totalKarma}",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
ResourceItem(
|
||||||
|
label = "Nuyen",
|
||||||
|
value = "${characterData.nuyen}\u00A5",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(spacing)
|
||||||
|
) {
|
||||||
|
ResourceItem(
|
||||||
|
label = "Essence",
|
||||||
|
value = formatEssence(characterData.essence),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
EdgeItem(
|
||||||
|
edge = edge,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExpandedResources(characterData: CharacterData, edge: Int, spacing: androidx.compose.ui.unit.Dp) {
|
||||||
|
val padding = UiConstants.Spacing.medium(LocalWindowSizeClass.current)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(padding),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(spacing),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
ResourceItem(
|
||||||
|
label = "Karma",
|
||||||
|
value = "${characterData.currentKarma} / ${characterData.totalKarma}",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
ResourceItem(
|
||||||
|
label = "Nuyen",
|
||||||
|
value = "${characterData.nuyen}\u00A5",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
ResourceItem(
|
||||||
|
label = "Essence",
|
||||||
|
value = formatEssence(characterData.essence),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
EdgeItem(
|
||||||
|
edge = edge,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ResourceItem(label: String, value: String, modifier: Modifier = Modifier) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edge resource item with distinct visual treatment (tertiary color).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun EdgeItem(edge: Int, modifier: Modifier = Modifier) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
|
color = MaterialTheme.colorScheme.tertiaryContainer
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Star,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(14.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onTertiaryContainer
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Edge",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = edge.toString(),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user