From 73e17676b58ef5701654a89089bd0c7a6ab4d3c8 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 9 Jul 2026 11:54:30 +0000 Subject: [PATCH] feat: match Happy Engineering mobile screenshots --- .../molberg/hermesmobile/MainActivity.kt | 113 ++++++++++-------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/MainActivity.kt b/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/MainActivity.kt index e0ef4e0..fae9853 100644 --- a/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/MainActivity.kt +++ b/apps/mobile/android/app/src/main/java/cloud/molberg/hermesmobile/MainActivity.kt @@ -105,11 +105,9 @@ import java.net.URLEncoder import java.util.concurrent.TimeUnit enum class Screen(val label: String, val icon: ImageVector, val title: String, val kicker: String) { - Quest("Chat", Icons.Filled.ChatBubble, "Agent", "Claude Code mobile client"), - Files("Files", Icons.Filled.Folder, "Workspace", "Project files"), - Terminal("Terminal", Icons.Filled.Terminal, "Terminal", "Run commands"), - Status("Status", Icons.Outlined.Bolt, "Sessions", "Companion status"), - Settings("Settings", Icons.Filled.Settings, "Settings", "Pair companion") + Inbox("Inbox", Icons.Filled.ChatBubble, "Inbox", "connected"), + Terminals("Terminals", Icons.Filled.Terminal, "Terminals", "connected"), + Settings("Settings", Icons.Filled.Settings, "Settings", "connected") } data class FileEntry(val name: String, val path: String, val type: String, val size: Long) @@ -231,7 +229,7 @@ class MainActivity : ComponentActivity() { fun HermesNativeApp() { val context = LocalContext.current val api = remember { CompanionApi(context.applicationContext) } - var screen by remember { mutableStateOf(Screen.Quest) } + var screen by remember { mutableStateOf(Screen.Inbox) } var online by remember { mutableStateOf(false) } LaunchedEffect(screen) { @@ -240,8 +238,8 @@ fun HermesNativeApp() { MaterialTheme( colorScheme = darkColorScheme( - background = Cream, - surface = Paper, + background = AppBackground, + surface = RowSurface, primary = Teal, onPrimary = Color.Black, onBackground = Ink, @@ -250,7 +248,7 @@ fun HermesNativeApp() { ) { Scaffold( modifier = Modifier.fillMaxSize(), - containerColor = Cream, + containerColor = AppBackground, topBar = { NativeTopBar(screen, online) }, bottomBar = { NativeBottomBar( @@ -272,10 +270,8 @@ fun HermesNativeApp() { label = "screen-slide" ) { active -> when (active) { - Screen.Quest -> QuestScreen(api) - Screen.Files -> FilesScreen(api) - Screen.Terminal -> TerminalScreen(api) - Screen.Status -> StatusScreen(api) + Screen.Inbox -> InboxScreen(api) + Screen.Terminals -> TerminalsScreen(api) Screen.Settings -> SettingsScreen(api) } } @@ -312,37 +308,26 @@ fun NativeTopBar(screen: Screen, online: Boolean) { .padding(horizontal = 16.dp, vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(10.dp) ) { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { - RaisedCircle(color = Purple, shadow = PurpleDark, content = "H") - Column(Modifier.weight(1f)) { - TinyLabel(screen.kicker) - Text(screen.title, fontSize = 30.sp, fontWeight = FontWeight.Black, color = Ink, lineHeight = 31.sp) - } - StatusDot(online) + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Text(screen.title, Modifier.weight(1f), fontSize = 24.sp, fontWeight = FontWeight.SemiBold, color = Ink, lineHeight = 28.sp) + if (screen == Screen.Terminals) Text("+", color = Ink, fontSize = 26.sp, fontWeight = FontWeight.Light) } - RaisedCard( - color = if (online) TealPanel else WarningPanel, - shadow = PanelShadow, - border = if (online) TealBorder else WarningBorder, - modifier = Modifier.fillMaxWidth() - ) { - Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Icon(if (online) Icons.Filled.Wifi else Icons.Filled.WifiOff, contentDescription = null, tint = if (online) Teal else Orange) - Text(if (online) "Companion connected" else "Set companion URL and access key", color = if (online) Teal else Orange, fontWeight = FontWeight.Black) - } + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Box(Modifier.size(8.dp).clip(CircleShape).background(if (online) Teal else Muted)) + Text(if (online) "connected" else "offline", color = if (online) Teal else Muted, fontWeight = FontWeight.SemiBold, fontSize = 13.sp) } } } @Composable fun NativeBottomBar(current: Screen, onSelect: (Screen) -> Unit) { - Surface(shadowElevation = 14.dp, color = Paper, tonalElevation = 0.dp) { + Surface(shadowElevation = 0.dp, color = AppBackground, tonalElevation = 0.dp) { NavigationBar( modifier = Modifier .fillMaxWidth() .navigationBarsPadding() - .height(82.dp), - containerColor = Paper, + .height(76.dp), + containerColor = AppBackground, tonalElevation = 0.dp ) { Screen.entries.forEach { item -> @@ -352,9 +337,9 @@ fun NativeBottomBar(current: Screen, onSelect: (Screen) -> Unit) { icon = { Icon(item.icon, contentDescription = item.label) }, label = { Text(item.label, fontWeight = FontWeight.Black, fontSize = 11.sp) }, colors = NavigationBarItemDefaults.colors( - selectedIconColor = Teal, - selectedTextColor = Teal, - indicatorColor = TealPanel, + selectedIconColor = Ink, + selectedTextColor = Ink, + indicatorColor = Color.Transparent, unselectedIconColor = Muted, unselectedTextColor = Muted ) @@ -365,7 +350,7 @@ fun NativeBottomBar(current: Screen, onSelect: (Screen) -> Unit) { } @Composable -fun QuestScreen(api: CompanionApi) { +fun InboxScreen(api: CompanionApi) { val scope = remember { CoroutineScope(Dispatchers.Main) } var prompt by remember { mutableStateOf("") } var busy by remember { mutableStateOf(false) } @@ -373,12 +358,7 @@ fun QuestScreen(api: CompanionApi) { val messages = remember { mutableStateListOf() } ScreenList { - HeroCard(icon = Icons.Filled.ChatBubble, title = "Claude Code from anywhere.", label = "Agent control", text = "Send prompts to your local Hermes companion and continue work from your phone.") - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - PathPill(Icons.Filled.CheckCircle, "Connect", true) - PathPill(Icons.Outlined.Bolt, "Prompt", true, current = true) - PathPill(Icons.Filled.ChatBubble, "Review", false) - } + Text("Hello!", color = Ink, fontSize = 32.sp, fontWeight = FontWeight.Normal, modifier = Modifier.padding(top = 18.dp, bottom = 24.dp)) if (messages.isEmpty()) EmptyLesson() messages.forEach { Bubble(it) } if (error.isNotBlank()) ErrorCard(error) @@ -453,7 +433,7 @@ fun FilesScreen(api: CompanionApi) { } @Composable -fun TerminalScreen(api: CompanionApi) { +fun TerminalsScreen(api: CompanionApi) { val scope = remember { CoroutineScope(Dispatchers.Main) } var cwd by remember { mutableStateOf(".") } var command by remember { mutableStateOf("pwd && ls") } @@ -461,7 +441,11 @@ fun TerminalScreen(api: CompanionApi) { var busy by remember { mutableStateOf(false) } ScreenList { - HeroCard(icon = Icons.Filled.Terminal, title = "Run workspace commands.", label = "Terminal", text = "Execute commands on the paired machine without leaving the app.", orbColor = Purple, orbShadow = PurpleDark) + SectionLabel("MACHINES") + ListRow(icon = Icons.Filled.Terminal, title = "Hermes", subtitle = "hermes-mobile • online") {} + TextButton(onClick = {}, modifier = Modifier.fillMaxWidth()) { Text("Show archived", color = Muted, fontWeight = FontWeight.SemiBold) } + SectionLabel("NEW CHAT") + ListRow(icon = Icons.Filled.ChatBubble, title = "New chat", subtitle = "Start a session on the paired machine") {} RaisedCard(Modifier.fillMaxWidth()) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { FieldLabel("Working directory") @@ -515,7 +499,12 @@ fun SettingsScreen(api: CompanionApi) { var status by remember { mutableStateOf("Paste the companion access key, then test the connection.") } ScreenList { - CompactQuest(title = "Pair companion", label = "Settings", text = status, icon = Icons.Filled.Settings, orbColor = Purple, orbShadow = PurpleDark) {} + SectionLabel("MACHINES") + ListRow(icon = Icons.Filled.Terminal, title = "Hermes", subtitle = "linux • online") {} + SectionLabel("FEATURES") + ListRow(icon = Icons.Filled.Settings, title = "Appearance", subtitle = "Customize how the app looks") {} + ListRow(icon = Icons.Outlined.Bolt, title = "Agent Defaults", subtitle = "Default model, effort, and permissions") {} + SectionLabel("COMPANION") RaisedCard(Modifier.fillMaxWidth()) { Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { FieldLabel("Companion server URL") @@ -547,13 +536,31 @@ fun ScreenList(content: @Composable ColumnScope.() -> Unit) { } @Composable -fun RaisedCard(modifier: Modifier = Modifier, color: Color = Paper, shadow: Color = NeutralShadow, border: Color = NeutralBorder, content: @Composable () -> Unit) { - Box(modifier.padding(bottom = 5.dp)) { - Box(Modifier.matchParentSize().padding(top = 5.dp).clip(RoundedCornerShape(22.dp)).background(shadow)) - Card(modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(22.dp), colors = CardDefaults.cardColors(containerColor = color), border = BorderStroke(2.dp, border), elevation = CardDefaults.cardElevation(0.dp)) { content() } +fun SectionLabel(text: String) { + Text(text, color = Muted, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 0.6.sp, modifier = Modifier.padding(top = 10.dp, start = 2.dp)) +} + +@Composable +fun ListRow(icon: ImageVector, title: String, subtitle: String, onClick: () -> Unit) { + RaisedCard(Modifier.fillMaxWidth()) { + Row(Modifier.clickable(onClick = onClick).padding(horizontal = 14.dp, vertical = 13.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { + Box(Modifier.size(32.dp).clip(RoundedCornerShape(8.dp)).background(Surface), contentAlignment = Alignment.Center) { + Icon(icon, null, tint = Ink, modifier = Modifier.size(19.dp)) + } + Column(Modifier.weight(1f)) { + Text(title, color = Ink, fontWeight = FontWeight.SemiBold, fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(subtitle, color = Muted, fontWeight = FontWeight.Normal, fontSize = 13.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) + } + Text("›", color = Muted, fontSize = 22.sp, fontWeight = FontWeight.Light) + } } } +@Composable +fun RaisedCard(modifier: Modifier = Modifier, color: Color = RowSurface, shadow: Color = Color.Transparent, border: Color = Color.Transparent, content: @Composable () -> Unit) { + Card(modifier = modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp), colors = CardDefaults.cardColors(containerColor = color), border = BorderStroke(0.dp, border), elevation = CardDefaults.cardElevation(0.dp)) { content() } +} + @Composable fun HeroCard(icon: ImageVector, title: String, label: String, text: String, orbColor: Color = Teal, orbShadow: Color = TealDark) { RaisedCard(Modifier.fillMaxWidth()) { @@ -611,9 +618,9 @@ val Red = Color(0xFFF87171) val PaleRed = Color(0xFF311719) val RedBorder = Color(0xFF7A363A) val RedShadow = Color(0xFF0A0304) -val Cream = Color(0xFF000000) -val Paper = Color(0xFF0B0F12) -val Surface = Color(0xFF12181D) +val AppBackground = Color(0xFF212121) +val RowSurface = Color(0xFF303030) +val Surface = Color(0xFF2B2B2B) val Ink = Color(0xFFF4F4F5) val Muted = Color(0xFF9CA3AF) val NeutralBorder = Color(0xFF2A3138)