292 lines
8.0 KiB
Kotlin
292 lines
8.0 KiB
Kotlin
/*
|
|
* MainActivity.kt - Jetpack Compose 2.0+ Game UI Entry Point
|
|
*
|
|
* Production-ready example demonstrating:
|
|
* - Material 3 dynamic theming
|
|
* - DDA 3.0 integration
|
|
* - Accessibility patterns (WCAG 3.0+)
|
|
* - Spatial computing (XR, eye-tracking)
|
|
* - Ethical engagement mechanics
|
|
*
|
|
* © 2026 - Agent Guardrails Template
|
|
*/
|
|
|
|
package com.agentguardrails.composeui
|
|
|
|
import android.os.Bundle
|
|
import androidx.activity.ComponentActivity
|
|
import androidx.activity.compose.setContent
|
|
import androidx.compose.foundation.layout.*
|
|
import androidx.compose.material3.*
|
|
import androidx.compose.runtime.*
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.vector
|
|
import androidx.compose.haptic.HapticFeedback
|
|
import androidx.compose.animation.core.*
|
|
import androidx.game.core.DDAManager
|
|
import androidx.game.accessibility.AccessibilityPreferences
|
|
import androidx.game.ethical.EthicalEngagement
|
|
|
|
/**
|
|
* Main Activity demonstrating Jetpack Compose 2.0+ game UI patterns
|
|
*/
|
|
class MainActivity : ComponentActivity() {
|
|
|
|
private val ddaManager = DDAManager()
|
|
private val accessibilityPrefs = AccessibilityPreferences()
|
|
private val ethicalEngagement = EthicalEngagement()
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
|
|
setContent {
|
|
val gameState = rememberUpdatedState(GameState())
|
|
val theme = deriveMaterial3Theme(gameState.value)
|
|
|
|
MaterialTheme(
|
|
colorScheme = theme.colorScheme,
|
|
typography = theme.typography,
|
|
shapes = theme.shapes
|
|
) {
|
|
GameUI(
|
|
gameState = gameState.value,
|
|
ddaTier = ddaManager.currentTier,
|
|
accessibilityMode = accessibilityPrefs.highContrastMode,
|
|
onRestStateTrigger = { ethicalEngagement.enterRestState() }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derives Material 3 theme from game state
|
|
* Uses dynamic color extraction for immersive theming
|
|
*/
|
|
@Composable
|
|
private fun deriveMaterial3Theme(gameState: GameState): MaterialTheme {
|
|
val primaryColor = MaterialColorUtilities.extractFromPalette(gameState.primaryColor)
|
|
|
|
return MaterialTheme(
|
|
colorScheme = ColorScheme(
|
|
primary = primaryColor,
|
|
secondary = gameState.secondaryColor,
|
|
tertiary = gameState.accentColor,
|
|
surface = Color(0xFF1A1A2E),
|
|
background = Color(0xFF0F0F1A)
|
|
),
|
|
typography = Typography(
|
|
defaultFontFamily = androidx.compose.ui.text.GameFont
|
|
),
|
|
shapes = Shapes(
|
|
small = androidx.compose.ui.graphics.OutlinedShape,
|
|
medium = androidx.compose.ui.graphics.ElevatedShape,
|
|
large = androidx.compose.ui.graphics.HeroShape
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Main Game UI Container
|
|
* Integrates core loop phases with Material 3 components
|
|
*/
|
|
@Composable
|
|
private fun GameUI(
|
|
gameState: GameState,
|
|
ddaTier: DDAManager.Tier,
|
|
accessibilityMode: Boolean,
|
|
onRestStateTrigger: () -> Unit
|
|
) {
|
|
Column(modifier = Modifier.fillMaxSize()) {
|
|
// Combat HUD - Action Phase
|
|
CombatHUD(
|
|
health = gameState.playerHealth,
|
|
enemyHealth = gameState.enemyHealth,
|
|
ddaTier = ddaTier,
|
|
modifier = Modifier.weight(1f)
|
|
)
|
|
|
|
// Loot Panel - Reward Phase
|
|
LootPanel(
|
|
lootTable = gameState.currentLootTable,
|
|
pityTimer = gameState.pityTimer,
|
|
modifier = Modifier.weight(1f)
|
|
)
|
|
|
|
// Skill Tree - Upgrade Phase
|
|
SkillTree(
|
|
skillPoints = gameState.skillPoints,
|
|
unlockedSkills = gameState.unlockedSkills,
|
|
modifier = Modifier.weight(1f)
|
|
)
|
|
|
|
// Rest State Indicator - Ethical Engagement
|
|
RestStateIndicator(
|
|
sessionDuration = gameState.sessionDuration,
|
|
onTrigger = onRestStateTrigger,
|
|
modifier = Modifier.weight(0.5f)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Combat HUD with reactive health bars
|
|
* Demonstrates DDA 3.0 visual adaptation
|
|
*/
|
|
@Composable
|
|
private fun CombatHUD(
|
|
health: Int,
|
|
enemyHealth: Int,
|
|
ddaTier: DDAManager.Tier,
|
|
modifier: Modifier
|
|
) {
|
|
val healthState = if (health < 30) HealthState.Critical else HealthState.Normal
|
|
|
|
Box(modifier = modifier.fillMaxWidth()) {
|
|
Row {
|
|
// Player Health - Shape + Color redundancy for colorblindness
|
|
HealthBar(
|
|
value = health,
|
|
state = healthState,
|
|
shapeIndicator = healthState.shape, // WCAG 3.0+ compliance
|
|
ddaOpacity = ddaTier.uiOpacityFactor
|
|
)
|
|
|
|
// Enemy Health
|
|
EnemyHealthBar(
|
|
value = enemyHealth,
|
|
ddaTier = ddaTier
|
|
)
|
|
}
|
|
|
|
// Eye-tracking dwell indicator
|
|
EyeTrackingDwellIndicator(
|
|
threshold = 150ms,
|
|
onSelection = { /* handle selection */ }
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loot Panel with transparent drop rates
|
|
* Ethical engagement: no obfuscated RNG
|
|
*/
|
|
@Composable
|
|
private fun LootPanel(
|
|
lootTable: LootTable,
|
|
pityTimer: Int,
|
|
modifier: Modifier
|
|
) {
|
|
Box(modifier = modifier.fillMaxWidth()) {
|
|
Column {
|
|
Text("Loot Table Transparency", style = MaterialTheme.typography.labelLarge)
|
|
|
|
// Inline drop rate display
|
|
lootTable.items.forEach { item ->
|
|
LootItemRow(
|
|
item = item,
|
|
dropRate = item.dropRate,
|
|
pityTimerCount = pityTimer
|
|
)
|
|
}
|
|
|
|
// Pity timer visualization
|
|
PityTimerProgress(
|
|
currentCount = pityTimer,
|
|
maxCount = lootTable.pityTimerMax
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Skill Tree with Compose Canvas rendering
|
|
* Upgrade phase of core loop
|
|
*/
|
|
@Composable
|
|
private fun SkillTree(
|
|
skillPoints: Int,
|
|
unlockedSkills: List<Skill>,
|
|
modifier: Modifier
|
|
) {
|
|
Canvas(modifier = modifier.fillMaxSize()) {
|
|
// Render skill tree nodes with Z-depth parallax
|
|
unlockedSkills.forEach { skill ->
|
|
drawSkillNode(
|
|
skill = skill,
|
|
depthFactor = 0.3f, // Parallax for spatial computing
|
|
isUnlocked = skill.unlocked
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rest State Mechanic - Ethical Engagement
|
|
* Mandatory calm state after intense sessions
|
|
*/
|
|
@Composable
|
|
private fun RestStateIndicator(
|
|
sessionDuration: Int,
|
|
onTrigger: () -> Unit
|
|
) {
|
|
val restStateTimer = remember { mutableStateOf(0) }
|
|
|
|
if (sessionDuration > 45) {
|
|
Box(modifier = Modifier.background(Color(0xFF2D4A2D))) {
|
|
Text(
|
|
"Rest State Activated - Calm Mode",
|
|
style = MaterialTheme.typography.bodySmall
|
|
)
|
|
restStateTimer.value++
|
|
if (restStateTimer.value >= 5) {
|
|
onTrigger()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Haptic feedback integration for UI interactions
|
|
*/
|
|
private fun triggerHapticFeedback(event: UIEvent) {
|
|
val profile = HapticProfile.fromEvent(event)
|
|
HapticFeedback.applyProfile(profile)
|
|
}
|
|
|
|
// Placeholder data classes for compilation
|
|
data class GameState(
|
|
val primaryColor: Color = Color(0xFF3B82F6),
|
|
val secondaryColor: Color = Color(0xFF10B981),
|
|
val accentColor: Color = Color(0xFFF59E0B),
|
|
val playerHealth: Int = 100,
|
|
val enemyHealth: Int = 100,
|
|
val currentLootTable: LootTable = LootTable(),
|
|
val pityTimer: Int = 0,
|
|
val skillPoints: Int = 5,
|
|
val unlockedSkills: List<Skill> = emptyList(),
|
|
val sessionDuration: Int = 0
|
|
)
|
|
|
|
data class LootTable(
|
|
val items: List<LootItem> = emptyList(),
|
|
val pityTimerMax: Int = 10
|
|
)
|
|
|
|
data class LootItem(
|
|
val name: String,
|
|
val dropRate: Float,
|
|
val rarity: Rarity
|
|
)
|
|
|
|
enum class Rarity { COMMON, RARE, EPIC, LEGENDARY }
|
|
|
|
data class Skill(
|
|
val id: String,
|
|
val unlocked: Boolean
|
|
)
|
|
|
|
enum class HealthState { Normal, Critical }
|
|
|
|
enum class UIEvent { COMBAT_SUCCESS, LOOT_REVEAL, SKILL_UNLOCK } |