Generated UTC: 2026-08-02T08:16:39+00:00 Host: Work Mobile root: /home/ray/ARMOR/apps/shire-mobile ============================================================ GIT STATUS ============================================================ Not an independent Git worktree. ============================================================ PROJECT FILES ============================================================ /home/ray/ARMOR/apps/shire-mobile/.gradle/9.4.1/gc.properties /home/ray/ARMOR/apps/shire-mobile/.gradle/buildOutputCleanup/cache.properties /home/ray/ARMOR/apps/shire-mobile/.gradle/vcs-1/gc.properties /home/ray/ARMOR/apps/shire-mobile/app/build.gradle.kts /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/app_metadata/debug/writeDebugAppMetadata/app-metadata.properties /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/compatible_screen_manifest/debug/createDebugCompatibleScreenManifests/output-metadata.json /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/incremental/debug/mergeDebugResources/compile-file-map.properties /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/incremental/debug/mergeDebugResources/merger.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/incremental/debug/packageDebugResources/merger.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/incremental/mergeDebugAssets/merger.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/linked_resources_binary_format/debug/processDebugResources/output-metadata.json /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/merged_manifest/debug/processDebugMainManifest/AndroidManifest.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/merged_manifests/debug/processDebugManifest/AndroidManifest.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/merged_manifests/debug/processDebugManifest/output-metadata.json /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/packaged_manifests/debug/processDebugManifestForPackage/AndroidManifest.xml /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/packaged_manifests/debug/processDebugManifestForPackage/output-metadata.json /home/ray/ARMOR/apps/shire-mobile/app/build/intermediates/signing_config_versions/debug/writeDebugSigningConfigVersions/signing-config-versions.json /home/ray/ARMOR/apps/shire-mobile/app/build/outputs/apk/debug/output-metadata.json /home/ray/ARMOR/apps/shire-mobile/app/src/main/AndroidManifest.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/drawable/shire_icon_foreground.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/drawable/shire_icon_monochrome.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi/ic_launcher.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/icon_colors.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/strings.xml /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/themes.xml /home/ray/ARMOR/apps/shire-mobile/build.gradle.kts /home/ray/ARMOR/apps/shire-mobile/gradle.properties /home/ray/ARMOR/apps/shire-mobile/gradle/wrapper/gradle-wrapper.properties /home/ray/ARMOR/apps/shire-mobile/local.properties /home/ray/ARMOR/apps/shire-mobile/settings.gradle.kts ============================================================ ANDROID MANIFEST ============================================================ 1 2 3 4 12 13 17 18 19 20 21 22 23 24 25 26 27 ============================================================ GRADLE CONFIGURATION ============================================================ FILE: /home/ray/ARMOR/apps/shire-mobile/settings.gradle.kts 1 pluginManagement { 2 repositories { 3 google() 4 mavenCentral() 5 gradlePluginPortal() 6 } 7 } 8 9 dependencyResolutionManagement { 10 repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 repositories { 12 google() 13 mavenCentral() 14 } 15 } 16 17 rootProject.name = "SHiREMobile" 18 include(":app") FILE: /home/ray/ARMOR/apps/shire-mobile/build.gradle.kts 1 plugins { 2 id("com.android.application") version "9.2.0" apply false 3 id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false 4 } FILE: /home/ray/ARMOR/apps/shire-mobile/app/build.gradle.kts 1 plugins { 2 id("com.android.application") 3 id("org.jetbrains.kotlin.plugin.compose") 4 } 5 6 android { 7 namespace = "com.shire.mobile" 8 compileSdk = 36 9 10 defaultConfig { 11 applicationId = "com.shire.mobile" 12 minSdk = 29 13 targetSdk = 36 14 versionCode = 5 15 versionName = "0.5.0-shire-icon-ota-foundation" 16 } 17 18 buildFeatures { 19 compose = true 20 buildConfig = true 21 } 22 23 compileOptions { 24 sourceCompatibility = JavaVersion.VERSION_17 25 targetCompatibility = JavaVersion.VERSION_17 26 } 27 28 packaging { 29 resources { 30 excludes += "/META-INF/{AL2.0,LGPL2.1}" 31 } 32 } 33 } 34 35 dependencies { 36 val composeBom = platform("androidx.compose:compose-bom:2026.06.00") 37 38 implementation(composeBom) 39 androidTestImplementation(composeBom) 40 41 implementation("androidx.activity:activity-compose:1.13.0") 42 implementation("androidx.compose.material3:material3") 43 implementation("androidx.compose.foundation:foundation") 44 implementation("androidx.compose.ui:ui") 45 implementation("androidx.compose.ui:ui-tooling-preview") 46 47 debugImplementation("androidx.compose.ui:ui-tooling") 48 } ============================================================ APPROVAL / NOTIFICATION / DEEP-LINK MATCHES ============================================================ /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:116: val evidence: Int, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:121:private data class ApprovalItem( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:175: var showApprovals by remember { mutableStateOf(false) } /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:213: ApprovalItem( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:216: stage = "Approve Learning", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:223: ApprovalItem( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:226: stage = "Approve Slicing", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:265: onApprovals = { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:267: showApprovals = true /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:270: approvals = approvals + recommendedApproval(approvals) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:292: onApprovals = { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:294: showApprovals = true /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:297: approvals = approvals + recommendedApproval(approvals) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:313: approvals = approvals + ApprovalItem( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:316: stage = "Approve Learning", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:326: message = "Safe Plan added to the Approval Centre." /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:331: if (showApprovals) { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:332: ApprovalCentreDialog( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:335: showApprovals = false /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:344: "Approved" -> ShireExpression.Happy /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:345: "Rejected" -> ShireExpression.Concerned /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:351: "Approved" -> "Only that exact stage was approved." /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:352: "Rejected" -> "Plan rejected. No action will be taken." /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:353: "Deferred" -> "Plan deferred and remains inactive." /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:355: else -> "Approval updated." /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:362:private fun recommendedApproval( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:363: approvals: List /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:364:): ApprovalItem { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:365: return ApprovalItem( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:368: stage = "Approve Learning", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:384: approvals: List, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:391: onApprovals: () -> Unit, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:443: pendingApprovals = approvals.count { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:451: pendingApprovals = approvals.count { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:455: onApprovals = onApprovals, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:499: onApprovals = onApprovals, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:515: approvals: List, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:523: onApprovals: () -> Unit, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:573: pendingApprovals = approvals.count { it.status == "Pending" }, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:575: onApprovals = onApprovals, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:612: onApprovals = onApprovals, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:621: onApprovals = onApprovals, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:685: "Private routing" to "Tailscale only" /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:691: subtitle = "Learning, evidence, curriculum and recommendations." /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:708: "Network control" to "Approval required", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:720: "Print actions" to "Approval locked", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:911:private fun CompactStatus(pendingApprovals: Int) { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:918: StatusChip("APPROVALS", "$pendingApprovals pending", Amber) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:958: pendingApprovals: Int, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:960: onApprovals: () -> Unit, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1005: onClick = onApprovals, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1012: Text("APPROVALS $pendingApprovals") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1151: approvals: List, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1153: onApprovals: () -> Unit, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1169: text = "SHiRE Mini • private Tailscale command workspace", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1189: ApprovalSummaryCard( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1191: onOpen = onApprovals /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1212: MetricCard("$approvals", "Approvals") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1278: text = "Tap to inspect curriculum, queue, evidence and guardrails.", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1287:private fun ApprovalSummaryCard( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1288: approvals: List, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1326: onApprovals: () -> Unit, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1338: evidence = 22, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1349: evidence = 18, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1360: evidence = 3, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1397: OutlinedButton(onClick = onApprovals) { /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1398: Text("Approvals") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1611: DetailLine("Evidence records", skill.evidence.toString()) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1700: Text("Add for Approval") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1718: GuardrailLine("Certification", "Approval required") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1934:private fun ApprovalCentreDialog( /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1935: approvals: List, /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1954: text = "Approval Centre", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1985: ApprovalDetail("STAGE", item.stage, Amber) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1986: ApprovalDetail("PURPOSE", item.purpose, TextPrimary) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1987: ApprovalDetail("AFFECTS", item.affects, Blue) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1988: ApprovalDetail("RISK", item.risk, Red) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1989: ApprovalDetail("ESTIMATE", item.estimate, Green) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1990: ApprovalDetail("ROLLBACK", item.rollback, TextMuted) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1991: ApprovalDetail("STATUS", item.status, Amber) /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:1995: text = "Approval applies only to this exact stage.", /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2008: onDecision(item.id, "Approved") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2011: Text("Approve Stage") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2019: Text("Request Changes") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2024: onDecision(item.id, "Deferred") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2027: Text("Defer") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2032: onDecision(item.id, "Rejected") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2039: Text("Reject") /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt:2056:private fun ApprovalDetail( /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/themes.xml:6: #090B10 /home/ray/ARMOR/apps/shire-mobile/app/src/main/AndroidManifest.xml:18: /home/ray/ARMOR/apps/shire-mobile/app/src/main/AndroidManifest.xml:21: ============================================================ KOTLIN AND JAVA SOURCE ============================================================ FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/java/com/shire/mobile/MainActivity.kt 1 package com.shire.mobile 2 3 import android.os.Bundle 4 import android.provider.Settings 5 import androidx.activity.ComponentActivity 6 import androidx.activity.compose.setContent 7 import androidx.compose.animation.AnimatedContent 8 import androidx.compose.animation.AnimatedVisibility 9 import androidx.compose.animation.core.Animatable 10 import androidx.compose.animation.core.RepeatMode 11 import androidx.compose.animation.core.animateFloat 12 import androidx.compose.animation.core.infiniteRepeatable 13 import androidx.compose.animation.core.rememberInfiniteTransition 14 import androidx.compose.animation.core.tween 15 import androidx.compose.foundation.Image 16 import androidx.compose.foundation.background 17 import androidx.compose.foundation.clickable 18 import androidx.compose.foundation.layout.Arrangement 19 import androidx.compose.foundation.layout.Box 20 import androidx.compose.foundation.layout.BoxWithConstraints 21 import androidx.compose.foundation.layout.Column 22 import androidx.compose.foundation.layout.FlowRow 23 import androidx.compose.foundation.layout.Row 24 import androidx.compose.foundation.layout.Spacer 25 import androidx.compose.foundation.layout.fillMaxHeight 26 import androidx.compose.foundation.layout.fillMaxSize 27 import androidx.compose.foundation.layout.fillMaxWidth 28 import androidx.compose.foundation.layout.height 29 import androidx.compose.foundation.layout.heightIn 30 import androidx.compose.foundation.layout.offset 31 import androidx.compose.foundation.layout.padding 32 import androidx.compose.foundation.layout.size 33 import androidx.compose.foundation.layout.width 34 import androidx.compose.foundation.layout.widthIn 35 import androidx.compose.foundation.rememberScrollState 36 import androidx.compose.foundation.shape.CircleShape 37 import androidx.compose.foundation.shape.RoundedCornerShape 38 import androidx.compose.foundation.verticalScroll 39 import androidx.compose.material3.AlertDialog 40 import androidx.compose.material3.Button 41 import androidx.compose.material3.ButtonDefaults 42 import androidx.compose.material3.Card 43 import androidx.compose.material3.CardDefaults 44 import androidx.compose.material3.ExperimentalMaterial3Api 45 import androidx.compose.material3.FilterChip 46 import androidx.compose.material3.LinearProgressIndicator 47 import androidx.compose.material3.MaterialTheme 48 import androidx.compose.material3.OutlinedButton 49 import androidx.compose.material3.OutlinedTextField 50 import androidx.compose.material3.RadioButton 51 import androidx.compose.material3.Scaffold 52 import androidx.compose.material3.Switch 53 import androidx.compose.material3.Text 54 import androidx.compose.material3.TextButton 55 import androidx.compose.material3.darkColorScheme 56 import androidx.compose.runtime.Composable 57 import androidx.compose.runtime.LaunchedEffect 58 import androidx.compose.runtime.getValue 59 import androidx.compose.runtime.mutableIntStateOf 60 import androidx.compose.runtime.mutableStateOf 61 import androidx.compose.runtime.remember 62 import androidx.compose.runtime.setValue 63 import androidx.compose.ui.Alignment 64 import androidx.compose.ui.Modifier 65 import androidx.compose.ui.draw.clip 66 import androidx.compose.ui.graphics.Color 67 import androidx.compose.ui.graphics.graphicsLayer 68 import androidx.compose.ui.layout.ContentScale 69 import androidx.compose.ui.platform.LocalContext 70 import androidx.compose.ui.res.painterResource 71 import androidx.compose.ui.text.font.FontWeight 72 import androidx.compose.ui.text.style.TextAlign 73 import androidx.compose.ui.unit.dp 74 import androidx.compose.ui.unit.sp 75 import kotlinx.coroutines.delay 76 77 private val Background = Color(0xFF090B10) 78 private val Surface = Color(0xFF11151D) 79 private val Raised = Color(0xFF181D28) 80 private val RaisedSoft = Color(0xFF202634) 81 private val Purple = Color(0xFFA987FF) 82 private val Blue = Color(0xFF73D7FF) 83 private val Green = Color(0xFF75E6A4) 84 private val Amber = Color(0xFFFFC766) 85 private val Red = Color(0xFFFF7D86) 86 private val TextPrimary = Color(0xFFF2F3F7) 87 private val TextMuted = Color(0xFF9EA6B5) 88 89 private enum class Screen { 90 Home, 91 Academy 92 } 93 94 private enum class ShireExpression { 95 Normal, 96 Happy, 97 Thinking, 98 Sentinel, 99 Concerned 100 } 101 102 private enum class ShireMode { 103 Calm, 104 Academy, 105 Sentinel, 106 Forge 107 } 108 109 private data class Skill( 110 val id: String, 111 val name: String, 112 val category: String, 113 val stage: String, 114 val progress: Float, 115 val state: String, 116 val evidence: Int, 117 val result: String, 118 val forge: String 119 ) 120 121 private data class ApprovalItem( 122 val id: Int, 123 val title: String, 124 val stage: String, 125 val purpose: String, 126 val affects: String, 127 val risk: String, 128 val estimate: String, 129 val rollback: String, 130 val status: String = "Pending" 131 ) 132 133 class MainActivity : ComponentActivity() { 134 override fun onCreate(savedInstanceState: Bundle?) { 135 super.onCreate(savedInstanceState) 136 137 setContent { 138 MaterialTheme( 139 colorScheme = darkColorScheme( 140 primary = Purple, 141 secondary = Blue, 142 background = Background, 143 surface = Surface, 144 onPrimary = Color.Black, 145 onBackground = TextPrimary, 146 onSurface = TextPrimary 147 ) 148 ) { 149 ShireMobileApp() 150 } 151 } 152 } 153 } 154 155 @Composable 156 private fun ShireMobileApp() { 157 val context = LocalContext.current 158 159 val systemAnimationsEnabled = remember { 160 Settings.Global.getFloat( 161 context.contentResolver, 162 Settings.Global.ANIMATOR_DURATION_SCALE, 163 1f 164 ) > 0f 165 } 166 167 var personalityAnimations by remember { 168 mutableStateOf(systemAnimationsEnabled) 169 } 170 171 var screen by remember { mutableStateOf(Screen.Home) } 172 var mode by remember { mutableStateOf(ShireMode.Calm) } 173 var expression by remember { mutableStateOf(ShireExpression.Normal) } 174 var showLearnPrint by remember { mutableStateOf(false) } 175 var showApprovals by remember { mutableStateOf(false) } 176 var message by remember { 177 mutableStateOf("Systems ready, Chief. What are we building today?") 178 } 179 180 val handleModeChange: (ShireMode) -> Unit = { selected -> 181 mode = selected 182 183 when (selected) { 184 ShireMode.Calm -> { 185 screen = Screen.Home 186 expression = ShireExpression.Normal 187 message = "Calm command mode ready, Chief." 188 } 189 190 ShireMode.Academy -> { 191 screen = Screen.Academy 192 expression = ShireExpression.Thinking 193 message = "Academy mode ready. Let us review what I have learned." 194 } 195 196 ShireMode.Sentinel -> { 197 screen = Screen.Home 198 expression = ShireExpression.Sentinel 199 message = "Sentinel mode active. Security systems have priority." 200 } 201 202 ShireMode.Forge -> { 203 screen = Screen.Home 204 expression = ShireExpression.Happy 205 message = "Forge mode ready. Let us build something remarkable." 206 } 207 } 208 } 209 210 var approvals by remember { 211 mutableStateOf( 212 listOf( 213 ApprovalItem( 214 id = 1, 215 title = "Gothic controller holder", 216 stage = "Approve Learning", 217 purpose = "Learn the geometry and support rules needed for a printable controller holder.", 218 affects = "Academy planning only. No Forge generation or printer action.", 219 risk = "Low. Originality and licensing checks remain required.", 220 estimate = "45–90 minutes. No material cost.", 221 rollback = "Can be cancelled before or during uncounted practice." 222 ), 223 ApprovalItem( 224 id = 2, 225 title = "Controller holder slicing", 226 stage = "Approve Slicing", 227 purpose = "Prepare an approved model for Bambu P1S #1 using black PLA.", 228 affects = "Creates a slicer preview only.", 229 risk = "Medium. Dimensions and supports require review.", 230 estimate = "About 180 g PLA and 6–8 hours.", 231 rollback = "Slicer output can be discarded. Printing remains locked." 232 ) 233 ) 234 ) 235 } 236 237 BoxWithConstraints( 238 modifier = Modifier 239 .fillMaxSize() 240 .background(Background) 241 ) { 242 val expanded = maxWidth >= 700.dp 243 244 Scaffold(containerColor = Background) { paddingValues -> 245 if (expanded) { 246 FoldCommandCentre( 247 modifier = Modifier.padding(paddingValues), 248 screen = screen, 249 mode = mode, 250 expression = expression, 251 message = message, 252 approvals = approvals, 253 personalityAnimations = personalityAnimations, 254 onPersonalityAnimationsChange = { 255 personalityAnimations = it 256 }, 257 onScreenChange = { screen = it }, 258 onModeChange = handleModeChange, 259 onExpressionChange = { expression = it }, 260 onMessageChange = { message = it }, 261 onLearnPrint = { 262 expression = ShireExpression.Thinking 263 showLearnPrint = true 264 }, 265 onApprovals = { 266 expression = ShireExpression.Thinking 267 showApprovals = true 268 }, 269 onAddRecommendation = { 270 approvals = approvals + recommendedApproval(approvals) 271 expression = ShireExpression.Happy 272 message = "Learning recommendation added for approval." 273 } 274 ) 275 } else { 276 FoldedCompanion( 277 modifier = Modifier.padding(paddingValues), 278 screen = screen, 279 mode = mode, 280 expression = expression, 281 message = message, 282 approvals = approvals, 283 personalityAnimations = personalityAnimations, 284 onScreenChange = { screen = it }, 285 onModeChange = handleModeChange, 286 onExpressionChange = { expression = it }, 287 onMessageChange = { message = it }, 288 onLearnPrint = { 289 expression = ShireExpression.Thinking 290 showLearnPrint = true 291 }, 292 onApprovals = { 293 expression = ShireExpression.Thinking 294 showApprovals = true 295 }, 296 onAddRecommendation = { 297 approvals = approvals + recommendedApproval(approvals) 298 expression = ShireExpression.Happy 299 message = "Learning recommendation added for approval." 300 } 301 ) 302 } 303 } 304 } 305 306 if (showLearnPrint) { 307 LearnAndPrintDialog( 308 onDismiss = { 309 showLearnPrint = false 310 expression = ShireExpression.Normal 311 }, 312 onCreatePlan = { request, mode -> 313 approvals = approvals + ApprovalItem( 314 id = (approvals.maxOfOrNull { it.id } ?: 0) + 1, 315 title = request.take(55), 316 stage = "Approve Learning", 317 purpose = request, 318 affects = "Creates a Safe Plan only. No learning, design, slicing or printing starts.", 319 risk = "Awaiting safety, licensing and feasibility analysis.", 320 estimate = mode, 321 rollback = "Fully cancellable before staged approval." 322 ) 323 324 showLearnPrint = false 325 expression = ShireExpression.Happy 326 message = "Safe Plan added to the Approval Centre." 327 } 328 ) 329 } 330 331 if (showApprovals) { 332 ApprovalCentreDialog( 333 approvals = approvals, 334 onDismiss = { 335 showApprovals = false 336 expression = ShireExpression.Normal 337 }, 338 onDecision = { id, decision -> 339 approvals = approvals.map { 340 if (it.id == id) it.copy(status = decision) else it 341 } 342 343 expression = when (decision) { 344 "Approved" -> ShireExpression.Happy 345 "Rejected" -> ShireExpression.Concerned 346 "Changes Requested" -> ShireExpression.Thinking 347 else -> ShireExpression.Normal 348 } 349 350 message = when (decision) { 351 "Approved" -> "Only that exact stage was approved." 352 "Rejected" -> "Plan rejected. No action will be taken." 353 "Deferred" -> "Plan deferred and remains inactive." 354 "Changes Requested" -> "A revised plan is now required." 355 else -> "Approval updated." 356 } 357 } 358 ) 359 } 360 } 361 362 private fun recommendedApproval( 363 approvals: List 364 ): ApprovalItem { 365 return ApprovalItem( 366 id = (approvals.maxOfOrNull { it.id } ?: 0) + 1, 367 title = "Parametric phone support geometry", 368 stage = "Approve Learning", 369 purpose = "Learn reusable phone-fit and support geometry for DockForge and Forge.", 370 affects = "Adds an Academy Safe Plan only.", 371 risk = "Low. No certification or Forge-permission changes.", 372 estimate = "45–90 minutes.", 373 rollback = "Can be rejected, deferred or cancelled." 374 ) 375 } 376 377 @Composable 378 private fun FoldedCompanion( 379 modifier: Modifier, 380 screen: Screen, 381 mode: ShireMode, 382 expression: ShireExpression, 383 message: String, 384 approvals: List, 385 personalityAnimations: Boolean, 386 onScreenChange: (Screen) -> Unit, 387 onModeChange: (ShireMode) -> Unit, 388 onExpressionChange: (ShireExpression) -> Unit, 389 onMessageChange: (String) -> Unit, 390 onLearnPrint: () -> Unit, 391 onApprovals: () -> Unit, 392 onAddRecommendation: () -> Unit 393 ) { 394 Column( 395 modifier = modifier 396 .fillMaxSize() 397 .padding(horizontal = 17.dp, vertical = 13.dp), 398 horizontalAlignment = Alignment.CenterHorizontally 399 ) { 400 AppHeader(compact = true) 401 402 Spacer(modifier = Modifier.height(9.dp)) 403 404 ModeSelector( 405 selected = mode, 406 onSelect = onModeChange 407 ) 408 409 Spacer(modifier = Modifier.height(10.dp)) 410 411 Box( 412 modifier = Modifier 413 .fillMaxWidth() 414 .weight(1f), 415 contentAlignment = Alignment.TopCenter 416 ) { 417 when (screen) { 418 Screen.Home -> { 419 Column( 420 modifier = Modifier 421 .fillMaxSize() 422 .verticalScroll(rememberScrollState()), 423 horizontalAlignment = Alignment.CenterHorizontally 424 ) { 425 RealShireFace( 426 expression = expression, 427 expanded = false, 428 personalityAnimations = personalityAnimations 429 ) 430 431 Spacer(modifier = Modifier.height(11.dp)) 432 433 Text( 434 text = message, 435 textAlign = TextAlign.Center, 436 fontSize = 16.sp, 437 lineHeight = 22.sp 438 ) 439 440 Spacer(modifier = Modifier.height(14.dp)) 441 442 CompactStatus( 443 pendingApprovals = approvals.count { 444 it.status == "Pending" 445 } 446 ) 447 448 Spacer(modifier = Modifier.height(12.dp)) 449 450 MainActionPanel( 451 pendingApprovals = approvals.count { 452 it.status == "Pending" 453 }, 454 onLearnPrint = onLearnPrint, 455 onApprovals = onApprovals, 456 onAcademy = { 457 onScreenChange(Screen.Academy) 458 }, 459 onShire = { 460 onExpressionChange( 461 ShireExpression.Normal 462 ) 463 onMessageChange( 464 "Local SHiRE mode selected." 465 ) 466 }, 467 onChatGpt = { 468 onExpressionChange( 469 ShireExpression.Thinking 470 ) 471 onMessageChange( 472 "ChatGPT mode selected." 473 ) 474 }, 475 onBoth = { 476 onExpressionChange( 477 ShireExpression.Happy 478 ) 479 onMessageChange( 480 "SHiRE and ChatGPT collaboration selected." 481 ) 482 } 483 ) 484 485 Spacer(modifier = Modifier.height(14.dp)) 486 487 CurrentLearningCard() 488 489 Spacer(modifier = Modifier.height(12.dp)) 490 } 491 } 492 493 Screen.Academy -> { 494 MiniAcademy( 495 expanded = false, 496 onBack = { 497 onScreenChange(Screen.Home) 498 }, 499 onApprovals = onApprovals, 500 onAddRecommendation = onAddRecommendation 501 ) 502 } 503 } 504 } 505 } 506 } 507 508 @Composable 509 private fun FoldCommandCentre( 510 modifier: Modifier, 511 screen: Screen, 512 mode: ShireMode, 513 expression: ShireExpression, 514 message: String, 515 approvals: List, 516 personalityAnimations: Boolean, 517 onPersonalityAnimationsChange: (Boolean) -> Unit, 518 onScreenChange: (Screen) -> Unit, 519 onModeChange: (ShireMode) -> Unit, 520 onExpressionChange: (ShireExpression) -> Unit, 521 onMessageChange: (String) -> Unit, 522 onLearnPrint: () -> Unit, 523 onApprovals: () -> Unit, 524 onAddRecommendation: () -> Unit 525 ) { 526 Row( 527 modifier = modifier 528 .fillMaxSize() 529 .padding(18.dp), 530 horizontalArrangement = Arrangement.spacedBy(16.dp) 531 ) { 532 Column( 533 modifier = Modifier 534 .weight(0.92f) 535 .fillMaxHeight() 536 .verticalScroll(rememberScrollState()), 537 horizontalAlignment = Alignment.CenterHorizontally 538 ) { 539 AppHeader(compact = false) 540 541 Spacer(modifier = Modifier.height(9.dp)) 542 543 ModeSelector( 544 selected = mode, 545 onSelect = onModeChange 546 ) 547 548 Spacer(modifier = Modifier.height(8.dp)) 549 550 RealShireFace( 551 expression = expression, 552 expanded = true, 553 personalityAnimations = personalityAnimations 554 ) 555 556 Spacer(modifier = Modifier.height(10.dp)) 557 558 Text( 559 text = message, 560 modifier = Modifier.widthIn(max = 460.dp), 561 textAlign = TextAlign.Center, 562 fontSize = 18.sp, 563 lineHeight = 24.sp 564 ) 565 566 Spacer(modifier = Modifier.height(13.dp)) 567 568 CurrentMissionCard() 569 570 Spacer(modifier = Modifier.height(12.dp)) 571 572 MainActionPanel( 573 pendingApprovals = approvals.count { it.status == "Pending" }, 574 onLearnPrint = onLearnPrint, 575 onApprovals = onApprovals, 576 onAcademy = { onScreenChange(Screen.Academy) }, 577 onShire = { 578 onExpressionChange(ShireExpression.Normal) 579 onMessageChange("Local SHiRE mode selected.") 580 }, 581 onChatGpt = { 582 onExpressionChange(ShireExpression.Thinking) 583 onMessageChange("ChatGPT mode selected.") 584 }, 585 onBoth = { 586 onExpressionChange(ShireExpression.Happy) 587 onMessageChange("SHiRE and ChatGPT collaboration selected.") 588 } 589 ) 590 591 Spacer(modifier = Modifier.height(12.dp)) 592 593 PersonalityAnimationControl( 594 enabled = personalityAnimations, 595 onChange = onPersonalityAnimationsChange 596 ) 597 } 598 599 Card( 600 modifier = Modifier 601 .weight(1.35f) 602 .fillMaxHeight(), 603 colors = CardDefaults.cardColors(containerColor = Surface), 604 shape = RoundedCornerShape(24.dp) 605 ) { 606 when (screen) { 607 Screen.Home -> { 608 CommandCentreOverview( 609 mode = mode, 610 approvals = approvals, 611 onAcademy = { onScreenChange(Screen.Academy) }, 612 onApprovals = onApprovals, 613 onAddRecommendation = onAddRecommendation 614 ) 615 } 616 617 Screen.Academy -> { 618 MiniAcademy( 619 expanded = true, 620 onBack = { onScreenChange(Screen.Home) }, 621 onApprovals = onApprovals, 622 onAddRecommendation = onAddRecommendation 623 ) 624 } 625 } 626 } 627 } 628 } 629 630 631 @Composable 632 private fun ModeSelector( 633 selected: ShireMode, 634 onSelect: (ShireMode) -> Unit 635 ) { 636 Card( 637 modifier = Modifier 638 .fillMaxWidth() 639 .widthIn(max = 650.dp), 640 colors = CardDefaults.cardColors(containerColor = Surface), 641 shape = RoundedCornerShape(18.dp) 642 ) { 643 FlowRow( 644 modifier = Modifier.padding(9.dp), 645 horizontalArrangement = Arrangement.spacedBy(7.dp), 646 verticalArrangement = Arrangement.spacedBy(7.dp) 647 ) { 648 ShireMode.entries.forEach { mode -> 649 FilterChip( 650 selected = selected == mode, 651 onClick = { onSelect(mode) }, 652 label = { 653 Text( 654 when (mode) { 655 ShireMode.Calm -> "Calm" 656 ShireMode.Academy -> "Academy" 657 ShireMode.Sentinel -> "Sentinel" 658 ShireMode.Forge -> "Forge" 659 } 660 ) 661 } 662 ) 663 } 664 } 665 } 666 } 667 668 @Composable 669 private fun ModeWorkspace(mode: ShireMode) { 670 val title: String 671 val subtitle: String 672 val status: String 673 val accent: Color 674 val details: List> 675 676 when (mode) { 677 ShireMode.Calm -> { 678 title = "Calm Command" 679 subtitle = "Daily assistance, conversation and mission control." 680 status = "Ready" 681 accent = Green 682 details = listOf( 683 "Conversation" to "Local SHiRE ready", 684 "Current mission" to "Mobile command expansion", 685 "Private routing" to "Tailscale only" 686 ) 687 } 688 689 ShireMode.Academy -> { 690 title = "Academy" 691 subtitle = "Learning, evidence, curriculum and recommendations." 692 status = "Learning" 693 accent = Blue 694 details = listOf( 695 "Current skill" to "Boolean Operations", 696 "Progress" to "72%", 697 "Protected skills" to "371" 698 ) 699 } 700 701 ShireMode.Sentinel -> { 702 title = "Sentinel" 703 subtitle = "Security posture, network alerts and protected actions." 704 status = "Protected" 705 accent = Red 706 details = listOf( 707 "Threat posture" to "Monitoring", 708 "Network control" to "Approval required", 709 "Public ports" to "None" 710 ) 711 } 712 713 ShireMode.Forge -> { 714 title = "Forge" 715 subtitle = "Design, modelling, slicing and printer readiness." 716 status = "Ready" 717 accent = Purple 718 details = listOf( 719 "Design engine" to "CadQuery available", 720 "Print actions" to "Approval locked", 721 "Default material" to "PLA" 722 ) 723 } 724 } 725 726 Card( 727 modifier = Modifier.fillMaxWidth(), 728 colors = CardDefaults.cardColors(containerColor = Raised), 729 shape = RoundedCornerShape(20.dp) 730 ) { 731 Column(modifier = Modifier.padding(17.dp)) { 732 Row(verticalAlignment = Alignment.CenterVertically) { 733 Column(modifier = Modifier.weight(1f)) { 734 SectionLabel("ACTIVE SHIRE MODE") 735 736 Text( 737 text = title, 738 fontSize = 21.sp, 739 fontWeight = FontWeight.Black 740 ) 741 } 742 743 Text( 744 text = status, 745 color = accent, 746 fontWeight = FontWeight.Bold 747 ) 748 } 749 750 Text( 751 text = subtitle, 752 color = TextMuted, 753 lineHeight = 19.sp 754 ) 755 756 Spacer(modifier = Modifier.height(11.dp)) 757 758 details.forEach { (label, value) -> 759 Row(modifier = Modifier.padding(vertical = 3.dp)) { 760 Text( 761 text = label, 762 modifier = Modifier.weight(1f), 763 color = TextMuted 764 ) 765 766 Text( 767 text = value, 768 color = accent, 769 fontWeight = FontWeight.SemiBold 770 ) 771 } 772 } 773 } 774 } 775 } 776 777 @Composable 778 private fun AppHeader(compact: Boolean) { 779 Column(horizontalAlignment = Alignment.CenterHorizontally) { 780 Text( 781 text = "SHiRE", 782 fontSize = if (compact) 27.sp else 31.sp, 783 fontWeight = FontWeight.Black, 784 letterSpacing = 5.sp 785 ) 786 787 Text( 788 text = if (compact) "MOBILE COMMAND" else "FOLD COMMAND CENTRE", 789 color = TextMuted, 790 fontSize = 10.sp, 791 letterSpacing = 2.sp 792 ) 793 } 794 } 795 796 @Composable 797 private fun RealShireFace( 798 expression: ShireExpression, 799 expanded: Boolean, 800 personalityAnimations: Boolean 801 ) { 802 val transition = rememberInfiniteTransition(label = "shire-idle") 803 804 val breathing by transition.animateFloat( 805 initialValue = if (personalityAnimations) 0.988f else 1f, 806 targetValue = if (personalityAnimations) 1.014f else 1f, 807 animationSpec = infiniteRepeatable( 808 animation = tween(2400), 809 repeatMode = RepeatMode.Reverse 810 ), 811 label = "breathing" 812 ) 813 814 val floating by transition.animateFloat( 815 initialValue = if (personalityAnimations) -2f else 0f, 816 targetValue = if (personalityAnimations) 3f else 0f, 817 animationSpec = infiniteRepeatable( 818 animation = tween(3000), 819 repeatMode = RepeatMode.Reverse 820 ), 821 label = "floating" 822 ) 823 824 val stretchX = remember { Animatable(1f) } 825 val stretchY = remember { Animatable(1f) } 826 var greetingVisible by remember { mutableStateOf(false) } 827 828 LaunchedEffect(expanded, personalityAnimations) { 829 if (expanded && personalityAnimations) { 830 greetingVisible = true 831 832 stretchX.snapTo(0.92f) 833 stretchY.snapTo(1.05f) 834 835 stretchX.animateTo( 836 targetValue = 1.10f, 837 animationSpec = tween(320) 838 ) 839 stretchY.animateTo( 840 targetValue = 0.94f, 841 animationSpec = tween(320) 842 ) 843 844 stretchX.animateTo( 845 targetValue = 1f, 846 animationSpec = tween(350) 847 ) 848 stretchY.animateTo( 849 targetValue = 1f, 850 animationSpec = tween(350) 851 ) 852 853 delay(1100) 854 greetingVisible = false 855 } else { 856 stretchX.snapTo(1f) 857 stretchY.snapTo(1f) 858 greetingVisible = false 859 } 860 } 861 862 val drawable = when (expression) { 863 ShireExpression.Normal -> R.drawable.shire_face_main 864 ShireExpression.Happy -> R.drawable.shire_face_happy 865 ShireExpression.Thinking -> R.drawable.shire_face_thinking 866 ShireExpression.Sentinel -> R.drawable.shire_face_sentinel 867 ShireExpression.Concerned -> R.drawable.shire_face_scared 868 } 869 870 Column(horizontalAlignment = Alignment.CenterHorizontally) { 871 Card( 872 modifier = Modifier 873 .size(if (expanded) 330.dp else 238.dp) 874 .offset(y = floating.dp) 875 .graphicsLayer { 876 scaleX = breathing * stretchX.value 877 scaleY = breathing * stretchY.value 878 }, 879 shape = CircleShape, 880 colors = CardDefaults.cardColors(containerColor = Raised), 881 elevation = CardDefaults.cardElevation(defaultElevation = 12.dp) 882 ) { 883 AnimatedContent( 884 targetState = drawable, 885 label = "shire-expression" 886 ) { face -> 887 Image( 888 painter = painterResource(face), 889 contentDescription = "SHiRE", 890 modifier = Modifier 891 .fillMaxSize() 892 .padding(4.dp) 893 .clip(CircleShape), 894 contentScale = ContentScale.Crop 895 ) 896 } 897 } 898 899 AnimatedVisibility(visible = greetingVisible) { 900 Text( 901 text = "Ahh… much better, Chief.", 902 modifier = Modifier.padding(top = 8.dp), 903 color = Green, 904 fontWeight = FontWeight.SemiBold 905 ) 906 } 907 } 908 } 909 910 @Composable 911 private fun CompactStatus(pendingApprovals: Int) { 912 FlowRow( 913 modifier = Modifier.fillMaxWidth(), 914 horizontalArrangement = Arrangement.spacedBy(7.dp), 915 verticalArrangement = Arrangement.spacedBy(7.dp) 916 ) { 917 StatusChip("ACADEMY", "Running", Green) 918 StatusChip("APPROVALS", "$pendingApprovals pending", Amber) 919 StatusChip("SENTINEL", "Protected", Green) 920 } 921 } 922 923 @Composable 924 private fun StatusChip( 925 title: String, 926 value: String, 927 valueColor: Color 928 ) { 929 Card( 930 colors = CardDefaults.cardColors(containerColor = Raised), 931 shape = RoundedCornerShape(13.dp) 932 ) { 933 Column( 934 modifier = Modifier.padding( 935 horizontal = 12.dp, 936 vertical = 9.dp 937 ) 938 ) { 939 Text( 940 text = title, 941 color = TextMuted, 942 fontSize = 9.sp, 943 fontWeight = FontWeight.Bold 944 ) 945 946 Text( 947 text = value, 948 color = valueColor, 949 fontSize = 13.sp, 950 fontWeight = FontWeight.SemiBold 951 ) 952 } 953 } 954 } 955 956 @Composable 957 private fun MainActionPanel( 958 pendingApprovals: Int, 959 onLearnPrint: () -> Unit, 960 onApprovals: () -> Unit, 961 onAcademy: () -> Unit, 962 onShire: () -> Unit, 963 onChatGpt: () -> Unit, 964 onBoth: () -> Unit 965 ) { 966 Card( 967 modifier = Modifier 968 .fillMaxWidth() 969 .widthIn(max = 650.dp), 970 colors = CardDefaults.cardColors(containerColor = Surface), 971 shape = RoundedCornerShape(20.dp) 972 ) { 973 Column( 974 modifier = Modifier.padding(14.dp), 975 verticalArrangement = Arrangement.spacedBy(9.dp) 976 ) { 977 Button( 978 onClick = onLearnPrint, 979 modifier = Modifier 980 .fillMaxWidth() 981 .height(52.dp), 982 colors = ButtonDefaults.buttonColors( 983 containerColor = Purple, 984 contentColor = Color.Black 985 ) 986 ) { 987 Text("LEARN & PRINT", fontWeight = FontWeight.Black) 988 } 989 990 Row( 991 horizontalArrangement = Arrangement.spacedBy(8.dp) 992 ) { 993 Button( 994 onClick = onAcademy, 995 modifier = Modifier.weight(1f), 996 colors = ButtonDefaults.buttonColors( 997 containerColor = Blue, 998 contentColor = Color.Black 999 ) 1000 ) { 1001 Text("ACADEMY", fontWeight = FontWeight.Bold) 1002 } 1003 1004 Button( 1005 onClick = onApprovals, 1006 modifier = Modifier.weight(1f), 1007 colors = ButtonDefaults.buttonColors( 1008 containerColor = Amber, 1009 contentColor = Color.Black 1010 ) 1011 ) { 1012 Text("APPROVALS $pendingApprovals") 1013 } 1014 } 1015 1016 Row(horizontalArrangement = Arrangement.spacedBy(7.dp)) { 1017 OutlinedButton( 1018 onClick = onShire, 1019 modifier = Modifier.weight(1f) 1020 ) { 1021 Text("SHiRE") 1022 } 1023 1024 OutlinedButton( 1025 onClick = onChatGpt, 1026 modifier = Modifier.weight(1f) 1027 ) { 1028 Text("ChatGPT") 1029 } 1030 1031 OutlinedButton( 1032 onClick = onBoth, 1033 modifier = Modifier.weight(1f) 1034 ) { 1035 Text("Both") 1036 } 1037 } 1038 } 1039 } 1040 } 1041 1042 @Composable 1043 private fun CurrentMissionCard() { 1044 Card( 1045 modifier = Modifier.fillMaxWidth(), 1046 colors = CardDefaults.cardColors(containerColor = Surface), 1047 shape = RoundedCornerShape(18.dp) 1048 ) { 1049 Column(modifier = Modifier.padding(14.dp)) { 1050 SectionLabel("CURRENT MISSION") 1051 1052 Text( 1053 text = "Expand SHiRE Mobile capability", 1054 fontWeight = FontWeight.Bold, 1055 fontSize = 17.sp 1056 ) 1057 1058 Text( 1059 text = "Mini Academy and adaptive Fold command centre", 1060 color = TextMuted, 1061 fontSize = 13.sp 1062 ) 1063 1064 Spacer(modifier = Modifier.height(8.dp)) 1065 1066 LinearProgressIndicator( 1067 progress = { 0.72f }, 1068 modifier = Modifier.fillMaxWidth() 1069 ) 1070 } 1071 } 1072 } 1073 1074 @Composable 1075 private fun CurrentLearningCard() { 1076 Card( 1077 modifier = Modifier.fillMaxWidth(), 1078 colors = CardDefaults.cardColors(containerColor = Surface), 1079 shape = RoundedCornerShape(18.dp) 1080 ) { 1081 Column(modifier = Modifier.padding(15.dp)) { 1082 SectionLabel("CURRENT LEARNING") 1083 1084 Text( 1085 text = "Boolean Operations", 1086 fontWeight = FontWeight.Bold, 1087 fontSize = 17.sp 1088 ) 1089 1090 Text( 1091 text = "Geometry Foundations • Stage 3", 1092 color = TextMuted 1093 ) 1094 1095 Spacer(modifier = Modifier.height(8.dp)) 1096 1097 LinearProgressIndicator( 1098 progress = { 0.72f }, 1099 modifier = Modifier.fillMaxWidth() 1100 ) 1101 1102 Spacer(modifier = Modifier.height(6.dp)) 1103 1104 Text( 1105 text = "72% • Read-only mobile view", 1106 color = Green, 1107 fontSize = 12.sp 1108 ) 1109 } 1110 } 1111 } 1112 1113 @Composable 1114 private fun PersonalityAnimationControl( 1115 enabled: Boolean, 1116 onChange: (Boolean) -> Unit 1117 ) { 1118 Card( 1119 modifier = Modifier.fillMaxWidth(), 1120 colors = CardDefaults.cardColors(containerColor = Surface), 1121 shape = RoundedCornerShape(17.dp) 1122 ) { 1123 Row( 1124 modifier = Modifier.padding(13.dp), 1125 verticalAlignment = Alignment.CenterVertically 1126 ) { 1127 Column(modifier = Modifier.weight(1f)) { 1128 Text( 1129 text = "Personality animations", 1130 fontWeight = FontWeight.SemiBold 1131 ) 1132 1133 Text( 1134 text = "Breathing, floating and unfolding stretch", 1135 color = TextMuted, 1136 fontSize = 12.sp 1137 ) 1138 } 1139 1140 Switch( 1141 checked = enabled, 1142 onCheckedChange = onChange 1143 ) 1144 } 1145 } 1146 } 1147 1148 @Composable 1149 private fun CommandCentreOverview( 1150 mode: ShireMode, 1151 approvals: List, 1152 onAcademy: () -> Unit, 1153 onApprovals: () -> Unit, 1154 onAddRecommendation: () -> Unit 1155 ) { 1156 Column( 1157 modifier = Modifier 1158 .fillMaxSize() 1159 .verticalScroll(rememberScrollState()) 1160 .padding(18.dp) 1161 ) { 1162 Text( 1163 text = "COMMAND OVERVIEW", 1164 fontSize = 24.sp, 1165 fontWeight = FontWeight.Black 1166 ) 1167 1168 Text( 1169 text = "SHiRE Mini • private Tailscale command workspace", 1170 color = TextMuted 1171 ) 1172 1173 Spacer(modifier = Modifier.height(15.dp)) 1174 1175 ModeWorkspace(mode) 1176 1177 Spacer(modifier = Modifier.height(14.dp)) 1178 1179 OverviewMetricRow( 1180 approvals = approvals.count { it.status == "Pending" } 1181 ) 1182 1183 Spacer(modifier = Modifier.height(14.dp)) 1184 1185 AcademySummaryCard(onOpen = onAcademy) 1186 1187 Spacer(modifier = Modifier.height(12.dp)) 1188 1189 ApprovalSummaryCard( 1190 approvals = approvals, 1191 onOpen = onApprovals 1192 ) 1193 1194 Spacer(modifier = Modifier.height(12.dp)) 1195 1196 RecommendationCard(onAdd = onAddRecommendation) 1197 1198 Spacer(modifier = Modifier.height(12.dp)) 1199 1200 SystemGuardrailsCard() 1201 } 1202 } 1203 1204 @Composable 1205 private fun OverviewMetricRow(approvals: Int) { 1206 FlowRow( 1207 horizontalArrangement = Arrangement.spacedBy(9.dp), 1208 verticalArrangement = Arrangement.spacedBy(9.dp) 1209 ) { 1210 MetricCard("371", "Skills") 1211 MetricCard("72%", "Current skill") 1212 MetricCard("$approvals", "Approvals") 1213 MetricCard("SAFE", "Guardrails") 1214 } 1215 } 1216 1217 @Composable 1218 private fun MetricCard(value: String, label: String) { 1219 Card( 1220 colors = CardDefaults.cardColors(containerColor = Raised), 1221 shape = RoundedCornerShape(15.dp) 1222 ) { 1223 Column( 1224 modifier = Modifier.padding( 1225 horizontal = 17.dp, 1226 vertical = 12.dp 1227 ) 1228 ) { 1229 Text( 1230 text = value, 1231 fontSize = 20.sp, 1232 fontWeight = FontWeight.Black, 1233 color = Purple 1234 ) 1235 1236 Text( 1237 text = label, 1238 color = TextMuted, 1239 fontSize = 11.sp 1240 ) 1241 } 1242 } 1243 } 1244 1245 @Composable 1246 private fun AcademySummaryCard(onOpen: () -> Unit) { 1247 Card( 1248 modifier = Modifier 1249 .fillMaxWidth() 1250 .clickable(onClick = onOpen), 1251 colors = CardDefaults.cardColors(containerColor = Raised), 1252 shape = RoundedCornerShape(18.dp) 1253 ) { 1254 Column(modifier = Modifier.padding(16.dp)) { 1255 SectionLabel("MINI ACADEMY") 1256 1257 Text( 1258 text = "Boolean Operations", 1259 fontSize = 19.sp, 1260 fontWeight = FontWeight.Bold 1261 ) 1262 1263 Text( 1264 text = "Current learning • Stage 3 • Running", 1265 color = Green 1266 ) 1267 1268 Spacer(modifier = Modifier.height(9.dp)) 1269 1270 LinearProgressIndicator( 1271 progress = { 0.72f }, 1272 modifier = Modifier.fillMaxWidth() 1273 ) 1274 1275 Spacer(modifier = Modifier.height(7.dp)) 1276 1277 Text( 1278 text = "Tap to inspect curriculum, queue, evidence and guardrails.", 1279 color = TextMuted, 1280 fontSize = 12.sp 1281 ) 1282 } 1283 } 1284 } 1285 1286 @Composable 1287 private fun ApprovalSummaryCard( 1288 approvals: List, 1289 onOpen: () -> Unit 1290 ) { 1291 Card( 1292 modifier = Modifier 1293 .fillMaxWidth() 1294 .clickable(onClick = onOpen), 1295 colors = CardDefaults.cardColors(containerColor = Raised), 1296 shape = RoundedCornerShape(18.dp) 1297 ) { 1298 Column(modifier = Modifier.padding(16.dp)) { 1299 SectionLabel("APPROVAL CENTRE") 1300 1301 Text( 1302 text = "${approvals.count { it.status == "Pending" }} actions waiting", 1303 fontSize = 18.sp, 1304 fontWeight = FontWeight.Bold, 1305 color = Amber 1306 ) 1307 1308 approvals 1309 .filter { it.status == "Pending" } 1310 .take(2) 1311 .forEach { 1312 Text( 1313 text = "• ${it.stage}: ${it.title}", 1314 color = TextMuted, 1315 fontSize = 13.sp 1316 ) 1317 } 1318 } 1319 } 1320 } 1321 1322 @Composable 1323 private fun MiniAcademy( 1324 expanded: Boolean, 1325 onBack: () -> Unit, 1326 onApprovals: () -> Unit, 1327 onAddRecommendation: () -> Unit 1328 ) { 1329 val skills = remember { 1330 listOf( 1331 Skill( 1332 id = "boolean", 1333 name = "Boolean Operations", 1334 category = "Geometry Foundations", 1335 stage = "Stage 3", 1336 progress = 0.72f, 1337 state = "Learning", 1338 evidence = 22, 1339 result = "Practice active", 1340 forge = "Locked" 1341 ), 1342 Skill( 1343 id = "parameters", 1344 name = "Parameter Architecture", 1345 category = "Parametric CAD", 1346 stage = "Review", 1347 progress = 1f, 1348 state = "Learned", 1349 evidence = 18, 1350 result = "Forge allowed", 1351 forge = "Allowed" 1352 ), 1353 Skill( 1354 id = "supports", 1355 name = "Printable Support Geometry", 1356 category = "Manufacturing", 1357 stage = "Queued", 1358 progress = 0.16f, 1359 state = "Queued", 1360 evidence = 3, 1361 result = "Awaiting plan", 1362 forge = "Locked" 1363 ) 1364 ) 1365 } 1366 1367 var selectedSkillId by remember { mutableStateOf(skills.first().id) } 1368 val selectedSkill = skills.first { it.id == selectedSkillId } 1369 1370 Column( 1371 modifier = Modifier 1372 .fillMaxSize() 1373 .verticalScroll(rememberScrollState()) 1374 .padding(if (expanded) 19.dp else 0.dp) 1375 ) { 1376 Row(verticalAlignment = Alignment.CenterVertically) { 1377 if (!expanded) { 1378 TextButton(onClick = onBack) { 1379 Text("Back") 1380 } 1381 } 1382 1383 Column(modifier = Modifier.weight(1f)) { 1384 Text( 1385 text = "MINI ACADEMY", 1386 fontSize = 25.sp, 1387 fontWeight = FontWeight.Black 1388 ) 1389 1390 Text( 1391 text = "Running • protected read-only view", 1392 color = Green, 1393 fontSize = 12.sp 1394 ) 1395 } 1396 1397 OutlinedButton(onClick = onApprovals) { 1398 Text("Approvals") 1399 } 1400 } 1401 1402 Spacer(modifier = Modifier.height(14.dp)) 1403 1404 AcademyHero() 1405 1406 Spacer(modifier = Modifier.height(13.dp)) 1407 1408 if (expanded) { 1409 Row( 1410 horizontalArrangement = Arrangement.spacedBy(12.dp) 1411 ) { 1412 Column(modifier = Modifier.weight(0.92f)) { 1413 SkillList( 1414 skills = skills, 1415 selectedSkillId = selectedSkillId, 1416 onSelect = { selectedSkillId = it } 1417 ) 1418 1419 Spacer(modifier = Modifier.height(12.dp)) 1420 1421 LearningQueueCard() 1422 } 1423 1424 Column(modifier = Modifier.weight(1.08f)) { 1425 SkillDetail(selectedSkill) 1426 1427 Spacer(modifier = Modifier.height(12.dp)) 1428 1429 RecommendationCard(onAdd = onAddRecommendation) 1430 1431 Spacer(modifier = Modifier.height(12.dp)) 1432 1433 SystemGuardrailsCard() 1434 } 1435 } 1436 } else { 1437 SkillList( 1438 skills = skills, 1439 selectedSkillId = selectedSkillId, 1440 onSelect = { selectedSkillId = it } 1441 ) 1442 1443 Spacer(modifier = Modifier.height(12.dp)) 1444 1445 SkillDetail(selectedSkill) 1446 1447 Spacer(modifier = Modifier.height(12.dp)) 1448 1449 LearningQueueCard() 1450 1451 Spacer(modifier = Modifier.height(12.dp)) 1452 1453 RecommendationCard(onAdd = onAddRecommendation) 1454 1455 Spacer(modifier = Modifier.height(12.dp)) 1456 1457 SystemGuardrailsCard() 1458 } 1459 } 1460 } 1461 1462 @Composable 1463 private fun AcademyHero() { 1464 Card( 1465 modifier = Modifier.fillMaxWidth(), 1466 colors = CardDefaults.cardColors(containerColor = Raised), 1467 shape = RoundedCornerShape(20.dp) 1468 ) { 1469 Column(modifier = Modifier.padding(17.dp)) { 1470 SectionLabel("CURRENT LEARNING") 1471 1472 Text( 1473 text = "Boolean Operations", 1474 fontSize = 21.sp, 1475 fontWeight = FontWeight.Black 1476 ) 1477 1478 Text( 1479 text = "Geometry Foundations • Stage 3", 1480 color = TextMuted 1481 ) 1482 1483 Spacer(modifier = Modifier.height(10.dp)) 1484 1485 LinearProgressIndicator( 1486 progress = { 0.72f }, 1487 modifier = Modifier.fillMaxWidth() 1488 ) 1489 1490 Spacer(modifier = Modifier.height(7.dp)) 1491 1492 Row { 1493 Text( 1494 text = "72% knowledge progress", 1495 color = Blue, 1496 modifier = Modifier.weight(1f) 1497 ) 1498 1499 Text( 1500 text = "22 successful runs", 1501 color = Green 1502 ) 1503 } 1504 1505 Spacer(modifier = Modifier.height(8.dp)) 1506 1507 Text( 1508 text = "The protected 371-skill state remains unchanged. " + 1509 "No certification or Forge permissions can be altered here.", 1510 color = TextMuted, 1511 fontSize = 12.sp, 1512 lineHeight = 17.sp 1513 ) 1514 } 1515 } 1516 } 1517 1518 @Composable 1519 private fun SkillList( 1520 skills: List, 1521 selectedSkillId: String, 1522 onSelect: (String) -> Unit 1523 ) { 1524 Card( 1525 modifier = Modifier.fillMaxWidth(), 1526 colors = CardDefaults.cardColors(containerColor = Raised), 1527 shape = RoundedCornerShape(19.dp) 1528 ) { 1529 Column(modifier = Modifier.padding(14.dp)) { 1530 SectionLabel("SKILLS") 1531 1532 skills.forEach { skill -> 1533 Card( 1534 modifier = Modifier 1535 .fillMaxWidth() 1536 .padding(vertical = 4.dp) 1537 .clickable { onSelect(skill.id) }, 1538 colors = CardDefaults.cardColors( 1539 containerColor = if (skill.id == selectedSkillId) { 1540 RaisedSoft 1541 } else { 1542 Surface 1543 } 1544 ), 1545 shape = RoundedCornerShape(14.dp) 1546 ) { 1547 Column(modifier = Modifier.padding(12.dp)) { 1548 Row { 1549 Column(modifier = Modifier.weight(1f)) { 1550 Text( 1551 text = skill.name, 1552 fontWeight = FontWeight.Bold 1553 ) 1554 1555 Text( 1556 text = "${skill.category} • ${skill.stage}", 1557 color = TextMuted, 1558 fontSize = 11.sp 1559 ) 1560 } 1561 1562 Text( 1563 text = skill.state, 1564 color = when (skill.state) { 1565 "Learned" -> Green 1566 "Learning" -> Blue 1567 else -> Amber 1568 }, 1569 fontSize = 12.sp 1570 ) 1571 } 1572 1573 Spacer(modifier = Modifier.height(6.dp)) 1574 1575 LinearProgressIndicator( 1576 progress = { skill.progress }, 1577 modifier = Modifier.fillMaxWidth() 1578 ) 1579 } 1580 } 1581 } 1582 } 1583 } 1584 } 1585 1586 @Composable 1587 private fun SkillDetail(skill: Skill) { 1588 Card( 1589 modifier = Modifier.fillMaxWidth(), 1590 colors = CardDefaults.cardColors(containerColor = Raised), 1591 shape = RoundedCornerShape(19.dp) 1592 ) { 1593 Column(modifier = Modifier.padding(16.dp)) { 1594 SectionLabel("SKILL DETAIL") 1595 1596 Text( 1597 text = skill.name, 1598 fontSize = 20.sp, 1599 fontWeight = FontWeight.Black 1600 ) 1601 1602 Text( 1603 text = "${skill.category} • ${skill.stage}", 1604 color = TextMuted 1605 ) 1606 1607 Spacer(modifier = Modifier.height(11.dp)) 1608 1609 DetailLine("State", skill.state) 1610 DetailLine("Progress", "${(skill.progress * 100).toInt()}%") 1611 DetailLine("Evidence records", skill.evidence.toString()) 1612 DetailLine("Current result", skill.result) 1613 DetailLine("Forge permission", skill.forge) 1614 1615 Spacer(modifier = Modifier.height(10.dp)) 1616 1617 Text( 1618 text = "Mobile Academy is read-only. Any change requires a Safe Plan and explicit approval.", 1619 color = Green, 1620 fontSize = 12.sp, 1621 lineHeight = 17.sp 1622 ) 1623 } 1624 } 1625 } 1626 1627 @Composable 1628 private fun LearningQueueCard() { 1629 Card( 1630 modifier = Modifier.fillMaxWidth(), 1631 colors = CardDefaults.cardColors(containerColor = Raised), 1632 shape = RoundedCornerShape(19.dp) 1633 ) { 1634 Column(modifier = Modifier.padding(16.dp)) { 1635 SectionLabel("LEARNING QUEUE") 1636 1637 QueueLine("Printable Support Geometry", "Next") 1638 QueueLine("Hard-Surface Modelling", "Planned") 1639 QueueLine("Blender Cloth Fundamentals", "Recommended") 1640 } 1641 } 1642 } 1643 1644 @Composable 1645 private fun QueueLine(name: String, state: String) { 1646 Row( 1647 modifier = Modifier.padding(vertical = 5.dp) 1648 ) { 1649 Text( 1650 text = name, 1651 modifier = Modifier.weight(1f), 1652 fontWeight = FontWeight.SemiBold 1653 ) 1654 1655 Text( 1656 text = state, 1657 color = Amber, 1658 fontSize = 12.sp 1659 ) 1660 } 1661 } 1662 1663 @Composable 1664 private fun RecommendationCard(onAdd: () -> Unit) { 1665 Card( 1666 modifier = Modifier.fillMaxWidth(), 1667 colors = CardDefaults.cardColors(containerColor = Raised), 1668 shape = RoundedCornerShape(19.dp) 1669 ) { 1670 Column(modifier = Modifier.padding(16.dp)) { 1671 SectionLabel("RECOMMENDED NEXT") 1672 1673 Text( 1674 text = "Parametric phone support geometry", 1675 fontSize = 18.sp, 1676 fontWeight = FontWeight.Bold 1677 ) 1678 1679 Text( 1680 text = "Supports DockForge, Forge accuracy and sellable products.", 1681 color = TextMuted, 1682 lineHeight = 19.sp 1683 ) 1684 1685 Spacer(modifier = Modifier.height(7.dp)) 1686 1687 Text( 1688 text = "Estimated effort: 45–90 minutes", 1689 color = Blue 1690 ) 1691 1692 Text( 1693 text = "Benefit: Forge + income", 1694 color = Green 1695 ) 1696 1697 Spacer(modifier = Modifier.height(10.dp)) 1698 1699 Button(onClick = onAdd) { 1700 Text("Add for Approval") 1701 } 1702 } 1703 } 1704 } 1705 1706 @Composable 1707 private fun SystemGuardrailsCard() { 1708 Card( 1709 modifier = Modifier.fillMaxWidth(), 1710 colors = CardDefaults.cardColors(containerColor = Raised), 1711 shape = RoundedCornerShape(19.dp) 1712 ) { 1713 Column(modifier = Modifier.padding(16.dp)) { 1714 SectionLabel("GUARDRAILS") 1715 1716 GuardrailLine("Existing 371-skill state", "Protected") 1717 GuardrailLine("Counted attempts", "Unchanged") 1718 GuardrailLine("Certification", "Approval required") 1719 GuardrailLine("Forge permissions", "Locked by default") 1720 GuardrailLine("Automatic printing", "Disabled") 1721 } 1722 } 1723 } 1724 1725 @Composable 1726 private fun GuardrailLine(name: String, state: String) { 1727 Row( 1728 modifier = Modifier.padding(vertical = 4.dp) 1729 ) { 1730 Text( 1731 text = name, 1732 modifier = Modifier.weight(1f), 1733 color = TextMuted 1734 ) 1735 1736 Text( 1737 text = state, 1738 color = Green, 1739 fontWeight = FontWeight.SemiBold, 1740 fontSize = 12.sp 1741 ) 1742 } 1743 } 1744 1745 @Composable 1746 private fun DetailLine(label: String, value: String) { 1747 Row( 1748 modifier = Modifier.padding(vertical = 4.dp) 1749 ) { 1750 Text( 1751 text = label, 1752 modifier = Modifier.weight(1f), 1753 color = TextMuted 1754 ) 1755 1756 Text( 1757 text = value, 1758 fontWeight = FontWeight.SemiBold 1759 ) 1760 } 1761 } 1762 1763 @Composable 1764 private fun SectionLabel(text: String) { 1765 Text( 1766 text = text, 1767 color = TextMuted, 1768 fontSize = 10.sp, 1769 fontWeight = FontWeight.Bold, 1770 letterSpacing = 1.4.sp 1771 ) 1772 1773 Spacer(modifier = Modifier.height(6.dp)) 1774 } 1775 1776 @OptIn(ExperimentalMaterial3Api::class) 1777 @Composable 1778 private fun LearnAndPrintDialog( 1779 onDismiss: () -> Unit, 1780 onCreatePlan: (String, String) -> Unit 1781 ) { 1782 val modes = listOf( 1783 "Learn Only", 1784 "Design Only", 1785 "Prepare to Print", 1786 "Learn + Design + Print" 1787 ) 1788 1789 var request by remember { mutableStateOf("") } 1790 var selectedMode by remember { 1791 mutableStateOf("Learn + Design + Print") 1792 } 1793 var printer by remember { mutableStateOf("Bambu P1S #1") } 1794 var material by remember { mutableStateOf("PLA") } 1795 1796 AlertDialog( 1797 onDismissRequest = onDismiss, 1798 title = { 1799 Column { 1800 Text( 1801 text = "Learn & Print", 1802 fontSize = 24.sp, 1803 fontWeight = FontWeight.Black 1804 ) 1805 1806 Text( 1807 text = "Create an approval-gated SHiRE mission.", 1808 color = TextMuted, 1809 fontSize = 13.sp 1810 ) 1811 } 1812 }, 1813 text = { 1814 Column( 1815 modifier = Modifier 1816 .heightIn(max = 580.dp) 1817 .verticalScroll(rememberScrollState()) 1818 ) { 1819 OutlinedTextField( 1820 value = request, 1821 onValueChange = { 1822 if (it.length <= 1000) request = it 1823 }, 1824 modifier = Modifier 1825 .fillMaxWidth() 1826 .heightIn(min = 155.dp), 1827 label = { Text("Describe the mission") }, 1828 supportingText = { 1829 Text( 1830 text = "${request.length} / 1000", 1831 modifier = Modifier.fillMaxWidth(), 1832 textAlign = TextAlign.End 1833 ) 1834 }, 1835 placeholder = { 1836 Text( 1837 "Learn how to design a gothic controller holder, " + 1838 "show me the model and prepare it for black PLA." 1839 ) 1840 }, 1841 maxLines = 12 1842 ) 1843 1844 Spacer(modifier = Modifier.height(10.dp)) 1845 SectionLabel("MISSION TYPE") 1846 1847 modes.forEach { mode -> 1848 Row(verticalAlignment = Alignment.CenterVertically) { 1849 RadioButton( 1850 selected = selectedMode == mode, 1851 onClick = { selectedMode = mode } 1852 ) 1853 Text(mode) 1854 } 1855 } 1856 1857 SectionLabel("PRINTER") 1858 1859 FlowRow( 1860 horizontalArrangement = Arrangement.spacedBy(7.dp) 1861 ) { 1862 listOf( 1863 "Bambu P1S #1", 1864 "Bambu P1S #2", 1865 "Creality K2 Plus", 1866 "Decide Later" 1867 ).forEach { 1868 FilterChip( 1869 selected = printer == it, 1870 onClick = { printer = it }, 1871 label = { Text(it) } 1872 ) 1873 } 1874 } 1875 1876 Spacer(modifier = Modifier.height(8.dp)) 1877 SectionLabel("MATERIAL") 1878 1879 FlowRow( 1880 horizontalArrangement = Arrangement.spacedBy(7.dp) 1881 ) { 1882 listOf( 1883 "PLA", 1884 "PETG", 1885 "TPU", 1886 "Decide Later" 1887 ).forEach { 1888 FilterChip( 1889 selected = material == it, 1890 onClick = { material = it }, 1891 label = { Text(it) } 1892 ) 1893 } 1894 } 1895 1896 Spacer(modifier = Modifier.height(10.dp)) 1897 1898 Card( 1899 colors = CardDefaults.cardColors(containerColor = Raised) 1900 ) { 1901 Text( 1902 text = "Submitting creates a Safe Plan only. Learning, design, " + 1903 "slicing and printing remain separately locked.", 1904 modifier = Modifier.padding(12.dp), 1905 color = Green, 1906 fontSize = 12.sp 1907 ) 1908 } 1909 } 1910 }, 1911 confirmButton = { 1912 Button( 1913 enabled = request.isNotBlank(), 1914 onClick = { 1915 onCreatePlan( 1916 request.trim(), 1917 "$selectedMode • $printer • $material" 1918 ) 1919 } 1920 ) { 1921 Text("Create Safe Plan") 1922 } 1923 }, 1924 dismissButton = { 1925 TextButton(onClick = onDismiss) { 1926 Text("Cancel") 1927 } 1928 }, 1929 containerColor = Surface 1930 ) 1931 } 1932 1933 @Composable 1934 private fun ApprovalCentreDialog( 1935 approvals: List, 1936 onDismiss: () -> Unit, 1937 onDecision: (Int, String) -> Unit 1938 ) { 1939 var selectedId by remember { 1940 mutableIntStateOf( 1941 approvals.firstOrNull { it.status == "Pending" }?.id 1942 ?: approvals.firstOrNull()?.id 1943 ?: -1 1944 ) 1945 } 1946 1947 val selected = approvals.firstOrNull { it.id == selectedId } 1948 1949 AlertDialog( 1950 onDismissRequest = onDismiss, 1951 title = { 1952 Column { 1953 Text( 1954 text = "Approval Centre", 1955 fontSize = 24.sp, 1956 fontWeight = FontWeight.Black 1957 ) 1958 1959 Text( 1960 text = "${approvals.count { it.status == "Pending" }} pending", 1961 color = Amber 1962 ) 1963 } 1964 }, 1965 text = { 1966 Column( 1967 modifier = Modifier 1968 .heightIn(max = 610.dp) 1969 .verticalScroll(rememberScrollState()) 1970 ) { 1971 approvals.forEach { item -> 1972 FilterChip( 1973 selected = selectedId == item.id, 1974 onClick = { selectedId = item.id }, 1975 label = { 1976 Text("${item.stage}: ${item.title} — ${item.status}") 1977 }, 1978 modifier = Modifier.fillMaxWidth() 1979 ) 1980 } 1981 1982 selected?.let { item -> 1983 Spacer(modifier = Modifier.height(12.dp)) 1984 1985 ApprovalDetail("STAGE", item.stage, Amber) 1986 ApprovalDetail("PURPOSE", item.purpose, TextPrimary) 1987 ApprovalDetail("AFFECTS", item.affects, Blue) 1988 ApprovalDetail("RISK", item.risk, Red) 1989 ApprovalDetail("ESTIMATE", item.estimate, Green) 1990 ApprovalDetail("ROLLBACK", item.rollback, TextMuted) 1991 ApprovalDetail("STATUS", item.status, Amber) 1992 1993 if (item.status == "Pending") { 1994 Text( 1995 text = "Approval applies only to this exact stage.", 1996 color = Green, 1997 fontSize = 12.sp 1998 ) 1999 2000 Spacer(modifier = Modifier.height(10.dp)) 2001 2002 FlowRow( 2003 horizontalArrangement = Arrangement.spacedBy(8.dp), 2004 verticalArrangement = Arrangement.spacedBy(8.dp) 2005 ) { 2006 Button( 2007 onClick = { 2008 onDecision(item.id, "Approved") 2009 } 2010 ) { 2011 Text("Approve Stage") 2012 } 2013 2014 OutlinedButton( 2015 onClick = { 2016 onDecision(item.id, "Changes Requested") 2017 } 2018 ) { 2019 Text("Request Changes") 2020 } 2021 2022 OutlinedButton( 2023 onClick = { 2024 onDecision(item.id, "Deferred") 2025 } 2026 ) { 2027 Text("Defer") 2028 } 2029 2030 Button( 2031 onClick = { 2032 onDecision(item.id, "Rejected") 2033 }, 2034 colors = ButtonDefaults.buttonColors( 2035 containerColor = Red, 2036 contentColor = Color.Black 2037 ) 2038 ) { 2039 Text("Reject") 2040 } 2041 } 2042 } 2043 } 2044 } 2045 }, 2046 confirmButton = { 2047 TextButton(onClick = onDismiss) { 2048 Text("Close") 2049 } 2050 }, 2051 containerColor = Surface 2052 ) 2053 } 2054 2055 @Composable 2056 private fun ApprovalDetail( 2057 label: String, 2058 value: String, 2059 valueColor: Color 2060 ) { 2061 SectionLabel(label) 2062 2063 Text( 2064 text = value, 2065 color = valueColor, 2066 lineHeight = 19.sp 2067 ) 2068 2069 Spacer(modifier = Modifier.height(8.dp)) 2070 } ============================================================ UI AND RESOURCE FILES ============================================================ FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/AndroidManifest.xml 1 2 3 4 12 13 17 18 19 20 21 22 23 24 25 26 27 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/drawable/shire_icon_foreground.xml 1 2 7 8 13 14 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/drawable/shire_icon_monochrome.xml 1 2 7 8 11 12 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 1 2 3 4 5 6 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 1 2 3 4 5 6 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi/ic_launcher.xml 1 2 3 4 10 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml 1 2 3 4 10 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/icon_colors.xml 1 2 3 #090B10 4 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/strings.xml 1 2 3 SHiRE Mobile 4 FILE: /home/ray/ARMOR/apps/shire-mobile/app/src/main/res/values/themes.xml 1 2 3 10 ============================================================ RELATED MINI API SOURCES ============================================================ /home/shire3d/ARMOR/agents/apps/flexiforge/backend/app.py /home/shire3d/ARMOR/agents/apps/flexiforge/integration/shire_tool_manifest.json /home/shire3d/ARMOR/agents/apps/flexiforge/shire_flexiforge.py /home/shire3d/ARMOR/agents/apps/flexiforge/tests/test_api_shire.py /home/shire3d/ARMOR/agents/apps/flexiforge/tests/test_shire_bridge.py /home/shire3d/ARMOR/agents/apps/trendscout/contracts/weekly-report.schema.json /home/shire3d/ARMOR/agents/apps/trendscout/source_catalogue.json /home/shire3d/ARMOR/agents/apps/trendscout/trendscout_api.py /home/shire3d/ARMOR/agents/apps/trendscout/trendscout_google_trends_import.py /home/shire3d/ARMOR/agents/apps/trendscout/trendscout_google_trends_selftest.py /home/shire3d/ARMOR/agents/apps/trendscout/trendscout_weekly_intelligence.py /home/shire3d/ARMOR/agents/definitions/flexiforge/agent.manifest.json /home/shire3d/ARMOR/agents/definitions/store-doctor/agent.manifest.json /home/shire3d/ARMOR/agents/definitions/trendscout/agent.manifest.json /home/shire3d/ARMOR/agents/runtime/agent_hub.py /home/shire3d/ARMOR/agents/runtime/specialist_agent_runtime.py /home/shire3d/ARMOR/ai/brain_server.py /home/shire3d/ARMOR/apps/shire-academy-full-ui-staging/node_modules/globals/globals.json /home/shire3d/ARMOR/apps/shire-academy-full-ui-staging/node_modules/sonner/package.json /home/shire3d/ARMOR/apps/shire-academy/server/autonomous_practice_cadquery.py /home/shire3d/ARMOR/apps/shire-academy/server/learning_bridge.py /home/shire3d/ARMOR/apps/shire-academy/server/shire_academy_server.py /home/shire3d/ARMOR/apps/shire-academy/server/stage3c7_safe_plans.py /home/shire3d/ARMOR/backups/ENG-SEC-0002A_20260708_143637/security_service.py /home/shire3d/ARMOR/backups/ENG-SEC-0002A_20260708_143637/shire.py /home/shire3d/ARMOR/codex/prompt_builder.py /home/shire3d/ARMOR/config/academy.json /home/shire3d/ARMOR/config/agents/registry.json /home/shire3d/ARMOR/config/education.json /home/shire3d/ARMOR/config/vision_modes.json /home/shire3d/ARMOR/config/voice_privacy.json /home/shire3d/ARMOR/core/shire_runtime.py /home/shire3d/ARMOR/core/task_manager.py /home/shire3d/ARMOR/data/academy/design_mastery/FILE_INVENTORY.json /home/shire3d/ARMOR/data/academy/design_mastery/curriculum/catalog.json /home/shire3d/ARMOR/data/academy/design_mastery/runtime/commercial_design_sprint.json /home/shire3d/ARMOR/data/academy/design_mastery/runtime/practice_state.json /home/shire3d/ARMOR/data/blender_codex/manifest.json /home/shire3d/ARMOR/data/blender_dry_run_plans/BDR-0001_from_BSD-0001/manifest.json /home/shire3d/ARMOR/data/blender_dry_run_plans/dry_run_registry.json /home/shire3d/ARMOR/data/blender_headless_probes/BHP-0001_from_BHR-0001/manifest.json /home/shire3d/ARMOR/data/blender_headless_probes/headless_probe_registry.json /home/shire3d/ARMOR/data/blender_headless_runs/BHR-0001_from_BDR-0001/manifest.json /home/shire3d/ARMOR/data/blender_headless_runs/headless_run_registry.json /home/shire3d/ARMOR/data/blender_mastery_projects/BMP-0001/history/manifest-event-000002.json /home/shire3d/ARMOR/data/blender_mastery_projects/BMP-0001/manifest.json /home/shire3d/ARMOR/data/forge_learning_queue.json /home/shire3d/ARMOR/data/forge_research_queue.json /home/shire3d/ARMOR/data/sentinel/alert_bridge_status.json /home/shire3d/ARMOR/data/sentinel/latest_alert.json /home/shire3d/ARMOR/data/shire3d_commercial_projects/COMM-0001/job.json /home/shire3d/ARMOR/data/shire3d_commercial_projects/COMM-0001/mark-ii-concept.json /home/shire3d/ARMOR/data/shire3d_commercial_projects/COMM-0001/mark-iii-industrial-design.json /home/shire3d/ARMOR/data/shire3d_commercial_projects/COMM-0001/mark-iv-design-convergence.json /home/shire3d/ARMOR/memory/shire_runtime_memory.json /home/shire3d/ARMOR/modules/agents.py /home/shire3d/ARMOR/modules/dashboard.py /home/shire3d/ARMOR/modules/education.py /home/shire3d/ARMOR/modules/forge.py /home/shire3d/ARMOR/modules/shire.py /home/shire3d/ARMOR/modules/tasks.py /home/shire3d/ARMOR/modules/unified_academy_dashboard.py /home/shire3d/ARMOR/modules/unified_learning.py /home/shire3d/ARMOR/services/blender_mastery_commands.py /home/shire3d/ARMOR/services/blender_mastery_service.py /home/shire3d/ARMOR/services/blender_mastery_state.py /home/shire3d/ARMOR/services/face_recognition_service.py /home/shire3d/ARMOR/services/forge_learning_service.py /home/shire3d/ARMOR/services/notifications.py /home/shire3d/ARMOR/services/security_service.py /home/shire3d/ARMOR/tests/test_blender_mastery_service.py /home/shire3d/ARMOR/tests/test_blender_mastery_state.py ============================================================ RELEVANT SERVICES ============================================================ apparmor.service loaded active exited Load AppArmor profiles shire-academy-autonomous-practice.service loaded inactive dead SHiRE Academy Autonomous Dynamic Dice Envelope Core v3 Practice Worker shire-academy-cadquery-fast.service loaded active running SHiRE Academy Persistent CadQuery Fast Engine shire-academy-safe-autonomous.service loaded activating start start SHiRE Academy Safe Autonomous Supported-Curriculum Tick shire-academy-ui.service loaded active running SHiRE Academy Local UI shire-agent-hub-tailnet.service loaded inactive dead Private Tailnet proxy for SHiRE Agent Hub shire-agent-hub.service loaded active running SHiRE Specialist Agent Hub shire-agent@covercanvas.service loaded active running SHiRE Specialist Agent Runtime — covercanvas shire-agent@limbforge.service loaded active running SHiRE Specialist Agent Runtime — limbforge shire-agent@scanforge.service loaded active running SHiRE Specialist Agent Runtime — scanforge shire-brain.service loaded active running SHIRE Brain API Server shire-marketing-boss-tailnet.service loaded active running Private Tailnet proxy for SHiRE Marketing Boss shire-marketing-boss.service loaded active running SHiRE Marketing Boss Drafting Workspace shire-marketing-cloudflare-tunnel.service loaded active running SHiRE Marketing OAuth Cloudflare Tunnel shire-marketing-oauth-callback.service loaded active running SHiRE Marketing OAuth Callback-Only Gate shire-marketing-pipeline.service loaded active running SHiRE Marketing Boss Instagram Production Pipeline shire-marketing-token-backup.service loaded inactive dead Back up Marketing Boss encrypted OAuth token to SHiREVault shire-marketing-token-lifecycle.service loaded inactive dead SHiRE Marketing Boss Instagram Token Lifecycle shire-marketing-token-reload.service loaded inactive dead Reload SHiRE Marketing Boss after protected Instagram token rotation shire-sentinel-alert.service loaded active running SHiRE Sentinel Live Threat Alert Bridge shire-sentinel-suricata-update.service loaded inactive dead Update SHiRE Sentinel Suricata Threat Rules shire-sentinel-suricata.service loaded active running SHiRE Sentinel Suricata Passive Network IDS shire-trendscout-tailnet.service loaded active running Private Tailnet proxy for SHiRE TrendScout shire-trendscout-weekly-intelligence.service loaded inactive dead SHiRE TrendScout Weekly Intelligence shire-trendscout.service loaded active running SHiRE TrendScout Native Read-Only Workspace shire-unified-learning.service loaded active running SHiRE Unified Learning 0002 Local Service