iOS: Fix canvas touch events and auto-hide status bubble

- Disable scroll on WKWebView to allow touch events to reach canvas
- Add WKNavigationDelegate to intercept clawdis:// deep links from canvas
- Wire up onDeepLink callback to handle taps on canvas buttons
- Auto-hide status bubble after 3 seconds
This commit is contained in:
Peter Steinberger
2025-12-14 05:14:26 +00:00
parent 26bbddde8f
commit a48aebc78c
2 changed files with 52 additions and 0 deletions

View File

@@ -35,6 +35,14 @@ final class NodeAppModel {
let enabled = UserDefaults.standard.bool(forKey: "voiceWake.enabled")
self.voiceWake.setEnabled(enabled)
// Wire up deep links from canvas taps
self.screen.onDeepLink = { [weak self] url in
guard let self else { return }
Task { @MainActor in
await self.handleDeepLink(url: url)
}
}
}
func setScenePhase(_ phase: ScenePhase) {

View File

@@ -7,14 +7,19 @@ import WebKit
@Observable
final class ScreenController {
let webView: WKWebView
private let navigationDelegate: ScreenNavigationDelegate
var mode: ClawdisScreenMode = .canvas
var urlString: String = ""
var errorText: String?
/// Callback invoked when a clawdis:// deep link is tapped in the canvas
var onDeepLink: ((URL) -> Void)?
init() {
let config = WKWebViewConfiguration()
config.websiteDataStore = .nonPersistent()
self.navigationDelegate = ScreenNavigationDelegate()
self.webView = WKWebView(frame: .zero, configuration: config)
self.webView.isOpaque = false
self.webView.backgroundColor = .clear
@@ -23,6 +28,11 @@ final class ScreenController {
self.webView.scrollView.contentInset = .zero
self.webView.scrollView.scrollIndicatorInsets = .zero
self.webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
// Disable scroll to allow touch events to pass through to canvas
self.webView.scrollView.isScrollEnabled = false
self.webView.scrollView.bounces = false
self.webView.navigationDelegate = self.navigationDelegate
self.navigationDelegate.controller = self
self.reload()
}
@@ -195,6 +205,11 @@ final class ScreenController {
statusEl.style.display = 'grid';
if (titleEl && typeof title === 'string') titleEl.textContent = title;
if (subtitleEl && typeof subtitle === 'string') subtitleEl.textContent = subtitle;
// Auto-hide after 3 seconds
clearTimeout(window.__statusTimeout);
window.__statusTimeout = setTimeout(() => {
statusEl.style.display = 'none';
}, 3000);
}
};
})();
@@ -203,3 +218,32 @@ final class ScreenController {
</html>
"""
}
// MARK: - Navigation Delegate
/// Handles navigation policy to intercept clawdis:// deep links from canvas
private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
weak var controller: ScreenController?
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// Intercept clawdis:// deep links
if url.scheme == "clawdis" {
decisionHandler(.cancel)
Task { @MainActor in
self.controller?.onDeepLink?(url)
}
return
}
decisionHandler(.allow)
}
}