refactor(ios): minimal full-screen canvas
This commit is contained in:
@@ -7,7 +7,7 @@ struct ClawdisNodeApp: App {
|
|||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
RootTabs()
|
RootCanvas()
|
||||||
.environmentObject(self.appModel)
|
.environmentObject(self.appModel)
|
||||||
.environmentObject(self.appModel.voiceWake)
|
.environmentObject(self.appModel.voiceWake)
|
||||||
.onChange(of: self.scenePhase) { _, newValue in
|
.onChange(of: self.scenePhase) { _, newValue in
|
||||||
|
|||||||
29
apps/ios/Sources/RootCanvas.swift
Normal file
29
apps/ios/Sources/RootCanvas.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RootCanvas: View {
|
||||||
|
@State private var isShowingSettings = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .topTrailing) {
|
||||||
|
ScreenTab()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
self.isShowingSettings = true
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "gearshape.fill")
|
||||||
|
.font(.system(size: 16, weight: .semibold))
|
||||||
|
.foregroundStyle(.primary)
|
||||||
|
.padding(10)
|
||||||
|
.background(.thinMaterial)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.trailing, 10)
|
||||||
|
.accessibilityLabel("Settings")
|
||||||
|
}
|
||||||
|
.sheet(isPresented: self.$isShowingSettings) {
|
||||||
|
SettingsTab()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,15 @@ import WebKit
|
|||||||
final class ScreenController: ObservableObject {
|
final class ScreenController: ObservableObject {
|
||||||
let webView: WKWebView
|
let webView: WKWebView
|
||||||
|
|
||||||
@Published var mode: ClawdisScreenMode = .web
|
@Published var mode: ClawdisScreenMode = .canvas
|
||||||
@Published var urlString: String = "https://example.com"
|
@Published var urlString: String = ""
|
||||||
@Published var errorText: String?
|
@Published var errorText: String?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let config = WKWebViewConfiguration()
|
let config = WKWebViewConfiguration()
|
||||||
config.websiteDataStore = .nonPersistent()
|
config.websiteDataStore = .nonPersistent()
|
||||||
self.webView = WKWebView(frame: .zero, configuration: config)
|
self.webView = WKWebView(frame: .zero, configuration: config)
|
||||||
|
self.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMode(_ mode: ClawdisScreenMode) {
|
func setMode(_ mode: ClawdisScreenMode) {
|
||||||
@@ -91,29 +92,37 @@ final class ScreenController: ObservableObject {
|
|||||||
:root { color-scheme: dark; }
|
:root { color-scheme: dark; }
|
||||||
html,body { height:100%; margin:0; }
|
html,body { height:100%; margin:0; }
|
||||||
body {
|
body {
|
||||||
font: 13px -apple-system, system-ui;
|
background:#000;
|
||||||
display:flex;
|
|
||||||
align-items:center;
|
|
||||||
justify-content:center;
|
|
||||||
background:#0b1020;
|
|
||||||
color:#e5e7eb;
|
|
||||||
}
|
}
|
||||||
.card {
|
canvas {
|
||||||
max-width: 520px;
|
display:block;
|
||||||
padding: 18px;
|
width:100vw;
|
||||||
border-radius: 14px;
|
height:100vh;
|
||||||
border: 1px solid rgba(255,255,255,.10);
|
|
||||||
background: rgba(255,255,255,.06);
|
|
||||||
box-shadow: 0 18px 60px rgba(0,0,0,.35);
|
|
||||||
}
|
}
|
||||||
.muted { color: rgba(229,231,235,.75); margin-top: 8px; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="card">
|
<canvas id="clawdis-canvas"></canvas>
|
||||||
<div style="font-weight:600; font-size:14px;">Canvas scaffold</div>
|
<script>
|
||||||
<div class="muted">Next: agent-driven on-disk workspace.</div>
|
(() => {
|
||||||
</div>
|
const canvas = document.getElementById('clawdis-canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
const w = Math.max(1, Math.floor(window.innerWidth * dpr));
|
||||||
|
const h = Math.max(1, Math.floor(window.innerHeight * dpr));
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
resize();
|
||||||
|
|
||||||
|
window.__clawdis = { canvas, ctx };
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -5,31 +5,19 @@ struct ScreenTab: View {
|
|||||||
@EnvironmentObject private var appModel: NodeAppModel
|
@EnvironmentObject private var appModel: NodeAppModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
ZStack(alignment: .top) {
|
||||||
VStack(spacing: 0) {
|
ScreenWebView(controller: self.appModel.screen)
|
||||||
ScreenWebView(controller: self.appModel.screen)
|
.ignoresSafeArea()
|
||||||
.overlay(alignment: .top) {
|
.overlay(alignment: .top) {
|
||||||
if let errorText = self.appModel.screen.errorText {
|
if let errorText = self.appModel.screen.errorText {
|
||||||
Text(errorText)
|
Text(errorText)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(.thinMaterial)
|
.background(.thinMaterial)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||||
.padding()
|
.padding()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.appModel.isBackgrounded {
|
|
||||||
Divider()
|
|
||||||
Text("Screen commands unavailable while backgrounded.")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.navigationTitle("Screen")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user