mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(ios): use dynamic settings bottom margin
This commit is contained in:
@@ -59,6 +59,11 @@ struct SettingsProTab: View {
|
||||
@State var notificationActionText = "Request Access"
|
||||
@State var diagnosticsLastRunText = "Not run"
|
||||
@State var diagnosticsIssueCount: Int?
|
||||
@State var bottomOverlayInset: CGFloat = 0
|
||||
|
||||
var bottomScrollMargin: CGFloat {
|
||||
max(0, self.bottomOverlayInset - SettingsLayout.rowHeight - SettingsLayout.bottomContentPadding)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
@@ -71,9 +76,13 @@ struct SettingsProTab: View {
|
||||
self.gatewaySection
|
||||
self.settingsListSection
|
||||
}
|
||||
.padding(.vertical, 18)
|
||||
.padding(.top, 18)
|
||||
.padding(.bottom, SettingsLayout.bottomContentPadding)
|
||||
}
|
||||
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
|
||||
.contentMargins(.bottom, self.bottomScrollMargin, for: .scrollContent)
|
||||
SettingsBottomOverlayInsetReader(inset: self.$bottomOverlayInset)
|
||||
.frame(width: 0, height: 0)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.navigationDestination(for: SettingsRoute.self) { route in
|
||||
|
||||
@@ -203,9 +203,13 @@ extension SettingsProTab {
|
||||
self.aboutDestination
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 18)
|
||||
.padding(.top, 18)
|
||||
.padding(.bottom, SettingsLayout.bottomContentPadding)
|
||||
}
|
||||
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
|
||||
.contentMargins(.bottom, self.bottomScrollMargin, for: .scrollContent)
|
||||
SettingsBottomOverlayInsetReader(inset: self.$bottomOverlayInset)
|
||||
.frame(width: 0, height: 0)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
.navigationTitle(self.title(for: route))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Darwin
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
enum SettingsRoute: Hashable {
|
||||
case gateway
|
||||
@@ -14,6 +15,110 @@ enum SettingsRoute: Hashable {
|
||||
enum SettingsLayout {
|
||||
static let cardRadius: CGFloat = 12
|
||||
static let rowHeight: CGFloat = 58
|
||||
static let bottomContentPadding: CGFloat = 12
|
||||
}
|
||||
|
||||
struct SettingsBottomOverlayInsetReader: UIViewRepresentable {
|
||||
@Binding var inset: CGFloat
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(inset: self.$inset)
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> SettingsBottomOverlayInsetProbeView {
|
||||
let view = SettingsBottomOverlayInsetProbeView()
|
||||
view.onInsetChange = { value in
|
||||
context.coordinator.updateInset(value)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: SettingsBottomOverlayInsetProbeView, context: Context) {
|
||||
context.coordinator.inset = self.$inset
|
||||
uiView.onInsetChange = { value in
|
||||
context.coordinator.updateInset(value)
|
||||
}
|
||||
uiView.updateInset()
|
||||
}
|
||||
|
||||
final class Coordinator {
|
||||
var inset: Binding<CGFloat>
|
||||
|
||||
init(inset: Binding<CGFloat>) {
|
||||
self.inset = inset
|
||||
}
|
||||
|
||||
func updateInset(_ value: CGFloat) {
|
||||
let rounded = max(0, ceil(value))
|
||||
guard abs(self.inset.wrappedValue - rounded) > 0.5 else { return }
|
||||
self.inset.wrappedValue = rounded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class SettingsBottomOverlayInsetProbeView: UIView {
|
||||
var onInsetChange: ((CGFloat) -> Void)?
|
||||
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
self.updateInset()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.updateInset()
|
||||
}
|
||||
|
||||
func updateInset() {
|
||||
let value = self.visibleTabBarHeight()
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onInsetChange?(value)
|
||||
}
|
||||
}
|
||||
|
||||
private func visibleTabBarHeight() -> CGFloat {
|
||||
let tabBarController = self.nearestViewController()?.tabBarController
|
||||
?? self.findTabBarController(in: self.window?.rootViewController)
|
||||
guard let tabBar = tabBarController?.tabBar,
|
||||
!tabBar.isHidden,
|
||||
tabBar.alpha > 0.01,
|
||||
tabBar.window != nil,
|
||||
self.window != nil
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let tabFrame = tabBar.convert(tabBar.bounds, to: nil)
|
||||
guard tabFrame.height.isFinite else { return 0 }
|
||||
return max(0, tabFrame.height)
|
||||
}
|
||||
|
||||
private func nearestViewController() -> UIViewController? {
|
||||
var responder: UIResponder? = self
|
||||
while let current = responder {
|
||||
if let viewController = current as? UIViewController {
|
||||
return viewController
|
||||
}
|
||||
responder = current.next
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func findTabBarController(in viewController: UIViewController?) -> UITabBarController? {
|
||||
guard let viewController else { return nil }
|
||||
if let tabBarController = viewController as? UITabBarController {
|
||||
return tabBarController
|
||||
}
|
||||
if let tabBarController = self.findTabBarController(in: viewController.presentedViewController) {
|
||||
return tabBarController
|
||||
}
|
||||
for child in viewController.children {
|
||||
if let tabBarController = self.findTabBarController(in: child) {
|
||||
return tabBarController
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
enum SettingsDiagnosticIssue: String, Equatable, CaseIterable {
|
||||
|
||||
Reference in New Issue
Block a user