diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt index d2e3afdab..ae4dcedea 100644 --- a/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt +++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/RootScreen.kt @@ -3,9 +3,12 @@ package com.steipete.clawdis.node.ui import android.annotation.SuppressLint import android.webkit.WebView import android.webkit.WebViewClient +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -26,7 +29,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.compose.ui.zIndex +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties import com.steipete.clawdis.node.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -37,23 +41,22 @@ fun RootScreen(viewModel: MainViewModel) { val safeButtonInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal) Box(modifier = Modifier.fillMaxSize()) { - CanvasView(viewModel = viewModel, modifier = Modifier.fillMaxSize().zIndex(0f)) + CanvasView(viewModel = viewModel, modifier = Modifier.fillMaxSize()) + } - Box( + // Keep the overlay buttons above the WebView canvas (AndroidView), otherwise they may not receive touches. + Popup(alignment = Alignment.TopCenter, properties = PopupProperties(focusable = false)) { + Row( modifier = Modifier - .fillMaxSize() - .zIndex(1f) + .fillMaxWidth() .windowInsetsPadding(safeButtonInsets) .padding(12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { - Box(modifier = Modifier.align(Alignment.TopStart)) { - Button(onClick = { sheet = Sheet.Chat }) { Text("Chat") } - } - - Box(modifier = Modifier.align(Alignment.TopEnd)) { - Button(onClick = { sheet = Sheet.Settings }) { Text("Settings") } - } + Button(onClick = { sheet = Sheet.Chat }) { Text("Chat") } + Button(onClick = { sheet = Sheet.Settings }) { Text("Settings") } } } @@ -65,7 +68,6 @@ fun RootScreen(viewModel: MainViewModel) { when (sheet) { Sheet.Chat -> ChatSheet(viewModel = viewModel) Sheet.Settings -> SettingsSheet(viewModel = viewModel) - null -> {} } } } diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/SettingsSheet.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/SettingsSheet.kt index 7fdb91046..6bfa0b428 100644 --- a/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/SettingsSheet.kt +++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/ui/SettingsSheet.kt @@ -9,10 +9,16 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -62,7 +68,12 @@ fun SettingsSheet(viewModel: MainViewModel) { LazyColumn( state = listState, - modifier = Modifier.fillMaxWidth().imePadding(), + modifier = + Modifier + .fillMaxWidth() + .fillMaxHeight() + .imePadding() + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(14.dp), ) { diff --git a/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift b/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift index 1470496b0..a095999d8 100644 --- a/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift +++ b/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift @@ -6,7 +6,7 @@ struct MasterDiscoveryInlineList: View { var discovery: MasterDiscoveryModel var currentTarget: String? var onSelect: (MasterDiscoveryModel.DiscoveredMaster) -> Void - @State private var hoveredMasterID: MasterDiscoveryModel.DiscoveredMaster.ID? + @State private var hoveredGatewayID: MasterDiscoveryModel.DiscoveredMaster.ID? var body: some View { VStack(alignment: .leading, spacing: 6) { @@ -25,19 +25,19 @@ struct MasterDiscoveryInlineList: View { .foregroundStyle(.secondary) } else { VStack(alignment: .leading, spacing: 6) { - ForEach(self.discovery.masters.prefix(6)) { master in - let target = self.suggestedSSHTarget(master) + ForEach(self.discovery.masters.prefix(6)) { gateway in + let target = self.suggestedSSHTarget(gateway) let selected = target != nil && self.currentTarget? .trimmingCharacters(in: .whitespacesAndNewlines) == target Button { withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) { - self.onSelect(master) + self.onSelect(gateway) } } label: { HStack(alignment: .center, spacing: 10) { VStack(alignment: .leading, spacing: 2) { - Text(master.displayName) + Text(gateway.displayName) .font(.callout.weight(.semibold)) .lineLimit(1) .truncationMode(.tail) @@ -65,7 +65,7 @@ struct MasterDiscoveryInlineList: View { RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(self.rowBackground( selected: selected, - hovered: self.hoveredMasterID == master.id))) + hovered: self.hoveredGatewayID == gateway.id))) .overlay( RoundedRectangle(cornerRadius: 10, style: .continuous) .strokeBorder( @@ -75,8 +75,8 @@ struct MasterDiscoveryInlineList: View { } .buttonStyle(.plain) .onHover { hovering in - self.hoveredMasterID = hovering ? master - .id : (self.hoveredMasterID == master.id ? nil : self.hoveredMasterID) + self.hoveredGatewayID = hovering ? gateway + .id : (self.hoveredGatewayID == gateway.id ? nil : self.hoveredGatewayID) } } } @@ -89,13 +89,13 @@ struct MasterDiscoveryInlineList: View { .help("Click a discovered master to fill the SSH target.") } - private func suggestedSSHTarget(_ master: MasterDiscoveryModel.DiscoveredMaster) -> String? { - let host = master.tailnetDns ?? master.lanHost + private func suggestedSSHTarget(_ gateway: MasterDiscoveryModel.DiscoveredMaster) -> String? { + let host = gateway.tailnetDns ?? gateway.lanHost guard let host else { return nil } let user = NSUserName() var target = "\(user)@\(host)" - if master.sshPort != 22 { - target += ":\(master.sshPort)" + if gateway.sshPort != 22 { + target += ":\(gateway.sshPort)" } return target } @@ -118,8 +118,8 @@ struct MasterDiscoveryMenu: View { Button(self.discovery.statusText) {} .disabled(true) } else { - ForEach(self.discovery.masters) { master in - Button(master.displayName) { self.onSelect(master) } + ForEach(self.discovery.masters) { gateway in + Button(gateway.displayName) { self.onSelect(gateway) } } } } label: { diff --git a/apps/macos/Sources/Clawdis/MasterDiscoveryModel.swift b/apps/macos/Sources/Clawdis/MasterDiscoveryModel.swift index 3a547d63b..128468d35 100644 --- a/apps/macos/Sources/Clawdis/MasterDiscoveryModel.swift +++ b/apps/macos/Sources/Clawdis/MasterDiscoveryModel.swift @@ -4,8 +4,8 @@ import Observation // We use “master” as the on-the-wire service name; keep the model aligned with the protocol/docs. @MainActor -// swiftlint:disable:next inclusive_language @Observable +// swiftlint:disable:next inclusive_language final class MasterDiscoveryModel { // swiftlint:disable:next inclusive_language struct DiscoveredMaster: Identifiable, Equatable { diff --git a/apps/macos/Sources/Clawdis/Onboarding.swift b/apps/macos/Sources/Clawdis/Onboarding.swift index c0a0b7b5f..abf4a1e1a 100644 --- a/apps/macos/Sources/Clawdis/Onboarding.swift +++ b/apps/macos/Sources/Clawdis/Onboarding.swift @@ -213,7 +213,8 @@ struct OnboardingView: View { "The connected AI agent (e.g. Claude) can trigger powerful actions on your Mac, " + "including running commands, reading/writing files, and capturing screenshots — " + "depending on the permissions you grant.\n\n" + - "Only enable Clawdis if you understand the risks and trust the prompts and integrations you use.") + "Only enable Clawdis if you understand the risks and trust the prompts and " + + "integrations you use.") .font(.subheadline) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -529,8 +530,10 @@ struct OnboardingView: View { } Text( - "This writes your identity to `~/.clawdis/clawdis.json` and into `AGENTS.md` inside the workspace. " + - "Treat that workspace as the agent’s “memory” and consider making it a (private) git repo.") + "This writes your identity to `~/.clawdis/clawdis.json` and into `AGENTS.md` " + + "inside the workspace. " + + "Treat that workspace as the agent’s “memory” and consider making it a (private) git " + + "repo.") .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true)