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
66 changes: 58 additions & 8 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
tableView.estimatedRowHeight = 74

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

addScenarioStepGestureRecognizers()

Expand Down Expand Up @@ -210,6 +210,11 @@ final class StatusTableViewController: LoopChartsTableViewController {
}
}

override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
updateTopContentInset()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
refreshContext.update(with: .size(size))

Expand Down Expand Up @@ -350,9 +355,23 @@ 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

private 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?.traitCollection.verticalSizeClass == .compact
}

// 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 lastLoopError: Error?

private var reloading = false
Expand All @@ -363,6 +382,16 @@ final class StatusTableViewController: LoopChartsTableViewController {
return !landscapeMode
}

private func updateTopContentInset() {
let top = shouldShowBannerWarning ? 0 : cornerClearanceTopInset
guard tableView.contentInset.top != top else { return }
let wasAtTop = tableView.contentOffset.y <= -tableView.adjustedContentInset.top
tableView.contentInset.top = top
if wasAtTop {
tableView.contentOffset.y = -tableView.adjustedContentInset.top
}
}

private var shouldShowStatus: Bool {
return !landscapeMode && statusRowMode.hasRow
}
Expand Down Expand Up @@ -792,6 +821,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
} else {
tableView.reloadRows(at: [IndexPath(row: 0, section: Section.alertWarning.rawValue)], with: .none)
}
updateTopContentInset()
}

private func updateBannerAndHUDandStatusRows(statusRowMode: StatusRowMode, newSize: CGSize?, animated: Bool) {
Expand All @@ -802,8 +832,8 @@ final class StatusTableViewController: LoopChartsTableViewController {

self.statusRowMode = statusRowMode

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

let hudIsVisible = self.shouldShowHUD
Expand Down Expand Up @@ -923,6 +953,8 @@ final class StatusTableViewController: LoopChartsTableViewController {
}

private class AlertPermissionsDisabledWarningCell: UITableViewCell {
var fullBleedTopInset: CGFloat?

override func updateConfiguration(using state: UICellConfigurationState) {
super.updateConfiguration(using: state)

Expand All @@ -948,18 +980,22 @@ final class StatusTableViewController: LoopChartsTableViewController {
backgroundConfiguration = backgroundConfig
backgroundConfiguration?.backgroundInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 5, trailing: 10)
backgroundConfiguration?.cornerRadius = 10
if fullBleedTopInset != nil {
applyFullBleedBackground()
}

let disclosureIndicator = UIImage(systemName: "chevron.right")?.withTintColor(.white)
let imageView = UIImageView(image: disclosureIndicator)
imageView.tintColor = .white
accessoryView = imageView

contentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 6, leading: 0, bottom: 13, trailing: 0)
contentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: (fullBleedTopInset ?? 0) + 6, leading: 0, bottom: 13, trailing: 0)
}
}

private class MuteAlertsWarningCell: UITableViewCell {
var formattedAlertMuteEndTime: String = NSLocalizedString("Unknown", comment: "label for when the alert mute end time is unknown")
var fullBleedTopInset: CGFloat?

fileprivate class GradientView: UIView {
override static var layerClass: AnyClass { CAGradientLayer.self }
Expand Down Expand Up @@ -992,26 +1028,32 @@ final class StatusTableViewController: LoopChartsTableViewController {
backgroundConfiguration = backgroundConfig
backgroundConfiguration?.backgroundInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 5, trailing: 5)
backgroundConfiguration?.cornerRadius = 10
if fullBleedTopInset != nil {
applyFullBleedBackground()
}

let unmuteIndicator = UIImage(systemName: "stop.circle")?.withTintColor(.white)
let imageView = UIImageView(image: unmuteIndicator)
imageView.tintColor = .white
imageView.frame.size = CGSize(width: 30, height: 30)
accessoryView = imageView

contentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 6, leading: 0, bottom: 13, trailing: 0)
contentView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: (fullBleedTopInset ?? 0) + 6, leading: 0, bottom: 13, trailing: 0)
}
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch Section(rawValue: indexPath.section)! {
case .alertWarning:
let fullBleedTopInset: CGFloat? = cornerClearanceTopInset > 0 ? cornerClearanceTopInset : nil
if alertPermissionsChecker.showWarning {
let cell = tableView.dequeueReusableCell(withIdentifier: AlertPermissionsDisabledWarningCell.className, for: indexPath) as! AlertPermissionsDisabledWarningCell
cell.fullBleedTopInset = fullBleedTopInset
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: MuteAlertsWarningCell.className, for: indexPath) as! MuteAlertsWarningCell
cell.formattedAlertMuteEndTime = alertMuter.formattedEndTime
cell.fullBleedTopInset = fullBleedTopInset
cell.selectionStyle = .none
return cell
}
Expand Down Expand Up @@ -1219,8 +1261,8 @@ 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)
availableSize -= (tableView.safeAreaInsets.top + tableView.safeAreaInsets.bottom + hudHeight)
var availableSize = landscapeMode ? max(tableView.bounds.width, tableView.bounds.height) : tableView.bounds.height
availableSize -= (tableView.safeAreaInsets.top + tableView.safeAreaInsets.bottom + tableView.contentInset.top + hudHeight)

switch ChartRow(rawValue: indexPath.row)! {
case .glucose:
Expand Down Expand Up @@ -2258,3 +2300,11 @@ extension StatusTableViewController: ServicesViewModelDelegate {
show(settingsViewController, sender: self)
}
}

private extension UITableViewCell {
// Without a top status bar, the banner runs to the top edge and the display corners round it.
func applyFullBleedBackground() {
backgroundConfiguration?.backgroundInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 5, trailing: 0)
backgroundConfiguration?.cornerRadius = 0
}
}
4 changes: 4 additions & 0 deletions Loop/Views/HUDViewTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ class HUDViewTableViewCell: UITableViewCell {

@IBOutlet var hudView: StatusBarHUDView!

override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = .secondarySystemBackground
}
}
8 changes: 6 additions & 2 deletions LoopUI/Views/StatusBarHUDView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ public class StatusBarHUDView: UIView, NibLoadable {
containerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerView)

// Use AutoLayout to have the stack view fill its entire container.
// Fill the container, up to a phone-like width on wide displays such as iPhone Duo's inner display.
let fillWidth = containerView.widthAnchor.constraint(equalTo: widthAnchor)
fillWidth.priority = .defaultHigh
NSLayoutConstraint.activate([
containerView.centerXAnchor.constraint(equalTo: centerXAnchor),
containerView.centerYAnchor.constraint(equalTo: centerYAnchor),
containerView.widthAnchor.constraint(equalTo: widthAnchor),
fillWidth,
containerView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
containerView.widthAnchor.constraint(lessThanOrEqualToConstant: 500),
containerView.heightAnchor.constraint(equalTo: heightAnchor),
])

Expand Down