mirror of
https://github.com/home-assistant/iOS.git
synced 2026-06-25 17:27:04 -05:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> This PR is a massive refactor of how the app handles UI presentation and navigation, goin from the UIKit based apps style to SwiftUI. ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
104 lines
3.7 KiB
Swift
104 lines
3.7 KiB
Swift
import PromiseKit
|
|
import Shared
|
|
import SwiftUI
|
|
import UIKit
|
|
@preconcurrency import WebKit
|
|
|
|
// MARK: - Public Navigation API
|
|
|
|
extension WebViewController {
|
|
/// avoidUnnecessaryReload Avoids reloading when the URL is the same as the current one
|
|
func open(inline url: URL, avoidUnnecessaryReload: Bool = false) {
|
|
loadViewIfNeeded()
|
|
|
|
// these paths do not show frontend pages, and so we don't want to display them in our webview
|
|
// otherwise the user will get stuck. e.g. /api is loaded by frigate to show video clips and images
|
|
let ignoredPaths = [
|
|
"/api",
|
|
"/static",
|
|
"/hacsfiles",
|
|
"/local",
|
|
]
|
|
|
|
if ignoredPaths.allSatisfy({ !url.path.hasPrefix($0) }) {
|
|
if avoidUnnecessaryReload, webView.url?.isEqualIgnoringQueryParams(to: url) == true {
|
|
Current.Log
|
|
.info(
|
|
"Not reloading WebView when open(inline) was requested, URL is the same as current and avoidUnnecessaryReload is true"
|
|
)
|
|
return
|
|
}
|
|
load(request: URLRequest(url: url))
|
|
} else {
|
|
openURLInBrowser(url, self)
|
|
}
|
|
}
|
|
|
|
/// Used for OpenPage intent
|
|
func openPanel(_ url: URL) {
|
|
loadViewIfNeeded()
|
|
|
|
guard url.queryItems?[AppConstants.QueryItems.openMoreInfoDialog.rawValue] == nil || server.info
|
|
.version >= .canNavigateMoreInfoDialogThroughFrontend else {
|
|
load(request: URLRequest(url: url))
|
|
Current.Log.verbose("Opening more-info dialog for URL: \(url)")
|
|
return
|
|
}
|
|
|
|
let urlPathIncludingQueryParams = {
|
|
// If the URL has query parameters, we need to include them in the path to ensure proper navigation
|
|
if let query = url.query, !query.isEmpty {
|
|
return "\(url.path)?\(query)"
|
|
}
|
|
return url.path
|
|
}()
|
|
|
|
navigate(path: urlPathIncludingQueryParams) { [weak self] success in
|
|
if !success {
|
|
Current.Log.warning("Failed to navigate through frontend for URL: \(url)")
|
|
// Fallback to loading the URL directly if navigation fails
|
|
self?.load(request: URLRequest(url: url))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Uses external bus to navigate through frontend instead of loading the page from scratch using the web view
|
|
/// Returns true if the navigation was successful
|
|
private func navigate(path: String, completion: @escaping (Bool) -> Void) {
|
|
guard server.info.version >= .canNavigateThroughFrontend else {
|
|
Current.Log.warning("Cannot navigate through frontend, core version is too low")
|
|
completion(false)
|
|
return
|
|
}
|
|
Current.Log.verbose("Requesting navigation using external bus to path: \(path)")
|
|
webViewExternalMessageHandler.sendExternalBus(message: .init(
|
|
command: WebViewExternalBusOutgoingMessage.navigate.rawValue,
|
|
payload: [
|
|
"path": path,
|
|
]
|
|
)).pipe { result in
|
|
switch result {
|
|
case .fulfilled:
|
|
completion(true)
|
|
case .rejected:
|
|
completion(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Manual reload does not take care of internal/external URL changes, prefer using `refresh()`
|
|
func reload() {
|
|
Current.Log.verbose("Reload webView requested")
|
|
webView.reload()
|
|
}
|
|
|
|
func showSettingsViewController() {
|
|
getLatestConfig()
|
|
Current.sceneManager.appCoordinator.done { $0.showSettings() }
|
|
}
|
|
|
|
func getLatestConfig() {
|
|
_ = Current.api(for: server)?.getConfig()
|
|
}
|
|
}
|