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
7 changes: 6 additions & 1 deletion Common/Models/PumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = [
var availableStaticPumpManagers: [PumpManagerDescriptor] {
if FeatureFlags.allowSimulators {
return [
PumpManagerDescriptor(identifier: MockPumpManager.managerIdentifier, localizedTitle: MockPumpManager.localizedTitle)
PumpManagerDescriptor(
identifier: MockPumpManager.managerIdentifier,
localizedTitle: MockPumpManager.localizedTitle,
manufacturer: "Simulator",
image: MockPumpManager.pickerImage
)
]
} else {
return []
Expand Down
4 changes: 4 additions & 0 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; };
4FDDD23720DC51DF00D04B16 /* LoopDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDDD23620DC51DF00D04B16 /* LoopDataManager.swift */; };
4FF4D0F81E1725B000846527 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434F54561D287FDB002A9274 /* NibLoadable.swift */; };
5021CA8C264146C9DFF33E9D /* PluginPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE102E23D8C1C1291EF66387 /* PluginPickerView.swift */; };
7D23667D21250C7E0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23667C21250C7E0028B67D /* LocalizedString.swift */; };
7E69CFFC2A16A77E00203CBD /* ResetLoopManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */; };
824A01CBE97A4F7A9D32F7D6 /* Loop/Resources/Sounds/clear_chimes.caf in Resources */ = {isa = PBXBuildFile; fileRef = 4A645E0E2DDA534A635C9201 /* Loop/Resources/Sounds/clear_chimes.caf */; };
Expand Down Expand Up @@ -1398,6 +1399,7 @@
C9A47B0475B13A9543390BC8 /* Loop/Resources/Sounds/critical.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/critical.caf; sourceTree = SOURCE_ROOT; };
C9EA3F48338B49BD9C6BD0F9 /* Loop/Resources/Sounds/urgent_low.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/urgent_low.caf; sourceTree = SOURCE_ROOT; };
C9SCENED0000000000000002 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
CE102E23D8C1C1291EF66387 /* PluginPickerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PluginPickerView.swift; sourceTree = "<group>"; };
DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrectionSelectionView.swift; sourceTree = "<group>"; };
DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationFactorStrategy.swift; sourceTree = "<group>"; };
DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorStrategy.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2057,6 +2059,7 @@
84DEB10C2C18FABA00170734 /* IOSFocusModesView.swift */,
84D1F1A62D09053A00CB271F /* StatusTableView.swift */,
B429CAB22E97C7F300FA988E /* LoopStatusModalView.swift */,
CE102E23D8C1C1291EF66387 /* PluginPickerView.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -3648,6 +3651,7 @@
B40D07C7251A89D500C1C6D7 /* GlucoseDisplay.swift in Sources */,
43C2FAE11EB656A500364AFF /* GlucoseEffectVelocity.swift in Sources */,
14C970862C5C358C00E8A01B /* FavoriteFoodInsightsChartsView.swift in Sources */,
5021CA8C264146C9DFF33E9D /* PluginPickerView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
82 changes: 0 additions & 82 deletions Loop/Extensions/UIAlertController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,6 @@ import LoopKitUI


extension UIAlertController {
/// Initializes an action sheet-styled controller for selecting a PumpManager
///
/// - Parameters:
/// - availablePumpManagers: An array of available PumpManagers
/// - selectionHandler: A closure to execute when a manager is selected
/// - identifier: Identifier of the selected PumpManager
internal convenience init(availablePumpManagers: [PumpManagerDescriptor], selectionHandler: @escaping (_ identifier: String) -> Void) {
self.init(
title: NSLocalizedString("Add Pump", comment: "Action sheet title selecting Pump"),
message: nil,
preferredStyle: .actionSheet
)

for availablePumpManager in availablePumpManagers {
addAction(UIAlertAction(
title: availablePumpManager.localizedTitle,
style: .default,
handler: { (_) in
selectionHandler(availablePumpManager.identifier)
}
))
}

addCancelAction()
}

/// Initializes an action sheet-styled controller for selecting a CGMManager
///
/// - Parameters:
/// - availableCGMManagers: An array of available CGMManagers
/// - selectionHandler: A closure to execute when either a new CGMManager or the current PumpManager is selected
/// - identifier: Identifier of the selected CGMManager
internal convenience init(availableCGMManagers: [CGMManagerDescriptor], selectionHandler: @escaping (_ identifier: String) -> Void) {
self.init(
title: NSLocalizedString("Add CGM", comment: "Action sheet title selecting CGM"),
message: nil,
preferredStyle: .actionSheet
)

for availableCGMManager in availableCGMManagers.sorted(by: {$0.localizedTitle < $1.localizedTitle}) {
addAction(UIAlertAction(
title: availableCGMManager.localizedTitle,
style: .default,
handler: { (_) in
selectionHandler(availableCGMManager.identifier)
}
))
}

addCancelAction()
}

internal convenience init(deleteCGMManagerHandler handler: @escaping (_ isDeleted: Bool) -> Void) {
self.init(
title: nil,
Expand All @@ -84,36 +32,6 @@ extension UIAlertController {
}
}

/// Initializes an action sheet-styled controller for selecting a service.
///
/// - Parameters:
/// - availableServices: An array of available services.
/// - 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: preferredStyle
)

for availableService in availableServices {
addAction(UIAlertAction(
title: availableService.localizedTitle,
style: .default,
handler: { (_) in
selectionHandler(availableService.identifier)
}
))
}

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: .destructive, handler: handler))
Expand Down
13 changes: 9 additions & 4 deletions Loop/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -5450,7 +5450,7 @@
}
},
"Add CGM" : {
"comment" : "Action sheet title selecting CGM\nThe title of the CGM chooser in settings\nTitle text for button to add CGM device\nTitle text for button to set up a CGM",
"comment" : "The title of the CGM chooser in settings\nTitle text for button to add CGM device\nTitle text for button to set up a CGM",
"localizations" : {
"ar" : {
"stringUnit" : {
Expand Down Expand Up @@ -5760,7 +5760,7 @@
}
},
"Add Pump" : {
"comment" : "Action sheet title selecting Pump\nThe title of the pump chooser in settings\nTitle text for button to add pump device\nTitle text for button to set up a Pump",
"comment" : "The title of the pump chooser in settings\nTitle text for button to add pump device\nTitle text for button to set up a Pump",
"localizations" : {
"ar" : {
"stringUnit" : {
Expand Down Expand Up @@ -5885,7 +5885,7 @@
}
},
"Add Service" : {
"comment" : "Action sheet title selecting service\nThe title of the add service action sheet in settings\nThe title of the add service button in settings",
"comment" : "The title of the add service button in settings\nThe title of the add service picker in settings",
"localizations" : {
"da" : {
"stringUnit" : {
Expand Down Expand Up @@ -10401,7 +10401,7 @@
"comment" : "Focus modes section title (1: app name)"
},
"Cancel" : {
"comment" : "Button label for cancel\nButton text to cancel\nCancel button for reset loop alert\nCancel export button title\nThe title of the cancel action in an action sheet",
"comment" : "Button label for cancel\nButton text to cancel\nCancel button for reset loop alert\nCancel export button title\nThe title of the cancel action in an action sheet\nThe title of the cancel button in the plugin picker",
"localizations" : {
"ar" : {
"stringUnit" : {
Expand Down Expand Up @@ -30171,6 +30171,9 @@
"comment" : "A button that opens the iOS settings app.",
"isCommentAutoGenerated" : true
},
"Other" : {
"comment" : "The group name for plugins that do not declare a manufacturer or category"
},
"Override Presets" : {
"comment" : "The title text for the override presets",
"extractionState" : "manual",
Expand Down Expand Up @@ -40331,6 +40334,7 @@
},
"Until I enter carbs" : {
"comment" : "The title of a target alert action specifying pre-meal targets duration for 1 hour or until the user enters carbs (whichever comes first).",
"extractionState" : "stale",
"localizations" : {
"da" : {
"stringUnit" : {
Expand Down Expand Up @@ -40607,6 +40611,7 @@
},
"Use Pre-Meal Preset" : {
"comment" : "The title of the alert controller used to select a duration for pre-meal targets",
"extractionState" : "stale",
"localizations" : {
"da" : {
"stringUnit" : {
Expand Down
8 changes: 7 additions & 1 deletion Loop/Managers/CGMManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import LoopKit
import LoopKitUI
import MockKit
import MockKitUI

let staticCGMManagersByIdentifier: [String: CGMManager.Type] = [
MockCGMManager.managerIdentifier: MockCGMManager.self
Expand All @@ -16,7 +17,12 @@ let staticCGMManagersByIdentifier: [String: CGMManager.Type] = [
var availableStaticCGMManagers: [CGMManagerDescriptor] {
if FeatureFlags.allowSimulators {
return [
CGMManagerDescriptor(identifier: MockCGMManager.managerIdentifier, localizedTitle: MockCGMManager.localizedTitle)
CGMManagerDescriptor(
identifier: MockCGMManager.managerIdentifier,
localizedTitle: MockCGMManager.localizedTitle,
manufacturer: "Simulator",
image: MockCGMManager.pickerImage
)
]
} else {
return []
Expand Down
11 changes: 10 additions & 1 deletion Loop/Managers/DeviceDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,16 @@ final class DeviceDataManager {
var availableCGMManagers: [CGMManagerDescriptor] {
var availableCGMManagers = pluginManager.availableCGMManagers + availableStaticCGMManagers
if let pumpManagerAsCGMManager = pumpManager as? CGMManager {
availableCGMManagers.append(CGMManagerDescriptor(identifier: pumpManagerAsCGMManager.pluginIdentifier, localizedTitle: pumpManagerAsCGMManager.localizedTitle))
let identifier = pumpManagerAsCGMManager.pluginIdentifier
let manufacturer = pluginManager.pluginBundle(forIdentifier: identifier)?.object(forInfoDictionaryKey: LoopPluginBundleKey.deviceManufacturer.rawValue) as? String
availableCGMManagers.append(
CGMManagerDescriptor(
identifier: identifier,
localizedTitle: pumpManagerAsCGMManager.localizedTitle,
manufacturer: manufacturer,
image: (type(of: pumpManagerAsCGMManager) as? CGMManagerUI.Type)?.pickerImage
)
)
}

availableCGMManagers = availableCGMManagers.filter({ cgmManager in
Expand Down
8 changes: 7 additions & 1 deletion Loop/Managers/Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import LoopKit
import LoopKitUI
import MockKit
import MockKitUI

let staticServices: [Service.Type] = [MockService.self]

Expand All @@ -19,7 +20,12 @@ let staticServicesByIdentifier: [String: Service.Type] = [
var availableStaticServices: [ServiceDescriptor] {
if FeatureFlags.allowSimulators {
return [
ServiceDescriptor(identifier: MockService.serviceIdentifier, localizedTitle: MockService.localizedTitle)
ServiceDescriptor(
identifier: MockService.serviceIdentifier,
localizedTitle: MockService.localizedTitle,
category: "Simulator",
image: MockService.image
)
]
} else {
return []
Expand Down
52 changes: 39 additions & 13 deletions Loop/Plugins/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,18 @@ class PluginManager {
var availablePumpManagers: [PumpManagerDescriptor] {
pluginBundles.compactMap({ (bundle) -> PumpManagerDescriptor? in
guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerDisplayName.rawValue) as? String,
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String else {
return nil
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String else {
return nil
}

return PumpManagerDescriptor(identifier: identifier, localizedTitle: title)

return PumpManagerDescriptor(
identifier: identifier,
localizedTitle: title,
manufacturer: bundle.object(
forInfoDictionaryKey: LoopPluginBundleKey.deviceManufacturer.rawValue
) as? String,
image: getPumpManagerTypeByIdentifier(identifier)?.pickerImage
)
})
}

Expand Down Expand Up @@ -107,11 +114,16 @@ class PluginManager {
var availableCGMManagers: [CGMManagerDescriptor] {
pluginBundles.compactMap({ (bundle) -> CGMManagerDescriptor? in
guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerDisplayName.rawValue) as? String,
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String else {
return nil
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String else {
return nil
}

return CGMManagerDescriptor(identifier: identifier, localizedTitle: title)

return CGMManagerDescriptor(
identifier: identifier,
localizedTitle: title,
manufacturer: bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.deviceManufacturer.rawValue) as? String,
image: getCGMManagerTypeByIdentifier(identifier)?.pickerImage
)
})
}

Expand Down Expand Up @@ -141,16 +153,30 @@ class PluginManager {
}

var availableServices: [ServiceDescriptor] {
return pluginBundles.compactMap({ (bundle) -> ServiceDescriptor? in
pluginBundles.compactMap({ (bundle) -> ServiceDescriptor? in
guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceDisplayName.rawValue) as? String,
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceIdentifier.rawValue) as? String else {
return nil
let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceIdentifier.rawValue) as? String else {
return nil
}

return ServiceDescriptor(identifier: identifier, localizedTitle: title)

return ServiceDescriptor(
identifier: identifier,
localizedTitle: title,
category: bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.serviceCategory.rawValue) as? String,
image: getServiceTypeByIdentifier(identifier)?.image
)
})
}

func pluginBundle(forIdentifier identifier: String) -> Bundle? {
let identifierKeys: [LoopPluginBundleKey] = [.pumpManagerIdentifier, .cgmManagerIdentifier, .serviceIdentifier]
return pluginBundles.first { bundle in
identifierKeys.contains { key in
(bundle.object(forInfoDictionaryKey: key.rawValue) as? String) == identifier
}
}
}

func getStatefulPluginTypeByIdentifier(_ identifier: String) -> StatefulPluggable.Type? {
for bundle in pluginBundles {
if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.statefulPluginIdentifier.rawValue) as? String, name == identifier {
Expand Down
Loading