Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 70 additions & 16 deletions openHAB/UI/OpenHABWebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class OpenHABWebViewController: OpenHABViewController {
private var lastLoadedURL: String? // Track the last successfully loaded URL from didFinish
private var isConfirmingExternalURL = false
private var externalURLCooldownUntil: Date?
private weak var webHistoryBackGesture: UIScreenEdgePanGestureRecognizer?

var hasLoadedPage: Bool {
!currentTarget.isEmpty
Expand Down Expand Up @@ -119,9 +120,10 @@ class OpenHABWebViewController: OpenHABViewController {
super.viewDidLoad()
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
attachWebViewToLayout(webView)
configureWebHistoryGestures(for: webView)

loadingOverlay.backgroundColor = .systemBackground
loadingOverlay.isHidden = true
loadingOverlay.isHidden = false
loadingOverlay.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(loadingOverlay)
NSLayoutConstraint.activate([
Expand Down Expand Up @@ -517,6 +519,8 @@ class OpenHABWebViewController: OpenHABViewController {
webview.isInspectable = true
}

configureWebHistoryGestures(for: webview)

// Avoid safe-area content insets which can leave a small gap at the bottom on iPad until a reload.
webview.scrollView.contentInsetAdjustmentBehavior = .never
webview.scrollView.contentInset = .zero
Expand Down Expand Up @@ -548,6 +552,56 @@ class OpenHABWebViewController: OpenHABViewController {
}
}

extension OpenHABWebViewController: UIGestureRecognizerDelegate {
private func configureWebHistoryGestures(for webView: WKWebView) {
webView.allowsBackForwardNavigationGestures = false

guard webHistoryBackGesture?.view !== webView else {
return
}

if let webHistoryBackGesture {
webHistoryBackGesture.view?.removeGestureRecognizer(webHistoryBackGesture)
}

let backGesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleWebHistoryEdgePan(_:)))
backGesture.edges = .left
backGesture.delegate = self
webView.addGestureRecognizer(backGesture)
webHistoryBackGesture = backGesture
}

@objc private func handleWebHistoryEdgePan(_ gesture: UIScreenEdgePanGestureRecognizer) {
guard gesture.state == .ended else { return }

let translation = gesture.translation(in: webView)
let velocity = gesture.velocity(in: webView)
let hasIntent = abs(translation.x) > 35 || abs(velocity.x) > 250
guard hasIntent else { return }

guard gesture.edges == .left,
translation.x > 0 || velocity.x > 0 else { return }

navigateWebHistory(
direction: "back",
script: "if (window.history.length > 1) { window.history.back(); true; } else { false; }"
)
}

private func navigateWebHistory(direction: String, script: String) {
webView.evaluateJavaScript(script) { _, error in
if let error {
Logger.viewController.error("web history \(direction, privacy: .public) failed: \(error.localizedDescription, privacy: .public)")
}
}
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
gestureRecognizer === webHistoryBackGesture
}
}

extension OpenHABWebViewController: WKScriptMessageHandler {
@MainActor
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
Expand Down Expand Up @@ -838,21 +892,6 @@ extension OpenHABWebViewController: WKNavigationDelegate {
}
}

private extension URL {
var isNativeWebURL: Bool {
guard let scheme = scheme?.lowercased() else { return true }
return ["http", "https", "about", "blob", "data", "javascript"].contains(scheme)
}

/// RFC 6454 origin: scheme + host + port, with default ports (80/443) omitted.
var webOrigin: String? {
guard let scheme = scheme?.lowercased(), let host else { return nil }
let defaultPort = scheme == "https" ? 443 : scheme == "http" ? 80 : nil
let portSuffix = (port != nil && port != defaultPort) ? ":\(port!)" : ""
return "\(scheme)://\(host)\(portSuffix)"
}
}

extension OpenHABWebViewController: WKUIDelegate {
func webView(_ webView: WKWebView,
createWebViewWith configuration: WKWebViewConfiguration,
Expand All @@ -879,3 +918,18 @@ extension OpenHABWebViewController: WKUIDelegate {
}
// swiftlint:enable async_without_await
}

private extension URL {
var isNativeWebURL: Bool {
guard let scheme = scheme?.lowercased() else { return true }
return ["http", "https", "about", "blob", "data", "javascript"].contains(scheme)
}

/// RFC 6454 origin: scheme + host + port, with default ports (80/443) omitted.
var webOrigin: String? {
guard let scheme = scheme?.lowercased(), let host else { return nil }
let defaultPort = scheme == "https" ? 443 : scheme == "http" ? 80 : nil
let portSuffix = (port != nil && port != defaultPort) ? ":\(port!)" : ""
return "\(scheme)://\(host)\(portSuffix)"
}
}
1 change: 1 addition & 0 deletions openHAB/UI/SwiftUI/Rows/WebRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ struct WebRowView: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
let homeId = Preferences.shared.currentHomePreferences.id
let webView = WKWebView(frame: .zero, configuration: WebRowViewConfigurationFactory.make(homeId: homeId))
webView.allowsBackForwardNavigationGestures = true
webView.navigationDelegate = context.coordinator
return webView
}
Expand Down