Skip to content
Merged
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
56 changes: 40 additions & 16 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
tableView.estimatedRowHeight = 74

// Estimate an initial value
landscapeMode = UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height
landscapeMode = ActionTabBarMetrics.isCompactHeight

addScenarioStepGestureRecognizers()

Expand Down Expand Up @@ -346,6 +346,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
let backgroundContainerView = UIView()
backgroundContainerView.backgroundColor = .systemBackground
let statusBarBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 0))
statusBarBackgroundView.autoresizingMask = .flexibleWidth
self.statusBarBackgroundView = statusBarBackgroundView
backgroundContainerView.addSubview(statusBarBackgroundView)
tableView.backgroundView = backgroundContainerView
Expand Down Expand Up @@ -379,7 +380,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
}
}

// Toggles the display mode based on the screen aspect ratio. Should not be updated outside of reloadData().
// Charts only, for compact height (an iPhone in landscape). Should not be updated outside of reloadData().
private var landscapeMode = false {
didSet {
setupPresetsStatusBar()
Expand All @@ -400,6 +401,12 @@ final class StatusTableViewController: LoopChartsTableViewController {
return !landscapeMode
}

// With a vertical status bar there is no top safe area inset, so keep the first row clear of the display corners.
private var cornerClearanceTopInset: CGFloat {
guard #available(iOS 26.0, *), let window = view.window else { return 0 }
return max(0, window.edgeInsets(for: .safeArea(cornerAdaptation: .vertical)).top - window.safeAreaInsets.top)
}

private var shouldShowStatus: Bool {
return !landscapeMode && statusRowMode.hasRow
}
Expand Down Expand Up @@ -656,7 +663,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
let statusRowMode = self.determineStatusRowMode()

updateBannerAndHUDandStatusRows(statusRowMode: statusRowMode, newSize: currentContext.newSize, animated: animated)
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: ActionTabBarMetrics.tableContentInset, right: 0)
tableView.contentInset = UIEdgeInsets(top: shouldShowBannerWarning ? 0 : cornerClearanceTopInset, left: 0, bottom: ActionTabBarMetrics.tableContentInset, right: 0)

redrawCharts()

Expand Down Expand Up @@ -822,8 +829,8 @@ final class StatusTableViewController: LoopChartsTableViewController {
self.presetsRowMode = determinePresetsRowMode()
self.statusRowMode = statusRowMode

if let newSize = newSize {
landscapeMode = newSize.width > newSize.height
if newSize != nil {
landscapeMode = traitCollection.verticalSizeClass == .compact
}

let presetsIsVisible = self.shouldShowPresets
Expand Down Expand Up @@ -968,7 +975,8 @@ final class StatusTableViewController: LoopChartsTableViewController {
case .alertWarning:
let cell = UITableViewCell()
let alert = AlertPermissionsChecker.UnsafeNotificationPermissionAlert(permissions: alertPermissionsChecker.notificationCenterSettings)

let fullBleedTopInset: CGFloat? = cornerClearanceTopInset > 0 ? cornerClearanceTopInset : nil

cell.contentConfiguration = UIHostingConfiguration {
if alertPermissionsChecker.showWarning {
if let alert {
Expand All @@ -988,9 +996,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
.font(.headline)
}
.foregroundStyle(Color.white)
.padding(8)
.background(Color.critical.cornerRadius(10))
.padding([.top, .horizontal], 8)
.modifier(WarningBannerStyle(fullBleedTopInset: fullBleedTopInset))
}
} else {
HStack {
Expand All @@ -1011,15 +1017,13 @@ final class StatusTableViewController: LoopChartsTableViewController {
.font(.title)
}
.foregroundStyle(Color.white)
.padding(8)
.background(Color.critical.cornerRadius(10))
.padding([.top, .horizontal], 8)
.modifier(WarningBannerStyle(fullBleedTopInset: fullBleedTopInset))
}
}
.margins(.all, 0)
cell.backgroundColor = .secondarySystemBackground

cell.backgroundColor = fullBleedTopInset == nil ? .secondarySystemBackground : .critical

return cell
case .hud:
let cell = tableView.dequeueReusableCell(withIdentifier: HUDViewTableViewCell.className, for: indexPath) as! HUDViewTableViewCell
Expand Down Expand Up @@ -1245,7 +1249,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
case .charts:
// Compute the height of the HUD, defaulting to 70
let hudHeight = ceil(hudView?.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height ?? 74)
var availableSize = max(tableView.bounds.width, tableView.bounds.height)
var availableSize = landscapeMode ? max(tableView.bounds.width, tableView.bounds.height) : tableView.bounds.height
availableSize -= (tableView.safeAreaInsets.top + tableView.safeAreaInsets.bottom + tableView.contentInset.bottom + hudHeight)

switch ChartRow(rawValue: indexPath.row)! {
Expand Down Expand Up @@ -2322,3 +2326,23 @@ extension StatusTableViewController {
updateStatusBar()
}
}

// Without a top status bar, the banner runs to the top edge and the display corners round it.
private struct WarningBannerStyle: ViewModifier {
let fullBleedTopInset: CGFloat?

func body(content: Content) -> some View {
if let fullBleedTopInset {
content
.padding(.top, fullBleedTopInset)
.padding(.horizontal, 24)
.padding(.bottom, 12)
.background(Color.critical)
} else {
content
.padding(8)
.background(Color.critical.cornerRadius(10))
.padding([.top, .horizontal], 8)
}
}
}
26 changes: 8 additions & 18 deletions Loop/Views/StatusTableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,16 +309,16 @@ enum ActionTabBarMetrics {
.safeAreaInsets.bottom ?? 0
}

static var interfaceOrientation: UIInterfaceOrientation {
static var isCompactHeight: Bool {
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
let scene = scenes.first { $0.windows.contains { $0.isKeyWindow } }
?? scenes.first { $0.activationState == .foregroundActive }
?? scenes.first
return scene?.interfaceOrientation ?? .portrait
return scene?.traitCollection.verticalSizeClass == .compact
}

static var tableContentInset: CGFloat {
guard !interfaceOrientation.isLandscape else { return 0 }
guard !isCompactHeight else { return 0 }
if #available(iOS 26.0, *), bottomSafeAreaInset == 0 {
return barHeight + 40
}
Expand Down Expand Up @@ -357,35 +357,25 @@ struct LegacyTabBarBackground: ViewModifier {

struct ActionTabView<Content: View>: View {

@State private var orientation: UIInterfaceOrientation
@Environment(\.verticalSizeClass) private var verticalSizeClass

private let content: Content
private let tabs: [ActionTab]

init(
@ViewBuilder content: @escaping () -> Content,
@ActionTabBuilder tabs: @escaping () -> [ActionTab],
) {
self.content = content()
self.tabs = tabs()
self.orientation = ActionTabBarMetrics.interfaceOrientation
}

var body: some View {
let isVisible = verticalSizeClass != .compact
content
.safeAreaInset(edge: .bottom, spacing: 0) {
ActionTabBar(items: tabs, isHidden: !orientation.isPortrait)
.modifier(LegacyTabBarBackground(isVisible: orientation.isPortrait))
}
.onAppear {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
orientation = ActionTabBarMetrics.interfaceOrientation
}
.onDisappear {
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
orientation = ActionTabBarMetrics.interfaceOrientation
ActionTabBar(items: tabs, isHidden: !isVisible)
.modifier(LegacyTabBarBackground(isVisible: isVisible))
}
}
}