Files
iOS/Sources/App/Frontend/WebView/WebViewController+Settings.swift
Bruno Pantaleão Gonçalves a21ebf003b Refactor WebViewController (#4383)
Split WebViewController functionality into multiple extension files
(Alerts, EmptyState, Gestures, Navigation, Onboarding, Settings,
StatusBar, URLLoading, WebViewSetup) and add FrontEndConnectionState
enum. Update Xcode project to include the new source files and adjust
several Database test file references and a local Swift package path
(Sources/SharedPush). Also add empty input/output path arrays to several
CocoaPods embed phases. This refactors web view features into modular
components and wires them into the project file.

<!-- 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 -->

## 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. -->
2026-02-23 11:57:56 +01:00

147 lines
5.6 KiB
Swift

import PromiseKit
import Shared
import UIKit
@preconcurrency import WebKit
// MARK: - Settings, Appearance & Pull-to-Refresh
extension WebViewController {
func styleUI() {
precondition(isViewLoaded && webView != nil)
let cachedColors = ThemeColors.cachedThemeColors(for: traitCollection)
view.backgroundColor = cachedColors[.primaryBackgroundColor]
webView?.backgroundColor = cachedColors[.primaryBackgroundColor]
webView?.scrollView.backgroundColor = cachedColors[.primaryBackgroundColor]
// Use the stored reference instead of searching by tag
if let statusBarView {
let backgroundColor = server.info.version < .canUseAppThemeForStatusBar
? cachedColors[.appHeaderBackgroundColor]
: cachedColors[.appThemeColor]
statusBarView.backgroundColor = backgroundColor
statusBarView.isOpaque = true
}
refreshControl.tintColor = cachedColors[.primaryColor]
let headerBackgroundIsLight = cachedColors[.appThemeColor].isLight
underlyingPreferredStatusBarStyle = headerBackgroundIsLight ? .darkContent : .lightContent
setNeedsStatusBarAppearanceUpdate()
}
func updateWebViewSettings(reason: WebViewSettingsUpdateReason) {
Current.Log.info("updating web view settings for \(reason)")
// iOS 14's `pageZoom` property is almost this, but not quite - it breaks the layout as well
// This is quasi-private API that has existed since pre-iOS 10, but the implementation
// changed in iOS 12 to be like the +/- zoom buttons in Safari, which scale content without
// resizing the scrolling viewport.
let viewScale = Current.settingsStore.pageZoom.viewScaleValue
Current.Log.info("setting view scale to \(viewScale)")
webView.setValue(viewScale, forKey: "viewScale")
if !Current.isCatalyst {
let zoomValue = Current.settingsStore.pinchToZoom ? "true" : "false"
webView.evaluateJavaScript("setOverrideZoomEnabled(\(zoomValue))", completionHandler: nil)
}
if reason == .settingChange {
setNeedsStatusBarAppearanceUpdate()
setNeedsUpdateOfHomeIndicatorAutoHidden()
updateEdgeToEdgeLayout()
}
}
@objc func updateWebViewSettingsForNotification() {
updateWebViewSettings(reason: .settingChange)
}
func updateEdgeToEdgeLayout() {
guard let statusBarView else { return }
// Edge-to-edge mode only applies to iOS (not Catalyst)
// Also use edge-to-edge behavior when fullScreen is enabled (status bar hidden)
let edgeToEdge = (Current.settingsStore.edgeToEdge || Current.settingsStore.fullScreen) && !Current.isCatalyst
// Deactivate the current constraint
webViewTopConstraint?.isActive = false
// Create the new constraint based on edge-to-edge setting
if edgeToEdge {
webViewTopConstraint = webView.topAnchor.constraint(equalTo: view.topAnchor)
statusBarView.isHidden = true
} else {
webViewTopConstraint = webView.topAnchor.constraint(equalTo: statusBarView.bottomAnchor)
statusBarView.isHidden = false
}
webViewTopConstraint?.isActive = true
// Force layout update
view.setNeedsLayout()
view.layoutIfNeeded()
// Refresh styling to ensure statusBarView has proper background color
styleUI()
// Animate the layout change
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
func setupPullToRefresh() {
if !Current.isCatalyst {
// refreshing is handled by menu/keyboard shortcuts
refreshControl.addTarget(self, action: #selector(pullToRefresh(_:)), for: .valueChanged)
webView.scrollView.addSubview(refreshControl)
webView.scrollView.bounces = true
}
}
@objc func pullToRefresh(_ sender: UIRefreshControl) {
let now = Current.date()
// Check if this is a consecutive pull-to-refresh within 10 seconds
if let lastTimestamp = lastPullToRefreshTimestamp,
now.timeIntervalSince(lastTimestamp) < 10 {
// Second pull-to-refresh within 10 seconds - reset frontend cache
Current.Log.info("Consecutive pull-to-refresh detected within 10 seconds, resetting frontend cache")
Current.impactFeedback.impactOccurred(style: .medium)
// Reset the cache
Current.websiteDataStoreHandler.cleanCache { [weak self] in
Current.Log.info("Frontend cache reset after consecutive pull-to-refresh")
self?.pullToRefreshActions()
}
// Set the timestamp to now after cache reset to ensure proper timing for next pull
// This prevents immediate re-triggering while still tracking for future pulls
lastPullToRefreshTimestamp = now
} else {
// First pull-to-refresh or outside the 10-second window
lastPullToRefreshTimestamp = now
pullToRefreshActions()
}
}
func pullToRefreshActions() {
refresh()
updateSensors()
}
@objc func updateSensors() {
// called via menu/keyboard shortcut too
firstly {
HomeAssistantAPI.manuallyUpdate(
applicationState: UIApplication.shared.applicationState,
type: .userRequested
)
}.catch { error in
Current.Log.error("Error when updating sensors from WKWebView reload: \(error)")
}
}
}