Skip to content

Commit daa4a05

Browse files
authored
Add experimental options to display iOS UI going edge to edge (#4279)
1 parent 6b2f83e commit daa4a05

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed

Sources/App/Settings/General/GeneralSettingsView.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct GeneralSettingsView: View {
4646
fullScreen
4747
refreshAfterInactive
4848
}
49+
edgeToEdge
4950
}
5051
.id(redrawHelper)
5152
}
@@ -173,6 +174,28 @@ struct GeneralSettingsView: View {
173174
}
174175
}
175176

177+
@ViewBuilder
178+
private var edgeToEdge: some View {
179+
Section {
180+
if !Current.isCatalyst {
181+
Toggle(isOn: .init(get: {
182+
Current.settingsStore.edgeToEdge
183+
}, set: { newValue in
184+
Current.settingsStore.edgeToEdge = newValue
185+
redrawView()
186+
})) {
187+
Text("Edge to edge display")
188+
}
189+
}
190+
} header: {
191+
Text("Experimental")
192+
} footer: {
193+
Text(
194+
"Display Home Assistant UI from edge to edge on devices that support it. This is an experimental feature which can be removed at any time and also may cause layout issues."
195+
)
196+
}
197+
}
198+
176199
@ViewBuilder
177200
private var refreshAfterInactive: some View {
178201
Toggle(isOn: .init(get: {

Sources/App/WebView/WebViewController.swift

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
3333
private var emptyStateView: UIView?
3434
private let emptyStateTransitionDuration: TimeInterval = 0.3
3535

36+
private var statusBarView: UIView?
37+
private var webViewTopConstraint: NSLayoutConstraint?
38+
3639
private var initialURL: URL?
3740
private var statusBarButtonsStack: UIStackView?
3841
private var lastNavigationWasServerError = false
@@ -454,9 +457,22 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
454457
webView.translatesAutoresizingMaskIntoConstraints = false
455458
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
456459
webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
457-
webView.topAnchor.constraint(equalTo: statusBarView.bottomAnchor).isActive = true
458460
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
459461
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
462+
463+
// Create the top constraint based on edge-to-edge setting
464+
// On iOS (not Catalyst), edge-to-edge mode pins the webview to the top of the view
465+
// On Catalyst, we always show the status bar buttons, so we pin to statusBarView
466+
// Also use edge-to-edge behavior when fullScreen is enabled (status bar hidden)
467+
let edgeToEdge = (Current.settingsStore.edgeToEdge || Current.settingsStore.fullScreen) && !Current.isCatalyst
468+
if edgeToEdge {
469+
webViewTopConstraint = webView.topAnchor.constraint(equalTo: view.topAnchor)
470+
statusBarView.isHidden = true
471+
} else {
472+
webViewTopConstraint = webView.topAnchor.constraint(equalTo: statusBarView.bottomAnchor)
473+
statusBarView.isHidden = false
474+
}
475+
webViewTopConstraint?.isActive = true
460476
}
461477

462478
private func setupURLObserver() {
@@ -516,6 +532,7 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
516532
private func setupStatusBarView() -> UIView {
517533
let statusBarView = UIView()
518534
statusBarView.tag = 111
535+
self.statusBarView = statusBarView
519536

520537
view.addSubview(statusBarView)
521538
statusBarView.translatesAutoresizingMaskIntoConstraints = false
@@ -530,7 +547,6 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
530547
if Current.isCatalyst {
531548
setupStatusBarButtons(in: statusBarView)
532549
}
533-
534550
return statusBarView
535551
}
536552

@@ -680,15 +696,17 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
680696

681697
let cachedColors = ThemeColors.cachedThemeColors(for: traitCollection)
682698

699+
view.backgroundColor = cachedColors[.primaryBackgroundColor]
683700
webView?.backgroundColor = cachedColors[.primaryBackgroundColor]
684701
webView?.scrollView.backgroundColor = cachedColors[.primaryBackgroundColor]
685702

686-
if let statusBarView = view.viewWithTag(111) {
687-
if server.info.version < .canUseAppThemeForStatusBar {
688-
statusBarView.backgroundColor = cachedColors[.appHeaderBackgroundColor]
689-
} else {
690-
statusBarView.backgroundColor = cachedColors[.appThemeColor]
691-
}
703+
// Use the stored reference instead of searching by tag
704+
if let statusBarView {
705+
let backgroundColor = server.info.version < .canUseAppThemeForStatusBar
706+
? cachedColors[.appHeaderBackgroundColor]
707+
: cachedColors[.appThemeColor]
708+
statusBarView.backgroundColor = backgroundColor
709+
statusBarView.isOpaque = true
692710
}
693711

694712
refreshControl.tintColor = cachedColors[.primaryColor]
@@ -744,6 +762,40 @@ final class WebViewController: UIViewController, WKNavigationDelegate, WKUIDeleg
744762
if reason == .settingChange {
745763
setNeedsStatusBarAppearanceUpdate()
746764
setNeedsUpdateOfHomeIndicatorAutoHidden()
765+
updateEdgeToEdgeLayout()
766+
}
767+
}
768+
769+
private func updateEdgeToEdgeLayout() {
770+
guard let statusBarView else { return }
771+
772+
// Edge-to-edge mode only applies to iOS (not Catalyst)
773+
// Also use edge-to-edge behavior when fullScreen is enabled (status bar hidden)
774+
let edgeToEdge = (Current.settingsStore.edgeToEdge || Current.settingsStore.fullScreen) && !Current.isCatalyst
775+
776+
// Deactivate the current constraint
777+
webViewTopConstraint?.isActive = false
778+
779+
// Create the new constraint based on edge-to-edge setting
780+
if edgeToEdge {
781+
webViewTopConstraint = webView.topAnchor.constraint(equalTo: view.topAnchor)
782+
statusBarView.isHidden = true
783+
} else {
784+
webViewTopConstraint = webView.topAnchor.constraint(equalTo: statusBarView.bottomAnchor)
785+
statusBarView.isHidden = false
786+
}
787+
webViewTopConstraint?.isActive = true
788+
789+
// Force layout update
790+
view.setNeedsLayout()
791+
view.layoutIfNeeded()
792+
793+
// Refresh styling to ensure statusBarView has proper background color
794+
styleUI()
795+
796+
// Animate the layout change
797+
UIView.animate(withDuration: 0.25) {
798+
self.view.layoutIfNeeded()
747799
}
748800
}
749801

Sources/Shared/Settings/SettingsStore.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,16 @@ public class SettingsStore {
188188
}
189189
}
190190

191+
public var edgeToEdge: Bool {
192+
get {
193+
prefs.bool(forKey: "edgeToEdge_experimental")
194+
}
195+
set {
196+
prefs.set(newValue, forKey: "edgeToEdge_experimental")
197+
NotificationCenter.default.post(name: Self.webViewRelatedSettingDidChange, object: nil)
198+
}
199+
}
200+
191201
public var refreshWebViewAfterInactive: Bool {
192202
get {
193203
if let value = prefs.object(forKey: "refreshWebViewAfterInactive") as? NSNumber {

0 commit comments

Comments
 (0)