Skip to content
Open
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
14 changes: 12 additions & 2 deletions Loop/Extensions/UIAlertController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ extension UIAlertController {
}
))
}

addCancelAction()
}

/// Initializes an action sheet-styled controller for selecting a CGMManager
Expand All @@ -112,6 +114,8 @@ extension UIAlertController {
}
))
}

addCancelAction()
}

internal convenience init(deleteCGMManagerHandler handler: @escaping (_ isDeleted: Bool) -> Void) {
Expand Down Expand Up @@ -141,10 +145,12 @@ extension UIAlertController {
/// - selectionHandler: A closure to execute when a service is selected.
/// - identifier: The identifier of the selected service.
internal convenience init(availableServices: [ServiceDescriptor], selectionHandler: @escaping (_ identifier: String) -> Void) {
let preferredStyle: UIAlertController.Style = .alert

self.init(
title: NSLocalizedString("Add Service", comment: "Action sheet title selecting service"),
message: nil,
preferredStyle: .actionSheet
preferredStyle: preferredStyle
)

for availableService in availableServices {
Expand All @@ -156,11 +162,15 @@ extension UIAlertController {
}
))
}

if #available(iOS 26.0, *) {
addCancelAction()
}
}

internal func addCancelAction(handler: ((UIAlertAction) -> Void)? = nil) {
let cancel = NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet")
addAction(UIAlertAction(title: cancel, style: .cancel, handler: handler))
addAction(UIAlertAction(title: cancel, style: .destructive, handler: handler))
}
}

Expand Down
2 changes: 0 additions & 2 deletions Loop/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@
<string>remote-notification</string>
<string>audio</string>
</array>
<key>UIDesignRequiresCompatibility</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
2 changes: 1 addition & 1 deletion Loop/Managers/AlertPermissionsChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ extension AlertPermissionsChecker {
message: Self.unsafeNotificationPermissionsAlertContent.body,
preferredStyle: .alert)
let titleImageAttachment = NSTextAttachment()
titleImageAttachment.image = UIImage(systemName: "exclamationmark.triangle.fill")?.withTintColor(.critical)
titleImageAttachment.image = UIImage(systemName: "exclamationmark.triangle.fill")?.withTintColor(.critical, renderingMode: .alwaysOriginal)
titleImageAttachment.bounds = CGRect(x: titleImageAttachment.bounds.origin.x, y: -10, width: 40, height: 35)
let titleWithImage = NSMutableAttributedString(attachment: titleImageAttachment)
titleWithImage.append(NSMutableAttributedString(string: "\n\n", attributes: [.font: UIFont.systemFont(ofSize: 8)]))
Expand Down
153 changes: 102 additions & 51 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,41 +273,76 @@ final class StatusTableViewController: LoopChartsTableViewController {
deviceManager.pumpManagerHUDProvider?.visible = active && onscreen
}

private lazy var carbEntryButton = makeToolbarButton(imageNamed: "carbs", tintColor: .carbTintColor, action: #selector(userTappedAddCarbs))
private lazy var bolusButton = makeToolbarButton(imageNamed: "bolus", tintColor: .insulinTintColor, action: #selector(presentBolusScreen))
private lazy var settingsButton = makeToolbarButton(imageNamed: "settings", tintColor: .secondaryLabel, action: #selector(onSettingsTapped))

private lazy var workoutButton: UIButton = {
let button = UIButton(type: .system)
button.tintColor = .glucoseTintColor
button.addTarget(self, action: #selector(toggleWorkoutMode(_:)), for: .touchUpInside)
button.constrainToToolbarIconSize()
return button
}()

private func makeToolbarButton(imageNamed name: String, tintColor: UIColor, action: Selector) -> UIButton {
let button = UIButton(type: .system)
button.setImage(UIImage(named: name), for: .normal)
button.tintColor = tintColor
button.addTarget(self, action: action, for: .touchUpInside)
button.constrainToToolbarIconSize()
return button
}

private func setupToolbarItems() {
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let carbs = UIBarButtonItem(image: UIImage(named: "carbs"), style: .plain, target: self, action: #selector(userTappedAddCarbs))
let bolus = UIBarButtonItem(image: UIImage(named: "bolus"), style: .plain, target: self, action: #selector(presentBolusScreen))
let settings = UIBarButtonItem(image: UIImage(named: "settings"), style: .plain, target: self, action: #selector(onSettingsTapped))

let carbs = UIBarButtonItem(customView: carbEntryButton)
let bolus = UIBarButtonItem(customView: bolusButton)
let settings = UIBarButtonItem(customView: settingsButton)

carbs.title = "Add Meal"

let preMeal = createPreMealButtonItem(selected: false, isEnabled: true)
let workout = createWorkoutButtonItem(selected: false, isEnabled: true)
toolbarItems = [
carbs,
space,
preMeal,
space,
bolus,
space,
workout,
space,
settings
]
}
updateWorkoutButton(selected: false, isEnabled: true)
let workout = UIBarButtonItem(customView: workoutButton)

func flexibleSpace() -> UIBarButtonItem {
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
}

if #available(iOS 26, *) {
toolbarItems = [carbs, preMeal, bolus, workout, settings]
} else {
toolbarItems = [
carbs,
flexibleSpace(),
preMeal,
flexibleSpace(),
bolus,
flexibleSpace(),
workout,
flexibleSpace(),
settings
]
}
}

private func updateToolbarItems() {
let isPumpOnboarded = onboardingManager.isComplete || deviceManager.pumpManager?.isOnboarded == true

toolbarItems![0].accessibilityLabel = NSLocalizedString("Add Meal", comment: "The label of the carb entry button")
toolbarItems![0].isEnabled = isPumpOnboarded
toolbarItems![0].tintColor = UIColor.carbTintColor
toolbarItems![4].accessibilityLabel = NSLocalizedString("Bolus", comment: "The label of the bolus entry button")
toolbarItems![4].isEnabled = isPumpOnboarded
toolbarItems![4].tintColor = UIColor.insulinTintColor
toolbarItems![8].accessibilityLabel = NSLocalizedString("Settings", comment: "The label of the settings button")
toolbarItems![8].tintColor = UIColor.secondaryLabel

toolbarItems![2] = createPreMealButtonItem(selected: preMealMode == true && preMealModeAllowed, isEnabled: preMealModeAllowed)
toolbarItems![6] = createWorkoutButtonItem(selected: workoutMode == true && workoutModeAllowed, isEnabled: workoutModeAllowed)
carbEntryButton.accessibilityLabel = NSLocalizedString("Add Meal", comment: "The label of the carb entry button")
carbEntryButton.isEnabled = isPumpOnboarded
bolusButton.accessibilityLabel = NSLocalizedString("Bolus", comment: "The label of the bolus entry button")
bolusButton.isEnabled = isPumpOnboarded
settingsButton.accessibilityLabel = NSLocalizedString("Settings", comment: "The label of the settings button")

let preMealIndex: Int
if #available(iOS 26, *) {
preMealIndex = 1
} else {
preMealIndex = 2
}
toolbarItems![preMealIndex] = createPreMealButtonItem(selected: preMealMode == true && preMealModeAllowed, isEnabled: preMealModeAllowed)
updateWorkoutButton(selected: workoutMode == true && workoutModeAllowed, isEnabled: workoutModeAllowed)
}

public var basalDeliveryState: PumpManagerStatus.BasalDeliveryState? = nil {
Expand Down Expand Up @@ -1378,7 +1413,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
let bolusEntryView = SimpleBolusView(viewModel: viewModel).environmentObject(deviceManager.displayGlucosePreference)
let hostingController = DismissibleHostingController(rootView: bolusEntryView, isModalInPresentation: false)
navigationWrapper = UINavigationController(rootViewController: hostingController)
hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: navigationWrapper, action: #selector(dismissWithAnimation))
hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Cancel", comment: ""), style: .plain, target: navigationWrapper, action: #selector(dismissWithAnimation))
present(navigationWrapper, animated: true)
} else {
let viewModel = CarbEntryViewModel(delegate: deviceManager)
Expand Down Expand Up @@ -1437,7 +1472,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
)

let navigationWrapper = UINavigationController(rootViewController: hostingController)
hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: navigationWrapper, action: #selector(dismissWithAnimation))
hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Cancel", comment: ""), style: .plain, target: navigationWrapper, action: #selector(dismissWithAnimation))
present(navigationWrapper, animated: true)
deviceManager.analyticsServicesManager.didDisplayBolusScreen()
}
Expand All @@ -1459,21 +1494,20 @@ final class StatusTableViewController: LoopChartsTableViewController {
return item
}

private func createWorkoutButtonItem(selected: Bool, isEnabled: Bool) -> UIBarButtonItem {
let item = UIBarButtonItem(image: UIImage.workoutImage(selected: selected), style: .plain, target: self, action: #selector(toggleWorkoutMode(_:)))
item.accessibilityLabel = NSLocalizedString("Workout Targets", comment: "The label of the workout mode toggle button")
private func updateWorkoutButton(selected: Bool, isEnabled: Bool) {
workoutButton.setImage(UIImage.workoutImage(selected: selected), for: .normal)
workoutButton.accessibilityLabel = NSLocalizedString("Workout Targets", comment: "The label of the workout mode toggle button")

if selected {
item.accessibilityTraits.insert(.selected)
item.accessibilityHint = NSLocalizedString("Disables", comment: "The action hint of the workout mode toggle button when enabled")
workoutButton.accessibilityTraits.insert(.selected)
workoutButton.accessibilityHint = NSLocalizedString("Disables", comment: "The action hint of the workout mode toggle button when enabled")
} else {
item.accessibilityHint = NSLocalizedString("Enables", comment: "The action hint of the workout mode toggle button when disabled")
workoutButton.accessibilityTraits.remove(.selected)
workoutButton.accessibilityHint = NSLocalizedString("Enables", comment: "The action hint of the workout mode toggle button when disabled")
}

item.tintColor = UIColor.glucoseTintColor
item.isEnabled = isEnabled

return item
workoutButton.isEnabled = isEnabled
workoutButton.sizeToFit()
}

@IBAction func premealButtonTapped(_ sender: UIBarButtonItem) {
Expand Down Expand Up @@ -1544,7 +1578,13 @@ final class StatusTableViewController: LoopChartsTableViewController {
}
} else {
if FeatureFlags.sensitivityOverridesEnabled {
performSegue(withIdentifier: OverrideSelectionViewController.className, sender: toolbarItems![6])
let overridesIndex: Int
if #available(iOS 26, *) {
overridesIndex = 3
} else {
overridesIndex = 6
}
performSegue(withIdentifier: OverrideSelectionViewController.className, sender: toolbarItems![overridesIndex])
} else {
presentWorkoutModeAlertController()
}
Expand Down Expand Up @@ -1781,15 +1821,15 @@ final class StatusTableViewController: LoopChartsTableViewController {

@objc private func pumpStatusTapped(_ sender: UIGestureRecognizer) {
if let pumpStatusView = sender.view as? PumpStatusHUDView {
executeHUDTapAction(deviceManager.didTapOnPumpStatus(pumpStatusView.pumpManagerProvidedHUD))
executeHUDTapAction(deviceManager.didTapOnPumpStatus(pumpStatusView.pumpManagerProvidedHUD), from: sender.view)
}
}

@objc private func cgmStatusTapped( _ sender: UIGestureRecognizer) {
executeHUDTapAction(deviceManager.didTapOnCGMStatus())
executeHUDTapAction(deviceManager.didTapOnCGMStatus(), from: sender.view)
}

private func executeHUDTapAction(_ action: HUDTapAction?) {
private func executeHUDTapAction(_ action: HUDTapAction?, from sourceView: UIView?) {
guard let action = action else {
return
}
Expand All @@ -1802,15 +1842,15 @@ final class StatusTableViewController: LoopChartsTableViewController {
case .openAppURL(let url):
UIApplication.shared.open(url)
case .setupNewCGM:
addNewCGMManager()
addNewCGMManager(from: sourceView)
case .setupNewPump:
addNewPumpManager()
addNewPumpManager(from: sourceView)
default:
return
}
}

private func addNewPumpManager() {
private func addNewPumpManager(from sourceView: UIView?) {
let availablePumpManagers = deviceManager.availablePumpManagers

switch availablePumpManagers.count {
Expand All @@ -1822,12 +1862,13 @@ final class StatusTableViewController: LoopChartsTableViewController {
let alert = UIAlertController(availablePumpManagers: availablePumpManagers) { [weak self] (identifier) in
self?.addPumpManager(withIdentifier: identifier)
}
alert.addCancelAction { _ in }
alert.popoverPresentationController?.sourceView = sourceView ?? view
alert.popoverPresentationController?.sourceRect = sourceView?.bounds ?? view.bounds
present(alert, animated: true, completion: nil)
}
}

private func addNewCGMManager() {
private func addNewCGMManager(from sourceView: UIView?) {
let availableCGMManagers = deviceManager.availableCGMManagers

switch availableCGMManagers.count {
Expand All @@ -1839,7 +1880,8 @@ final class StatusTableViewController: LoopChartsTableViewController {
let alert = UIAlertController(availableCGMManagers: availableCGMManagers) { [weak self] identifier in
self?.addCGMManager(withIdentifier: identifier)
}
alert.addCancelAction { _ in }
alert.popoverPresentationController?.sourceView = sourceView ?? view
alert.popoverPresentationController?.sourceRect = sourceView?.bounds ?? view.bounds
present(alert, animated: true, completion: nil)
}
}
Expand Down Expand Up @@ -2042,6 +2084,15 @@ final class StatusTableViewController: LoopChartsTableViewController {
}
}

private extension UIButton {
func constrainToToolbarIconSize() {
setContentHuggingPriority(.required, for: .horizontal)
setContentHuggingPriority(.required, for: .vertical)
setContentCompressionResistancePriority(.required, for: .horizontal)
setContentCompressionResistancePriority(.required, for: .vertical)
}
}

extension UIAlertController {
func addActivityIndicator() {
let frame = CGRect(x: 0, y: 0, width: 40, height: 40)
Expand Down
Loading