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