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 secondsmain
parent
26bbddde8f
commit
a48aebc78c
|
|
@ -35,6 +35,14 @@ final class NodeAppModel {
|
||||||
|
|
||||||
let enabled = UserDefaults.standard.bool(forKey: "voiceWake.enabled")
|
let enabled = UserDefaults.standard.bool(forKey: "voiceWake.enabled")
|
||||||
self.voiceWake.setEnabled(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) {
|
func setScenePhase(_ phase: ScenePhase) {
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,19 @@ import WebKit
|
||||||
@Observable
|
@Observable
|
||||||
final class ScreenController {
|
final class ScreenController {
|
||||||
let webView: WKWebView
|
let webView: WKWebView
|
||||||
|
private let navigationDelegate: ScreenNavigationDelegate
|
||||||
|
|
||||||
var mode: ClawdisScreenMode = .canvas
|
var mode: ClawdisScreenMode = .canvas
|
||||||
var urlString: String = ""
|
var urlString: String = ""
|
||||||
var errorText: String?
|
var errorText: String?
|
||||||
|
|
||||||
|
/// Callback invoked when a clawdis:// deep link is tapped in the canvas
|
||||||
|
var onDeepLink: ((URL) -> Void)?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
let config = WKWebViewConfiguration()
|
let config = WKWebViewConfiguration()
|
||||||
config.websiteDataStore = .nonPersistent()
|
config.websiteDataStore = .nonPersistent()
|
||||||
|
self.navigationDelegate = ScreenNavigationDelegate()
|
||||||
self.webView = WKWebView(frame: .zero, configuration: config)
|
self.webView = WKWebView(frame: .zero, configuration: config)
|
||||||
self.webView.isOpaque = false
|
self.webView.isOpaque = false
|
||||||
self.webView.backgroundColor = .clear
|
self.webView.backgroundColor = .clear
|
||||||
|
|
@ -23,6 +28,11 @@ final class ScreenController {
|
||||||
self.webView.scrollView.contentInset = .zero
|
self.webView.scrollView.contentInset = .zero
|
||||||
self.webView.scrollView.scrollIndicatorInsets = .zero
|
self.webView.scrollView.scrollIndicatorInsets = .zero
|
||||||
self.webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
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()
|
self.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +205,11 @@ final class ScreenController {
|
||||||
statusEl.style.display = 'grid';
|
statusEl.style.display = 'grid';
|
||||||
if (titleEl && typeof title === 'string') titleEl.textContent = title;
|
if (titleEl && typeof title === 'string') titleEl.textContent = title;
|
||||||
if (subtitleEl && typeof subtitle === 'string') subtitleEl.textContent = subtitle;
|
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>
|
</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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue