From 3c76fbea7bf780b3e789b1083916005d79a098f2 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Sat, 18 Jul 2026 23:58:42 -0700 Subject: [PATCH 1/5] xcode 27 --- Loop.xcodeproj/project.pbxproj | 67 +-- .../xcshareddata/swiftpm/Package.resolved | 39 +- .../xcshareddata/xcschemes/WatchApp.xcscheme | 2 - Loop/AppDelegate.swift | 62 +-- Loop/Extensions/ChartSettings+Loop.swift | 2 +- Loop/Extensions/Image+Optional.swift | 20 - Loop/Extensions/UIAlertController.swift | 8 +- Loop/Info.plist | 23 +- .../Live Activity/ChartAxisGenerator.swift | 7 +- Loop/Managers/LoopAppManager.swift | 2 + Loop/Managers/StatusChartsManager.swift | 10 +- Loop/SceneDelegate.swift | 88 +++ .../CarbAbsorptionViewController.swift | 2 +- .../PredictionTableViewController.swift | 6 +- .../StatusTableViewController.swift | 25 +- Loop/View Models/BolusEntryViewModel.swift | 1 - Loop/Views/Charts/LoopChartView.swift | 2 +- Loop/Views/SettingsView.swift | 71 ++- Loop/Views/StatusTableView.swift | 279 ++++++---- LoopUI/Models/ChartLineModel.swift | 19 +- LoopUITests/LoopChartUITests.swift | 500 ++++++++++++++++++ 21 files changed, 893 insertions(+), 342 deletions(-) delete mode 100644 Loop/Extensions/Image+Optional.swift create mode 100644 Loop/SceneDelegate.swift create mode 100644 LoopUITests/LoopChartUITests.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index e94e7da8db..4ffe6b482f 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + CAFE10000000000000000009 /* LoopChartUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE10000000000000000008 /* LoopChartUITests.swift */; }; 1419606928D9554E00BA86E0 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 437AFEE6203688CF008C4892 /* LoopKitUI.framework */; }; 1419606A28D955BC00BA86E0 /* MockKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C101947127DD473C004E7EB8 /* MockKitUI.framework */; }; 142CB7592A60BF2E0075748A /* EditMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142CB7582A60BF2E0075748A /* EditMode.swift */; }; @@ -158,6 +159,7 @@ 4374B5EF209D84BF00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; 4374B5F0209D857E00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43776F8F1B8022E90074EA36 /* AppDelegate.swift */; }; + C9SCENED0000000000000001 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9SCENED0000000000000002 /* SceneDelegate.swift */; }; 43776F971B8022E90074EA36 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43776F951B8022E90074EA36 /* Main.storyboard */; }; 43785E932120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */; }; 43785E972120E4500057DED1 /* INRelevantShortcutStore+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E952120E4010057DED1 /* INRelevantShortcutStore+Loop.swift */; }; @@ -270,7 +272,6 @@ 8446319F2F5A2AB3003825AE /* PresetsPerformanceHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8446319E2F5A2AA9003825AE /* PresetsPerformanceHistoryViewModel.swift */; }; 847F23432E4543140035C864 /* ActivePresetBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847F23422E4543140035C864 /* ActivePresetBanner.swift */; }; 8496F7312B5711C4003E672C /* ContentMargin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8496F7302B5711C4003E672C /* ContentMargin.swift */; }; - 84A7B5502D2D972C00B6D202 /* Image+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A7B54F2D2D972C00B6D202 /* Image+Optional.swift */; }; 84AA81D32A4A27A3000B658B /* LoopWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA81D22A4A27A3000B658B /* LoopWidgets.swift */; }; 84AA81D62A4A28AF000B658B /* WidgetBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA81D52A4A28AF000B658B /* WidgetBackground.swift */; }; 84AA81D82A4A2910000B658B /* StatusWidgetTimelimeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA81D72A4A2910000B658B /* StatusWidgetTimelimeEntry.swift */; }; @@ -459,7 +460,6 @@ C10C57FE2E71E87D00A4825C /* ActiveOverrideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10C57FD2E71E87400A4825C /* ActiveOverrideView.swift */; }; C110888D2A3913C600BA4898 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C110888C2A3913C600BA4898 /* BuildDetails.swift */; }; C11445B22DA98A3400034864 /* CorrectionRangeInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11445B12DA98A2D00034864 /* CorrectionRangeInformationView.swift */; }; - C11B9D5B286778A800500CF8 /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C11B9D5A286778A800500CF8 /* SwiftCharts */; }; C11B9D5E286778D000500CF8 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C11B9D5D286778D000500CF8 /* LoopKitUI.framework */; }; C11B9D62286779C000500CF8 /* MockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C11B9D60286779C000500CF8 /* MockKit.framework */; }; C11B9D63286779C000500CF8 /* MockKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C11B9D60286779C000500CF8 /* MockKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -487,7 +487,6 @@ C152B9F72C9C7EF100ACBC06 /* AutomationHistoryEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C152B9F62C9C7EF100ACBC06 /* AutomationHistoryEntryTests.swift */; }; FEEDB0000000000000000005 /* GlucoseStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */; }; C1550B0C2E6F249A009369DC /* LoopCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1550B0B2E6F249A009369DC /* LoopCircleView.swift */; }; - C15A8C492A7305B1009D736B /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C1E3DC4628595FAA00CA19FF /* SwiftCharts */; }; C165756F2534C468004AE16E /* SimpleBolusViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165756E2534C468004AE16E /* SimpleBolusViewModelTests.swift */; }; C16575712538A36B004AE16E /* CGMStalenessMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16575702538A36B004AE16E /* CGMStalenessMonitor.swift */; }; C16575732538AFF6004AE16E /* CGMStalenessMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16575722538AFF6004AE16E /* CGMStalenessMonitorTests.swift */; }; @@ -554,7 +553,6 @@ C1DE5D23251BFC4D00439E49 /* SimpleBolusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DE5D22251BFC4D00439E49 /* SimpleBolusView.swift */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C1E3862628247C6100F561A4 /* StoredLoopNotRunningNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E3862428247B7100F561A4 /* StoredLoopNotRunningNotification.swift */; }; - C1E3DC4928595FAA00CA19FF /* SwiftCharts in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C1E3DC4628595FAA00CA19FF /* SwiftCharts */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C1ED6C612E79BBA5002F91C2 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1ED6C602E79BB9C002F91C2 /* NotificationManager.swift */; }; C1ED6C622E79BBA5002F91C2 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1ED6C602E79BB9C002F91C2 /* NotificationManager.swift */; }; C1ED6C642E7C6DB9002F91C2 /* SettingsRequestUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10C57E82E705CCB00A4825C /* SettingsRequestUserInfo.swift */; }; @@ -588,8 +586,6 @@ C1ED6C832E7D8E3D002F91C2 /* AcknowledgeAlertUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1ED6C812E7D8E36002F91C2 /* AcknowledgeAlertUserInfo.swift */; }; C1EE9E812A38D0FB0064784A /* BuildDetails.plist in Resources */ = {isa = PBXBuildFile; fileRef = C1EE9E802A38D0FB0064784A /* BuildDetails.plist */; }; C1EF747228D6A44A00C8C083 /* CrashRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */; }; - C1F00C60285A802A006302C5 /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; }; - C1F00C78285A8256006302C5 /* SwiftCharts in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */; }; C1F2CAAA2B76B3EE00D7F581 /* TempBasalRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2CAA92B76B3EE00D7F581 /* TempBasalRecommendation.swift */; }; C1F2CAAC2B7A980600D7F581 /* BasalRelativeDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2CAAB2B7A980600D7F581 /* BasalRelativeDose.swift */; }; @@ -654,6 +650,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + CAFE1000000000000000000B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43776F8B1B8022E90074EA36; + remoteInfo = Loop; + }; 1481F9BD28DA26F4004C5AEB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 43776F841B8022E90074EA36 /* Project object */; @@ -743,7 +746,6 @@ C11B9D63286779C000500CF8 /* MockKit.framework in Embed Frameworks */, C19C8BBB28651DFB0056D5E4 /* TrueTime.framework in Embed Frameworks */, C11B9D65286779C000500CF8 /* MockKitUI.framework in Embed Frameworks */, - C1F00C78285A8256006302C5 /* SwiftCharts in Embed Frameworks */, C19C8BBF28651E3D0056D5E4 /* LoopKit.framework in Embed Frameworks */, 43D9FFD721EAE05D00AF44BF /* LoopCore.framework in Embed Frameworks */, ); @@ -780,7 +782,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - C1E3DC4928595FAA00CA19FF /* SwiftCharts in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -788,6 +789,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + CAFE10000000000000000002 /* LoopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoopUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CAFE10000000000000000008 /* LoopChartUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopChartUITests.swift; sourceTree = ""; }; 142CB7582A60BF2E0075748A /* EditMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMode.swift; sourceTree = ""; }; 142CB75A2A60BFC30075748A /* FavoriteFoodsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteFoodsView.swift; sourceTree = ""; }; 1452F4A82A851C9400F8B9E4 /* FavoriteFoodAddEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteFoodAddEditViewModel.swift; sourceTree = ""; }; @@ -927,6 +930,7 @@ 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewCell.swift; sourceTree = ""; }; 43776F8C1B8022E90074EA36 /* Loop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Loop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43776F8F1B8022E90074EA36 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C9SCENED0000000000000002 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 43776F961B8022E90074EA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43776F9B1B8022E90074EA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NewCarbEntryIntent+Loop.swift"; sourceTree = ""; }; @@ -1071,7 +1075,6 @@ 8446319E2F5A2AA9003825AE /* PresetsPerformanceHistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetsPerformanceHistoryViewModel.swift; sourceTree = ""; }; 847F23422E4543140035C864 /* ActivePresetBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePresetBanner.swift; sourceTree = ""; }; 8496F7302B5711C4003E672C /* ContentMargin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentMargin.swift; sourceTree = ""; }; - 84A7B54F2D2D972C00B6D202 /* Image+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Optional.swift"; sourceTree = ""; }; 84AA81D22A4A27A3000B658B /* LoopWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopWidgets.swift; sourceTree = ""; }; 84AA81D52A4A28AF000B658B /* WidgetBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBackground.swift; sourceTree = ""; }; 84AA81D72A4A2910000B658B /* StatusWidgetTimelimeEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusWidgetTimelimeEntry.swift; sourceTree = ""; }; @@ -1458,6 +1461,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + CAFE10000000000000000007 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 14B1735928AED9EC006CCD7C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1493,7 +1503,6 @@ C11B9D62286779C000500CF8 /* MockKit.framework in Frameworks */, C19C8BBE28651E3D0056D5E4 /* LoopKit.framework in Frameworks */, C19C8BBA28651DFB0056D5E4 /* TrueTime.framework in Frameworks */, - C1F00C60285A802A006302C5 /* SwiftCharts in Frameworks */, C19C8BC328651EAE0056D5E4 /* LoopTestingKit.framework in Frameworks */, C1735B1E2A0809830082BB8A /* ZIPFoundation in Frameworks */, C19C8BCE28651F520056D5E4 /* LoopKitUI.framework in Frameworks */, @@ -1521,7 +1530,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C15A8C492A7305B1009D736B /* SwiftCharts in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1530,7 +1538,6 @@ buildActionMask = 2147483647; files = ( C1CCF1122858FA900035389C /* LoopCore.framework in Frameworks */, - C11B9D5B286778A800500CF8 /* SwiftCharts in Frameworks */, C11B9D5E286778D000500CF8 /* LoopKitUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1772,6 +1779,7 @@ 43F5C2D41B92A4A6003EB13D /* Info.plist */, C1EE9E802A38D0FB0064784A /* BuildDetails.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, + C9SCENED0000000000000002 /* SceneDelegate.swift */, 1D12D3B82548EFDD00B53E8B /* main.swift */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, @@ -1983,7 +1991,6 @@ C13DA2AF24F6C7690098BB29 /* UIViewController.swift */, 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */, A9B607AF247F000F00792BE4 /* UserNotifications+Loop.swift */, - 84A7B54F2D2D972C00B6D202 /* Image+Optional.swift */, ); path = Extensions; sourceTree = ""; @@ -2831,7 +2838,6 @@ ); name = Loop; packageProductDependencies = ( - C1F00C5F285A802A006302C5 /* SwiftCharts */, C1D6EE9F2A06C7270047DE5C /* MKRingProgressView */, C1735B1D2A0809830082BB8A /* ZIPFoundation */, ); @@ -2911,7 +2917,6 @@ ); name = LoopTests; packageProductDependencies = ( - C1E3DC4628595FAA00CA19FF /* SwiftCharts */, ); productName = LoopTests; productReference = 43E2D90B1D20C581004DA55F /* LoopTests.xctest */; @@ -2933,7 +2938,6 @@ ); name = LoopUI; packageProductDependencies = ( - C11B9D5A286778A800500CF8 /* SwiftCharts */, ); productName = LoopUI; productReference = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; @@ -3058,7 +3062,6 @@ ); mainGroup = 43776F831B8022E90074EA36; packageReferences = ( - C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */, C1D6EE9E2A06C7270047DE5C /* XCRemoteSwiftPackageReference "MKRingProgressView" */, C1735B1C2A0809830082BB8A /* XCRemoteSwiftPackageReference "ZIPFoundation" */, ); @@ -3390,6 +3393,7 @@ E9B355292935919E0076AB04 /* MissedMealSettings.swift in Sources */, 43A51E1F1EB6D62A000736CC /* CarbAbsorptionViewController.swift in Sources */, 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */, + C9SCENED0000000000000001 /* SceneDelegate.swift in Sources */, 4372E48B213CB5F00068E043 /* Double.swift in Sources */, 84DF48C12F6A0AED00BEDB40 /* Double+Closest.swift in Sources */, 430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */, @@ -3612,7 +3616,6 @@ 439706E622D2E84900C81566 /* PredictionSettingTableViewCell.swift in Sources */, 430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */, 43A51E211EB6DBDD000736CC /* LoopChartsTableViewController.swift in Sources */, - 84A7B5502D2D972C00B6D202 /* Image+Optional.swift in Sources */, 8968B1122408B3520074BB48 /* UIFont.swift in Sources */, 1452F4A92A851C9400F8B9E4 /* FavoriteFoodAddEditViewModel.swift in Sources */, 438D42FB1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift in Sources */, @@ -3912,6 +3915,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + CAFE1000000000000000000A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43776F8B1B8022E90074EA36 /* Loop */; + targetProxy = CAFE1000000000000000000B /* PBXContainerItemProxy */; + }; 1481F9BE28DA26F4004C5AEB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4F75288A1DFE1DC600C322D6 /* LoopUI */; @@ -5187,14 +5195,6 @@ revision = c67b7509ec82ee2b4b0ab3f97742b94ed9692494; }; }; - C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ivanschuetz/SwiftCharts"; - requirement = { - branch = master; - kind = branch; - }; - }; C1D6EE9E2A06C7270047DE5C /* XCRemoteSwiftPackageReference "MKRingProgressView" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/maxkonovalov/MKRingProgressView.git"; @@ -5206,11 +5206,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - C11B9D5A286778A800500CF8 /* SwiftCharts */ = { - isa = XCSwiftPackageProductDependency; - package = C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */; - productName = SwiftCharts; - }; C1735B1D2A0809830082BB8A /* ZIPFoundation */ = { isa = XCSwiftPackageProductDependency; package = C1735B1C2A0809830082BB8A /* XCRemoteSwiftPackageReference "ZIPFoundation" */; @@ -5221,16 +5216,6 @@ package = C1D6EE9E2A06C7270047DE5C /* XCRemoteSwiftPackageReference "MKRingProgressView" */; productName = MKRingProgressView; }; - C1E3DC4628595FAA00CA19FF /* SwiftCharts */ = { - isa = XCSwiftPackageProductDependency; - package = C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */; - productName = SwiftCharts; - }; - C1F00C5F285A802A006302C5 /* SwiftCharts */ = { - isa = XCSwiftPackageProductDependency; - package = C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */; - productName = SwiftCharts; - }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9cb3736d66..bbc0c86c20 100644 --- a/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,32 +1,23 @@ { - "originHash" : "c4cada90348e9cd61ccdc1fdd95d021f037913f127c30ed5678702f19c1b75db", - "pins" : [ + "originHash": "c4cada90348e9cd61ccdc1fdd95d021f037913f127c30ed5678702f19c1b75db", + "pins": [ { - "identity" : "mkringprogressview", - "kind" : "remoteSourceControl", - "location" : "https://github.com/maxkonovalov/MKRingProgressView.git", - "state" : { - "branch" : "master", - "revision" : "660888aab1d2ab0ed7eb9eb53caec12af4955fa7" + "identity": "mkringprogressview", + "kind": "remoteSourceControl", + "location": "https://github.com/maxkonovalov/MKRingProgressView.git", + "state": { + "branch": "master", + "revision": "660888aab1d2ab0ed7eb9eb53caec12af4955fa7" } }, { - "identity" : "swiftcharts", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ivanschuetz/SwiftCharts", - "state" : { - "branch" : "master", - "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2" - } - }, - { - "identity" : "zipfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/LoopKit/ZIPFoundation.git", - "state" : { - "revision" : "c67b7509ec82ee2b4b0ab3f97742b94ed9692494" + "identity": "zipfoundation", + "kind": "remoteSourceControl", + "location": "https://github.com/LoopKit/ZIPFoundation.git", + "state": { + "revision": "c67b7509ec82ee2b4b0ab3f97742b94ed9692494" } } ], - "version" : 3 -} + "version": 3 +} \ No newline at end of file diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme index 4d7e977e53..d36ffa7ea7 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme @@ -84,7 +84,6 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "43A943711B926B7B0051FA24" BuildableName = "WatchApp.app" - BlueprintName = "WatchApp" ReferencedContainer = "container:Loop.xcodeproj"> @@ -102,7 +101,6 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "43A943711B926B7B0051FA24" BuildableName = "WatchApp.app" - BlueprintName = "WatchApp" ReferencedContainer = "container:Loop.xcodeproj"> diff --git a/Loop/AppDelegate.swift b/Loop/AppDelegate.swift index 0e0eeb1222..33af2f4ae0 100644 --- a/Loop/AppDelegate.swift +++ b/Loop/AppDelegate.swift @@ -9,10 +9,12 @@ import UIKit import LoopKit -final class AppDelegate: UIResponder, UIApplicationDelegate, WindowProvider { - var window: UIWindow? +final class AppDelegate: UIResponder, UIApplicationDelegate { + + let loopAppManager = LoopAppManager() + + private(set) var launchOptions: [UIApplication.LaunchOptionsKey: Any]? - private let loopAppManager = LoopAppManager() private let log = DiagnosticLog(category: "AppDelegate") // MARK: - UIApplicationDelegate - Initialization @@ -22,45 +24,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, WindowProvider { setenv("CFNETWORK_DIAGNOSTICS", "3", 1) - log.default("lastPathComponent = %{public}@", String(describing: Bundle.main.appStoreReceiptURL?.lastPathComponent)) - - loopAppManager.initialize(windowProvider: self, launchOptions: launchOptions) - loopAppManager.launch() - return loopAppManager.isLaunchComplete - // Avoid doing full initialization when running tests - if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil { - loopAppManager.initialize(windowProvider: self, launchOptions: launchOptions) - loopAppManager.launch() - return loopAppManager.isLaunchComplete - } else { - return true - } - } - - // MARK: - UIApplicationDelegate - Life Cycle - - func applicationDidBecomeActive(_ application: UIApplication) { - log.default(#function) + self.launchOptions = launchOptions - loopAppManager.didBecomeActive() - } - - func applicationWillResignActive(_ application: UIApplication) { - log.default(#function) - } - - func applicationDidEnterBackground(_ application: UIApplication) { - log.default(#function) - } - - func applicationWillEnterForeground(_ application: UIApplication) { - log.default(#function) - - loopAppManager.askUserToConfirmLoopReset() - } - - func applicationWillTerminate(_ application: UIApplication) { - log.default(#function) + return true } // MARK: - UIApplicationDelegate - Environment @@ -91,20 +57,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, WindowProvider { completionHandler(loopAppManager.handleRemoteNotification(userInfo as? [String: AnyObject]) ? .noData : .failed) } - - // MARK: - UIApplicationDelegate - Deeplinking - - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - loopAppManager.handle(url) - } - - // MARK: - UIApplicationDelegate - Continuity - - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - log.default(#function) - - return loopAppManager.userActivity(userActivity, restorationHandler: restorationHandler) - } // MARK: - UIApplicationDelegate - Interface diff --git a/Loop/Extensions/ChartSettings+Loop.swift b/Loop/Extensions/ChartSettings+Loop.swift index 731a67f126..348d9120c0 100644 --- a/Loop/Extensions/ChartSettings+Loop.swift +++ b/Loop/Extensions/ChartSettings+Loop.swift @@ -5,7 +5,7 @@ // Copyright © 2017 LoopKit Authors. All rights reserved. // -import SwiftCharts +import LoopKitUI extension ChartSettings { static var `default`: ChartSettings { diff --git a/Loop/Extensions/Image+Optional.swift b/Loop/Extensions/Image+Optional.swift deleted file mode 100644 index 775196265e..0000000000 --- a/Loop/Extensions/Image+Optional.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Image+Optional.swift -// Loop -// -// Created by Cameron Ingham on 1/7/25. -// Copyright © 2025 LoopKit Authors. All rights reserved. -// - -import SwiftUI - -// Since this `Image` initializer provides a view even if the asset is not found in the bundle, it can double the spacing between adjacent elements in a `VStack`, `HStack`, etc. -extension Image { - init?(_ name: String, bundle: Bundle? = nil) { - if let _ = UIImage(named: name, in: bundle, with: nil) { - self = Image(name, bundle: bundle) - } else { - return nil - } - } -} diff --git a/Loop/Extensions/UIAlertController.swift b/Loop/Extensions/UIAlertController.swift index e483d7d2fd..bce885b56c 100644 --- a/Loop/Extensions/UIAlertController.swift +++ b/Loop/Extensions/UIAlertController.swift @@ -43,7 +43,7 @@ extension UIAlertController { self.init( title: NSLocalizedString("Add Pump", comment: "Action sheet title selecting Pump"), message: nil, - preferredStyle: .actionSheet + preferredStyle: .alert ) for availablePumpManager in availablePumpManagers { @@ -55,6 +55,8 @@ extension UIAlertController { } )) } + + addCancelAction() } /// Initializes an action sheet-styled controller for selecting a CGMManager @@ -67,7 +69,7 @@ extension UIAlertController { self.init( title: NSLocalizedString("Add CGM", comment: "Action sheet title selecting CGM"), message: nil, - preferredStyle: .actionSheet + preferredStyle: .alert ) for availableCGMManager in availableCGMManagers.sorted(by: {$0.localizedTitle < $1.localizedTitle}) { @@ -79,6 +81,8 @@ extension UIAlertController { } )) } + + addCancelAction() } internal convenience init(deleteCGMManagerHandler handler: @escaping (_ isDeleted: Bool) -> Void) { diff --git a/Loop/Info.plist b/Loop/Info.plist index f99682f82b..f68e6a0d89 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -84,6 +84,25 @@ NewCarbEntryIntent ViewLoopStatus + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UIBackgroundModes bluetooth-central @@ -91,12 +110,8 @@ remote-notification audio - UIDesignRequiresCompatibility - UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/Loop/Managers/Live Activity/ChartAxisGenerator.swift b/Loop/Managers/Live Activity/ChartAxisGenerator.swift index 536d10daa7..be4b621dc3 100644 --- a/Loop/Managers/Live Activity/ChartAxisGenerator.swift +++ b/Loop/Managers/Live Activity/ChartAxisGenerator.swift @@ -9,7 +9,6 @@ import Foundation import HealthKit import LoopAlgorithm -import SwiftCharts import UIKit struct ChartAxisGenerator { @@ -19,7 +18,6 @@ struct ChartAxisGenerator { private static let minSegmentCount: Double = 2 private static let addPaddingSegmentIfEdge = false - private static let axisLabelSettings = ChartLabelSettings(font: .systemFont(ofSize: 14), fontColor: UIColor.secondaryLabel) // This logic is copied/ported from generateYAxisValuesUsingLinearSegmentStep static func getYAxis(points: [Double], isMmol: Bool) -> [Double] { @@ -101,7 +99,7 @@ struct ChartAxisGenerator { { scalar = 0 } - return ChartAxisValueDouble(scalar, labelSettings: axisLabelSettings).scalar + return scalar } } @@ -119,7 +117,10 @@ struct ChartAxisGenerator { } } +infix operator >=~ : ComparisonPrecedence + fileprivate extension Double { + // `=~` is declared in Common/Extensions/Double.swift static func >=~ (a: Double, b: Double) -> Bool { return a =~ b || a > b } diff --git a/Loop/Managers/LoopAppManager.swift b/Loop/Managers/LoopAppManager.swift index 829f9f22f4..58d1938e46 100644 --- a/Loop/Managers/LoopAppManager.swift +++ b/Loop/Managers/LoopAppManager.swift @@ -161,6 +161,8 @@ class LoopAppManager: NSObject { var isLaunchComplete: Bool { state == .launchComplete } + var isInInitialState: Bool { state == .initialize } + private func resumeLaunch() async { if state == .checkProtectedDataAvailable { checkProtectedDataAvailable() diff --git a/Loop/Managers/StatusChartsManager.swift b/Loop/Managers/StatusChartsManager.swift index b72a4fd031..0666136655 100644 --- a/Loop/Managers/StatusChartsManager.swift +++ b/Loop/Managers/StatusChartsManager.swift @@ -9,8 +9,8 @@ import Foundation import LoopKit import LoopUI import LoopKitUI -import SwiftCharts import LoopAlgorithm +import UIKit class StatusChartsManager: ChartsManager { @@ -68,7 +68,7 @@ extension StatusChartsManager { invalidateChart(atIndex: ChartIndex.glucose.rawValue) } - func glucoseChart(withFrame frame: CGRect) -> Chart? { + func glucoseChart(withFrame frame: CGRect) -> UIView? { return chart(atIndex: ChartIndex.glucose.rawValue, frame: frame) } @@ -109,7 +109,7 @@ extension StatusChartsManager { invalidateChart(atIndex: ChartIndex.iob.rawValue) } - func iobChart(withFrame frame: CGRect, highlightLabelOffsetY: CGFloat) -> Chart? { + func iobChart(withFrame frame: CGRect, highlightLabelOffsetY: CGFloat) -> UIView? { return chart(atIndex: ChartIndex.iob.rawValue, frame: frame, highlightLabelOffsetY: highlightLabelOffsetY) } } @@ -121,7 +121,7 @@ extension StatusChartsManager { invalidateChart(atIndex: ChartIndex.dose.rawValue) } - func doseChart(withFrame frame: CGRect) -> Chart? { + func doseChart(withFrame frame: CGRect) -> UIView? { return chart(atIndex: ChartIndex.dose.rawValue, frame: frame) } } @@ -133,7 +133,7 @@ extension StatusChartsManager { invalidateChart(atIndex: ChartIndex.cob.rawValue) } - func cobChart(withFrame frame: CGRect) -> Chart? { + func cobChart(withFrame frame: CGRect) -> UIView? { return chart(atIndex: ChartIndex.cob.rawValue, frame: frame) } } diff --git a/Loop/SceneDelegate.swift b/Loop/SceneDelegate.swift new file mode 100644 index 0000000000..31b9e154d1 --- /dev/null +++ b/Loop/SceneDelegate.swift @@ -0,0 +1,88 @@ +// +// SceneDelegate.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import UIKit +import LoopKit + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate, WindowProvider { + + var window: UIWindow? + + private let log = DiagnosticLog(category: "SceneDelegate") + + private var loopAppManager: LoopAppManager? { + (UIApplication.shared.delegate as? AppDelegate)?.loopAppManager + } + + // MARK: - UIWindowSceneDelegate - Connection + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + log.default(#function) + + + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { + return + } + let loopAppManager = appDelegate.loopAppManager + + guard loopAppManager.isInInitialState else { + return + } + + loopAppManager.initialize(windowProvider: self, launchOptions: appDelegate.launchOptions) + loopAppManager.launch() + + if let url = connectionOptions.urlContexts.first?.url { + _ = loopAppManager.handle(url) + } + for userActivity in connectionOptions.userActivities { + _ = loopAppManager.userActivity(userActivity, restorationHandler: { _ in }) + } + } + + // MARK: - UIWindowSceneDelegate - Life Cycle + + func sceneDidBecomeActive(_ scene: UIScene) { + log.default(#function) + + loopAppManager?.didBecomeActive() + } + + func sceneWillResignActive(_ scene: UIScene) { + log.default(#function) + } + + func sceneWillEnterForeground(_ scene: UIScene) { + log.default(#function) + + guard let loopAppManager, loopAppManager.isLaunchComplete else { + return + } + loopAppManager.askUserToConfirmLoopReset() + } + + func sceneDidEnterBackground(_ scene: UIScene) { + log.default(#function) + } + + // MARK: - UIWindowSceneDelegate - Deeplinking + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + guard let url = URLContexts.first?.url else { + return + } + _ = loopAppManager?.handle(url) + } + + // MARK: - UIWindowSceneDelegate - Continuity + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + log.default(#function) + + _ = loopAppManager?.userActivity(userActivity, restorationHandler: { _ in }) + } +} diff --git a/Loop/View Controllers/CarbAbsorptionViewController.swift b/Loop/View Controllers/CarbAbsorptionViewController.swift index cd4f44e2b0..3bc9c49415 100644 --- a/Loop/View Controllers/CarbAbsorptionViewController.swift +++ b/Loop/View Controllers/CarbAbsorptionViewController.swift @@ -275,7 +275,7 @@ final class CarbAbsorptionViewController: LoopChartsTableViewController, Identif switch ChartRow(rawValue: indexPath.row)! { case .carbEffect: cell.setChartGenerator(generator: { [weak self] (frame) in - return self?.charts.chart(atIndex: 0, frame: frame)?.view + return self?.charts.chart(atIndex: 0, frame: frame) }) } diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index df7f672d07..d6b06bec3f 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -138,9 +138,9 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable } if let lastPoint = self.glucoseChart.alternatePredictedGlucosePoints?.last?.y { - let valueAttributedString = NSMutableAttributedString(string: String(describing: lastPoint.copy), attributes: [.font: UIFont.systemFont(ofSize: 22, weight: .semibold), .foregroundColor: ChartColorPalette.primary.glucoseTint]) + let valueAttributedString = NSMutableAttributedString(string: lastPoint.valueLabel ?? lastPoint.label, attributes: [.font: UIFont.systemFont(ofSize: 22, weight: .semibold), .foregroundColor: ChartColorPalette.primary.glucoseTint]) let spacer = NSAttributedString(string: "\u{00a0}") - let unitAttributedString = NSAttributedString(string: String(describing: lastPoint).replacingOccurrences(of: String(describing: lastPoint.copy), with: "").trimmingCharacters(in: .whitespacesAndNewlines), attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular), .foregroundColor: ChartColorPalette.primary.glucoseTint]) + let unitAttributedString = NSAttributedString(string: lastPoint.unitLabel ?? "", attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular), .foregroundColor: ChartColorPalette.primary.glucoseTint]) valueAttributedString.append(spacer) valueAttributedString.append(unitAttributedString) @@ -222,7 +222,7 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable let cell = tableView.dequeueReusableCell(withIdentifier: ChartTableViewCell.className, for: indexPath) as! ChartTableViewCell cell.contentView.layoutMargins.left = tableView.separatorInset.left cell.setChartGenerator(generator: { [weak self] (frame) in - return self?.charts.chart(atIndex: 0, frame: frame)?.view + return self?.charts.chart(atIndex: 0, frame: frame) }) self.tableView(tableView, updateTitleFor: cell, at: indexPath) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index db0511d6b2..736198bdf2 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -14,7 +14,6 @@ import LoopKit import LoopKitUI import LoopTestingKit import LoopUI -import SwiftCharts import os.log import Combine import WidgetKit @@ -227,7 +226,7 @@ final class StatusTableViewController: LoopChartsTableViewController { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: animated) - navigationController?.setToolbarHidden(false, animated: animated) + navigationController?.setToolbarHidden(true, animated: animated) alertPermissionsChecker.checkNow() @@ -541,9 +540,9 @@ final class StatusTableViewController: LoopChartsTableViewController { if !FeatureFlags.predictedGlucoseChartClampEnabled, let lastPoint = self.statusCharts.glucose.predictedGlucosePoints.last?.y { - let valueAttributedString = NSMutableAttributedString(string: String(describing: lastPoint.copy), attributes: [.font: UIFont.systemFont(ofSize: 22, weight: .semibold), .foregroundColor: ChartColorPalette.primary.glucoseTint]) + let valueAttributedString = NSMutableAttributedString(string: lastPoint.valueLabel ?? lastPoint.label, attributes: [.font: UIFont.systemFont(ofSize: 22, weight: .semibold), .foregroundColor: ChartColorPalette.primary.glucoseTint]) let spacer = NSAttributedString(string: "\u{00a0}") - let unitAttributedString = NSAttributedString(string: String(describing: lastPoint).replacingOccurrences(of: String(describing: lastPoint.copy), with: "").trimmingCharacters(in: .whitespacesAndNewlines), attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular), .foregroundColor: ChartColorPalette.primary.glucoseTint]) + let unitAttributedString = NSAttributedString(string: lastPoint.unitLabel ?? "", attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular), .foregroundColor: ChartColorPalette.primary.glucoseTint]) valueAttributedString.append(spacer) valueAttributedString.append(unitAttributedString) @@ -596,9 +595,10 @@ final class StatusTableViewController: LoopChartsTableViewController { charts.setCOBValues(cobValues) } if let index = charts.cob.cobPoints.closestIndex(priorTo: Date()) { - let valueAttributedString = NSMutableAttributedString(string: String(describing: charts.cob.cobPoints[index].y.copy), attributes: [.font: UIFont.systemFont(ofSize: 22, weight: .semibold), .foregroundColor: ChartColorPalette.primary.carbTint]) + let cobValue = charts.cob.cobPoints[index].y + let valueAttributedString = NSMutableAttributedString(string: cobValue.valueLabel ?? cobValue.label, attributes: [.font: UIFont.systemFont(ofSize: 22, weight: .semibold), .foregroundColor: ChartColorPalette.primary.carbTint]) let spacer = NSAttributedString(string: "\u{00a0}") - let unitAttributedString = NSAttributedString(string: String(describing: charts.cob.cobPoints[index].y).replacingOccurrences(of: String(describing: charts.cob.cobPoints[index].y.copy), with: "").trimmingCharacters(in: .whitespacesAndNewlines), attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular), .foregroundColor: ChartColorPalette.primary.carbTint]) + let unitAttributedString = NSAttributedString(string: cobValue.unitLabel ?? "", attributes: [.font: UIFont.systemFont(ofSize: 15, weight: .regular), .foregroundColor: ChartColorPalette.primary.carbTint]) valueAttributedString.append(spacer) valueAttributedString.append(unitAttributedString) @@ -643,7 +643,8 @@ final class StatusTableViewController: LoopChartsTableViewController { let statusRowMode = self.determineStatusRowMode() updateBannerAndHUDandStatusRows(statusRowMode: statusRowMode, newSize: currentContext.newSize, animated: animated) - + tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: UIDevice.current.orientation.isLandscape ? 0 : 52, right: 0) + redrawCharts() reloading = false @@ -1008,25 +1009,25 @@ final class StatusTableViewController: LoopChartsTableViewController { switch ChartRow(rawValue: indexPath.row)! { case .glucose: cell.setChartGenerator(generator: { [weak self] (frame) in - return self?.statusCharts.glucoseChart(withFrame: frame)?.view + return self?.statusCharts.glucoseChart(withFrame: frame) }) cell.setTitleLabelText(label: NSLocalizedString("Glucose", comment: "The title of the glucose and prediction graph")) cell.setTitleTextColor(color: ChartColorPalette.primary.glucoseTint) cell.doesNavigate = settingsManager.dosingEnabled || !FeatureFlags.simpleBolusCalculatorEnabled case .iob: cell.setSupplementalChartGenerator(generator: { [weak self] (frame) in - return self?.statusCharts.doseChart(withFrame: frame)?.view + return self?.statusCharts.doseChart(withFrame: frame) }) cell.setChartGenerator(generator: { [weak self] (frame) in - return self?.statusCharts.iobChart(withFrame: frame, highlightLabelOffsetY: cell.supplementalChartContentView?.bounds.height ?? 0)?.view + return self?.statusCharts.iobChart(withFrame: frame, highlightLabelOffsetY: cell.supplementalChartContentView?.bounds.height ?? 0) }) cell.setTitleLabelText(label: NSLocalizedString("Active Insulin", comment: "The title of the Insulin On-Board graph")) cell.setTitleTextColor(color: ChartColorPalette.primary.insulinTint) cell.setFooterView(content: iobFooterViewContent) case .cob: cell.setChartGenerator(generator: { [weak self] (frame) in - return self?.statusCharts.cobChart(withFrame: frame)?.view + return self?.statusCharts.cobChart(withFrame: frame) }) cell.setTitleLabelText(label: NSLocalizedString("Active Carbohydrates", comment: "The title of the Carbs On-Board graph")) cell.setTitleTextColor(color: ChartColorPalette.primary.carbTint) @@ -1208,7 +1209,7 @@ final class StatusTableViewController: LoopChartsTableViewController { // 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) + availableSize -= (tableView.safeAreaInsets.top + tableView.safeAreaInsets.bottom + tableView.contentInset.bottom + hudHeight) switch ChartRow(rawValue: indexPath.row)! { case .glucose: diff --git a/Loop/View Models/BolusEntryViewModel.swift b/Loop/View Models/BolusEntryViewModel.swift index 1ec763d9e4..2dea723af9 100644 --- a/Loop/View Models/BolusEntryViewModel.swift +++ b/Loop/View Models/BolusEntryViewModel.swift @@ -15,7 +15,6 @@ import LoopKit import LoopKitUI import LoopUI import SwiftUI -import SwiftCharts import LoopAlgorithm @MainActor diff --git a/Loop/Views/Charts/LoopChartView.swift b/Loop/Views/Charts/LoopChartView.swift index be6965c9b5..dd875477aa 100644 --- a/Loop/Views/Charts/LoopChartView.swift +++ b/Loop/Views/Charts/LoopChartView.swift @@ -22,7 +22,7 @@ struct LoopChartView: UIViewRepresentable { let view = ChartContainerView() view.chartGenerator = { [chartManager] frame in - chartManager.chart(atIndex: chartIndex, frame: frame)?.view + chartManager.chart(atIndex: chartIndex, frame: frame) } let gestureRecognizer = UILongPressGestureRecognizer() diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 14efe111bd..d81ccdbba5 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -118,24 +118,14 @@ struct SettingsView: View { .insetGroupedListStyle() .navigationBarTitle(Text(NSLocalizedString("Settings", comment: "Settings screen title"))) .navigationBarItems(trailing: dismissButton) - .actionSheet(item: $actionSheet) { actionSheet in - switch actionSheet { - case .cgmPicker: - return ActionSheet( - title: Text("Add CGM", comment: "The title of the CGM chooser in settings"), - buttons: cgmChoices - ) - case .pumpPicker: - return ActionSheet( - title: Text("Add Pump", comment: "The title of the pump chooser in settings"), - buttons: pumpChoices - ) - case .servicePicker: - return ActionSheet( - title: Text("Add Service", comment: "The title of the add service action sheet in settings"), - buttons: serviceChoices - ) - } + .alert(NSLocalizedString("Add CGM", comment: "The title of the CGM chooser in settings"), isPresented: pickerBinding(.cgmPicker)) { + cgmChoices + } + .alert(NSLocalizedString("Add Pump", comment: "The title of the pump chooser in settings"), isPresented: pickerBinding(.pumpPicker)) { + pumpChoices + } + .alert(NSLocalizedString("Add Service", comment: "The title of the add service action sheet in settings"), isPresented: pickerBinding(.servicePicker)) { + serviceChoices } .alert(item: $alert) { alert in switch alert { @@ -480,14 +470,25 @@ extension SettingsView { } } - private var pumpChoices: [ActionSheet.Button] { - var result = viewModel.pumpManagerSettingsViewModel.availableDevices.map { availableDevice in - ActionSheet.Button.default(Text(availableDevice.localizedTitle)) { + private func pickerBinding(_ destination: Destination.ActionSheet) -> Binding { + Binding( + get: { actionSheet == destination }, + set: { isPresented in + if !isPresented, actionSheet == destination { + actionSheet = nil + } + } + ) + } + + @ViewBuilder + private var pumpChoices: some View { + ForEach(viewModel.pumpManagerSettingsViewModel.availableDevices, id: \.identifier) { availableDevice in + Button(availableDevice.localizedTitle) { self.viewModel.pumpManagerSettingsViewModel.didTapAdd(availableDevice) } } - result.append(.cancel()) - return result + Button(NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet"), role: .cancel) {} } @ViewBuilder @@ -517,16 +518,14 @@ extension SettingsView { } } - private var cgmChoices: [ActionSheet.Button] { - var result = viewModel.cgmManagerSettingsViewModel.availableDevices - .sorted(by: {$0.localizedTitle < $1.localizedTitle}) - .map { availableDevice in - ActionSheet.Button.default(Text(availableDevice.localizedTitle)) { - self.viewModel.cgmManagerSettingsViewModel.didTapAdd(availableDevice) + @ViewBuilder + private var cgmChoices: some View { + ForEach(viewModel.cgmManagerSettingsViewModel.availableDevices.sorted(by: {$0.localizedTitle < $1.localizedTitle}), id: \.identifier) { availableDevice in + Button(availableDevice.localizedTitle) { + self.viewModel.cgmManagerSettingsViewModel.didTapAdd(availableDevice) } } - result.append(.cancel()) - return result + Button(NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet"), role: .cancel) {} } private var servicesSection: some View { @@ -548,14 +547,14 @@ extension SettingsView { } } - private var serviceChoices: [ActionSheet.Button] { - var result = viewModel.servicesViewModel.inactiveServices().map { availableService in - ActionSheet.Button.default(Text(availableService.localizedTitle)) { + @ViewBuilder + private var serviceChoices: some View { + ForEach(viewModel.servicesViewModel.inactiveServices(), id: \.identifier) { availableService in + Button(availableService.localizedTitle) { self.viewModel.servicesViewModel.didTapAddService(availableService) } } - result.append(.cancel()) - return result + Button(NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet"), role: .cancel) {} } private var deleteDataSection: some View { diff --git a/Loop/Views/StatusTableView.swift b/Loop/Views/StatusTableView.swift index 56c1425907..e7adcdbd46 100644 --- a/Loop/Views/StatusTableView.swift +++ b/Loop/Views/StatusTableView.swift @@ -132,136 +132,185 @@ struct StatusTableView: View { } var body: some View { - wrappedView - .ignoresSafeArea(edges: .bottom) - .onChange(of: viewModel.temporaryPresetsManager.activeOverride) { _, _ in - Task { - await viewController.reloadData(animated: true) + ActionTabView { + wrappedView + .ignoresSafeArea(edges: .bottom) + .onChange(of: viewModel.temporaryPresetsManager.activeOverride) { _, _ in + Task { + await viewController.reloadData(animated: true) + } + } + .sheet(item: $viewModel.pendingPreset) { preset in + // This is the active preset; edit disabled + PresetDetentView(preset: preset, roundBasalRate: viewModel.loopDataManager.deliveryDelegate?.roundBasalRate, didTapEdit: { }) + .accessibilityIdentifier("bar_Presets") } + } tabs: { + ActionTab( + title: "Add Carbs", + icon: "carbs", + tintColor: .carbTintColor + ) { + viewController.userTappedAddCarbs() } - .sheet(item: $viewModel.pendingPreset) { preset in - // This is the active preset; edit disabled - PresetDetentView(preset: preset, roundBasalRate: viewModel.loopDataManager.deliveryDelegate?.roundBasalRate, didTapEdit: { }) - .accessibilityIdentifier("bar_Presets") + + ActionTab( + title: "Bolus", + icon: "bolus", + tintColor: .insulinTintColor + ) { + viewController.presentBolusScreen() } - .toolbar { - if !isLandscape { - if #available(iOS 26, *) { - ToolbarItemGroup(placement: .bottomBar) { - carbTab - Spacer() - bolusTab - Spacer() - presetsTab - Spacer() - settingsTab - } - } else { - ToolbarItem(placement: .bottomBar) { - HStack { - carbTab - bolusTab - presetsTab - settingsTab - } - } - } - } + + ActionTab( + title: "Presets", + icon: viewModel.temporaryPresetsManager.activeOverride != nil + ? "presets-selected" + : "presets", + tintColor: .presets + ) { + viewController.presentPresets() + } + + ActionTab( + title: "Settings", + icon: "settings", + tintColor: .secondaryLabel + ) { + viewController.presentSettings() } - .toolbar(isLandscape ? .hidden : .visible, for: .bottomBar) - .toolbarBackground(.visible, for: .bottomBar) + } } - - var carbTab: some View { - Button { - viewController.userTappedAddCarbs() - } label: { - VStack(spacing: 0) { - Image("carbs") - .renderingMode(.template) - .resizable() - .scaledToFit() - .frame(height: 32) - .frame(maxWidth: .infinity) - .foregroundStyle(Color.carbs) +} - Text("Add Carbs") - .font(.footnote) - .foregroundStyle(.secondary) - .fixedSize() - } +struct ActionTab: Identifiable { + let id = UUID() + let title: String + let icon: String + let tintColor: UIColor + let action: () -> Void +} + +struct ActionTabBar: UIViewRepresentable { + + let items: [ActionTab] + + func makeUIView(context: Context) -> UITabBar { + let bar = UITabBar() + bar.delegate = context.coordinator + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + bar.standardAppearance = appearance + bar.scrollEdgeAppearance = appearance + bar.tintColor = .label + return bar + } + + func updateUIView(_ uiView: UITabBar, context: Context) { + context.coordinator.tabs = items + uiView.items = items.enumerated().map { idx, item in + UITabBarItem( + title: item.title, + image: UIImage(named: item.icon)?.scaledToFit(height: 28).withTintColor(item.tintColor, renderingMode: .alwaysOriginal), + tag: idx + ) } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewControllerCarbsButton") } - - var bolusTab: some View { - Button { - viewController.presentBolusScreen() - } label: { - VStack(spacing: 0) { - Image("bolus") - .renderingMode(.template) - .resizable() - .scaledToFit() - .frame(height: 32) - .frame(maxWidth: .infinity) - .foregroundStyle(Color.insulin) - - Text("Bolus") - .font(.footnote) - .foregroundStyle(.secondary) - .fixedSize() + + func makeCoordinator() -> Coordinator { Coordinator() } + + final class Coordinator: NSObject, UITabBarDelegate { + var tabs: [ActionTab] = [] + + func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { + guard tabs.indices.contains(item.tag) else { return } + tabs[item.tag].action() + DispatchQueue.main.async { + tabBar.selectedItem = nil } } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewControllerBolusButton") } +} + +private extension UIImage { + /// Tab icons come from assets of varying raw sizes; normalize them so every + /// tab renders at the same height. + func scaledToFit(height: CGFloat) -> UIImage { + guard size.height > 0, size.height != height else { return self } + let scale = height / size.height + let newSize = CGSize(width: size.width * scale, height: height) + let renderer = UIGraphicsImageRenderer(size: newSize) + return renderer.image { _ in + draw(in: CGRect(origin: .zero, size: newSize)) + } + } +} + +@resultBuilder +enum ActionTabBuilder { + static func buildBlock(_ components: ActionTab...) -> [ActionTab] { components } + static func buildOptional(_ component: [ActionTab]?) -> [ActionTab] { component ?? [] } + static func buildEither(first component: [ActionTab]) -> [ActionTab] { component } + static func buildEither(second component: [ActionTab]) -> [ActionTab] { component } + static func buildArray(_ components: [[ActionTab]]) -> [ActionTab] { components.flatMap { $0 } } +} + +struct LegacyTabBarBackground: ViewModifier { - var presetsTab: some View { - Button { - viewController.presentPresets() - } label: { - VStack(spacing: 0) { - Image(viewModel.temporaryPresetsManager.activeOverride != nil ? "presets-selected" : "presets") - .renderingMode(.template) - .resizable() - .scaledToFit() - .frame(height: 32) - .frame(maxWidth: .infinity) - .foregroundStyle(Color.presets) - .animation(.default, value: viewModel.temporaryPresetsManager.activeOverride) - - Text("Presets") - .font(.footnote) - .foregroundStyle(.secondary) - .fixedSize() - } + private let isCompatibilityModeActive = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false + + func body(content: Content) -> some View { + if #available(iOS 26.0, *), !isCompatibilityModeActive { + content + .frame(height: 0) + } else { + content + .frame(height: 49) + .background( + Color(UIColor.systemBackground) + .ignoresSafeArea(edges: .bottom) + ) } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewPresetsButton") } +} + +struct ActionTabView: View { - var settingsTab: some View { - Button { - viewController.presentSettings() - } label: { - VStack(spacing: 0) { - Image("settings") - .renderingMode(.template) - .resizable() - .scaledToFit() - .frame(height: 32) - .frame(maxWidth: .infinity) - .foregroundStyle(Color.secondary) - - Text("Settings") - .font(.footnote) - .foregroundStyle(.secondary) - .fixedSize() + @State private var orientation: UIDeviceOrientation + + private let allowedOrientations: [UIDeviceOrientation] + private let content: Content + private let tabs: [ActionTab] + + init( + allowedOrientations: [UIDeviceOrientation] = [.portrait], + @ViewBuilder content: @escaping () -> Content, + @ActionTabBuilder tabs: @escaping () -> [ActionTab], + ) { + self.content = content() + self.tabs = tabs() + + self.allowedOrientations = allowedOrientations + self.orientation = UIDevice.current.orientation + } + + var body: some View { + content + .safeAreaInset(edge: .bottom, spacing: 0) { + if orientation == .unknown || allowedOrientations.contains(orientation) { + ActionTabBar(items: tabs) + .modifier(LegacyTabBarBackground()) + } } - } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewControllerSettingsButton") + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + orientation = UIDevice.current.orientation + } + } + + private static func currentOrientation() -> UIInterfaceOrientation { + UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first? + .interfaceOrientation ?? .portrait } } diff --git a/LoopUI/Models/ChartLineModel.swift b/LoopUI/Models/ChartLineModel.swift index 346de2b871..cfb69e0ac4 100644 --- a/LoopUI/Models/ChartLineModel.swift +++ b/LoopUI/Models/ChartLineModel.swift @@ -5,19 +5,6 @@ // Copyright © 2017 LoopKit Authors. All rights reserved. // -import SwiftCharts - - -extension ChartLineModel { - /// Creates a model configured with the dashed prediction line style - /// - /// - Parameters: - /// - points: The points to construct the line - /// - color: The line color - /// - width: The line width - /// - Returns: A new line model - static func predictionLine(points: [T], color: UIColor, width: CGFloat) -> ChartLineModel { - // TODO: Bug in ChartPointsLineLayer requires a non-zero animation to draw the dash pattern - return self.init(chartPoints: points, lineColor: color, lineWidth: width, animDuration: 0.0001, animDelay: 0, dashPattern: [6, 5]) - } -} +// The dashed prediction-line style now lives in LoopKitUI's ChartLineStyle, +// following the migration from the third-party SwiftCharts library to Apple's +// Swift Charts framework. diff --git a/LoopUITests/LoopChartUITests.swift b/LoopUITests/LoopChartUITests.swift new file mode 100644 index 0000000000..e2df3f74b1 --- /dev/null +++ b/LoopUITests/LoopChartUITests.swift @@ -0,0 +1,500 @@ +// +// LoopChartUITests.swift +// LoopUITests +// +// Drives a fresh Loop install into a chart-populated state on the simulator: +// skips onboarding via the debug logo long-press, adds the CGM and pump +// simulators, backfills glucose history, enables closed loop, and logs a carb +// entry and a small bolus so all four home-screen charts have content. +// + +import XCTest + +final class LoopChartUITests: XCTestCase { + + private var app: XCUIApplication! + + override func setUp() { + continueAfterFailure = true + app = XCUIApplication() + } + + func testSetupLoopForCharts() throws { + // Write the scenario before the app's first launch so the scenarios + // manager sees it when it scans its directory during startup. + injectScenarioFile() + + // XCUIApplication.launch() fails against this app's teamless bundle id + // ("com..loopkit.Loop"), so launch it from the home screen instead. + startAppViaSpringboard() + sleep(3) + + skipOnboardingIfNeeded() + allowHealthAccessIfNeeded() + returnToHome() + snap("01-home-initial") + + addCGMSimulatorIfNeeded() + snap("02-after-cgm") + + addPumpSimulatorIfNeeded() + snap("03-after-pump") + + backfillGlucose() + snap("04-after-backfill") + + enableClosedLoop() + snap("05-settings-closed-loop") + + logCarbEntry() + snap("06-after-carbs") + + deliverBolus() + snap("07-after-bolus") + + loadChartDemoScenario() + + sleep(5) + snap("08-final-home") + + visitDetailScreens() + } + + private static let chartDemoScenarioJSON = #"{"glucoseValues": [{"mgdlValue": 133.8, "dateOffset": -21600}, {"mgdlValue": 135.5, "dateOffset": -21300}, {"mgdlValue": 137.6, "dateOffset": -21000}, {"mgdlValue": 139.9, "dateOffset": -20700}, {"mgdlValue": 142.3, "dateOffset": -20400}, {"mgdlValue": 144.8, "dateOffset": -20100}, {"mgdlValue": 146.9, "dateOffset": -19800}, {"mgdlValue": 148.7, "dateOffset": -19500}, {"mgdlValue": 149.8, "dateOffset": -19200}, {"mgdlValue": 150.0, "dateOffset": -18900}, {"mgdlValue": 149.3, "dateOffset": -18600}, {"mgdlValue": 147.5, "dateOffset": -18300}, {"mgdlValue": 144.7, "dateOffset": -18000}, {"mgdlValue": 140.9, "dateOffset": -17700}, {"mgdlValue": 136.4, "dateOffset": -17400}, {"mgdlValue": 131.2, "dateOffset": -17100}, {"mgdlValue": 125.8, "dateOffset": -16800}, {"mgdlValue": 120.3, "dateOffset": -16500}, {"mgdlValue": 115.0, "dateOffset": -16200}, {"mgdlValue": 110.2, "dateOffset": -15900}, {"mgdlValue": 106.0, "dateOffset": -15600}, {"mgdlValue": 102.4, "dateOffset": -15300}, {"mgdlValue": 99.6, "dateOffset": -15000}, {"mgdlValue": 97.5, "dateOffset": -14700}, {"mgdlValue": 96.0, "dateOffset": -14400}, {"mgdlValue": 94.9, "dateOffset": -14100}, {"mgdlValue": 94.0, "dateOffset": -13800}, {"mgdlValue": 93.3, "dateOffset": -13500}, {"mgdlValue": 92.5, "dateOffset": -13200}, {"mgdlValue": 91.5, "dateOffset": -12900}, {"mgdlValue": 90.2, "dateOffset": -12600}, {"mgdlValue": 88.8, "dateOffset": -12300}, {"mgdlValue": 87.2, "dateOffset": -12000}, {"mgdlValue": 85.6, "dateOffset": -11700}, {"mgdlValue": 84.2, "dateOffset": -11400}, {"mgdlValue": 83.1, "dateOffset": -11100}, {"mgdlValue": 82.7, "dateOffset": -10800}, {"mgdlValue": 83.0, "dateOffset": -10500}, {"mgdlValue": 84.2, "dateOffset": -10200}, {"mgdlValue": 86.4, "dateOffset": -9900}, {"mgdlValue": 89.5, "dateOffset": -9600}, {"mgdlValue": 93.6, "dateOffset": -9300}, {"mgdlValue": 98.4, "dateOffset": -9000}, {"mgdlValue": 103.8, "dateOffset": -8700}, {"mgdlValue": 109.5, "dateOffset": -8400}, {"mgdlValue": 115.2, "dateOffset": -8100}, {"mgdlValue": 120.6, "dateOffset": -7800}, {"mgdlValue": 125.6, "dateOffset": -7500}, {"mgdlValue": 130.0, "dateOffset": -7200}, {"mgdlValue": 133.5, "dateOffset": -6900}, {"mgdlValue": 136.2, "dateOffset": -6600}, {"mgdlValue": 138.1, "dateOffset": -6300}, {"mgdlValue": 139.3, "dateOffset": -6000}, {"mgdlValue": 140.0, "dateOffset": -5700}, {"mgdlValue": 140.2, "dateOffset": -5400}, {"mgdlValue": 140.3, "dateOffset": -5100}, {"mgdlValue": 140.4, "dateOffset": -4800}, {"mgdlValue": 140.5, "dateOffset": -4500}, {"mgdlValue": 140.9, "dateOffset": -4200}, {"mgdlValue": 141.4, "dateOffset": -3900}, {"mgdlValue": 142.1, "dateOffset": -3600}, {"mgdlValue": 142.9, "dateOffset": -3300}, {"mgdlValue": 143.6, "dateOffset": -3000}, {"mgdlValue": 143.9, "dateOffset": -2700}, {"mgdlValue": 143.8, "dateOffset": -2400}, {"mgdlValue": 143.0, "dateOffset": -2100}, {"mgdlValue": 141.4, "dateOffset": -1800}, {"mgdlValue": 138.9, "dateOffset": -1500}, {"mgdlValue": 135.5, "dateOffset": -1200}, {"mgdlValue": 131.2, "dateOffset": -900}, {"mgdlValue": 126.3, "dateOffset": -600}, {"mgdlValue": 120.8, "dateOffset": -300}, {"mgdlValue": 115.0, "dateOffset": 0}], "basalDoses": [{"unitsPerHourValue": 0.5, "dateOffset": -21600, "duration": 1800}, {"unitsPerHourValue": 1.2, "dateOffset": -19800, "duration": 1800}, {"unitsPerHourValue": 0.0, "dateOffset": -18000, "duration": 1800}, {"unitsPerHourValue": 2.4, "dateOffset": -16200, "duration": 1800}, {"unitsPerHourValue": 1.8, "dateOffset": -14400, "duration": 1800}, {"unitsPerHourValue": 0.3, "dateOffset": -12600, "duration": 1800}, {"unitsPerHourValue": 1.0, "dateOffset": -10800, "duration": 1800}, {"unitsPerHourValue": 0.05, "dateOffset": -9000, "duration": 1800}, {"unitsPerHourValue": 1.6, "dateOffset": -7200, "duration": 1800}, {"unitsPerHourValue": 0.8, "dateOffset": -5400, "duration": 1800}, {"unitsPerHourValue": 2.0, "dateOffset": -3600, "duration": 1800}, {"unitsPerHourValue": 0.4, "dateOffset": -1800, "duration": 1800}], "bolusDoses": [{"unitsValue": 2.5, "dateOffset": -14400, "deliveryDuration": 100}, {"unitsValue": 1.2, "dateOffset": -7200, "deliveryDuration": 48}, {"unitsValue": 0.6, "dateOffset": -1800, "deliveryDuration": 24}], "carbEntries": [{"gramValue": 45, "dateOffset": -14400, "absorptionTime": 10800}, {"gramValue": 20, "dateOffset": -7200, "absorptionTime": 7200}, {"gramValue": 12, "dateOffset": -1500, "absorptionTime": 10800}]}"# + + /// Writes the chart-demo scenario into the app bundle's Scenarios directory. + /// The runner and app containers are siblings, and simulator processes can + /// write to host paths, so this survives xcodebuild's app reinstall. + private func injectScenarioFile() { + let fm = FileManager.default + let applicationsDir = Bundle.main.bundleURL.deletingLastPathComponent().deletingLastPathComponent() + guard let containers = try? fm.contentsOfDirectory(at: applicationsDir, includingPropertiesForKeys: nil) else { return } + for container in containers { + let loopApp = container.appendingPathComponent("Loop.app") + guard fm.fileExists(atPath: loopApp.path) else { continue } + let scenariosDir = loopApp.appendingPathComponent("Scenarios") + try? fm.createDirectory(at: scenariosDir, withIntermediateDirectories: true) + try? Self.chartDemoScenarioJSON.data(using: .utf8)?.write(to: scenariosDir.appendingPathComponent("chart-demo.json")) + } + } + + /// Opens the debug menu (six quick rotations) and loads the injected chart-demo scenario + private func loadChartDemoScenario() { + + let device = XCUIDevice.shared + for _ in 0..<4 { + device.orientation = .landscapeLeft + usleep(400_000) + device.orientation = .portrait + usleep(400_000) + } + sleep(2) + snap("debug-menu") + if tapAnything(labeled: "Scenarios", timeout: 6) { + sleep(1) + snap("scenario-list") + if tapAnything(labeled: "chart-demo", timeout: 6) { + sleep(1) + _ = tap(app.navigationBars.buttons["Load"], fallback: app.buttons["Load"], timeout: 5) + sleep(8) + } else { + cancelModal() + } + } + + // If the debug sheet is still up (e.g. Scenarios missing), dismiss it + let cancel = app.buttons["Cancel"] + if cancel.exists, cancel.isHittable { + cancel.tap() + sleep(1) + } + } + + private func visitDetailScreens() { + // Glucose chart row -> prediction screen + app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.33)).tap() + sleep(3) + snap("09-prediction") + goBack() + + // Carbs chart row -> carb absorption screen + app.swipeUp() + sleep(1) + app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.8)).tap() + sleep(3) + snap("10-carb-absorption") + goBack() + } + + private func goBack() { + let back = app.navigationBars.buttons.firstMatch + if back.exists, back.isHittable { + back.tap() + sleep(1) + } + } + + // MARK: - Steps + + private func startAppViaSpringboard() { + if app.state == .runningForeground { + return + } + + let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") + springboard.activate() + sleep(2) + + let icon = springboard.icons["Loop"] + if icon.waitForExistence(timeout: 5), icon.isHittable { + icon.tap() + } else { + // The icon may be on another page; try the first page after a swipe + springboard.swipeRight() + sleep(1) + if icon.exists, icon.isHittable { + icon.tap() + } else { + XCTFail("Could not find Loop icon on home screen: \(springboard.debugDescription)") + return + } + } + + _ = app.wait(for: .runningForeground, timeout: 20) + } + + private func skipOnboardingIfNeeded() { + guard app.staticTexts["Welcome to Loop"].waitForExistence(timeout: 8) else { + return + } + + // The logo image is decorative (not in the accessibility tree); long-press by position. + // Attempt a few vertical offsets to be resilient to layout differences. + for dy in [0.37, 0.30, 0.44, 0.24] { + app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: dy)).press(forDuration: 2.8) + sleep(2) + if !app.staticTexts["Welcome to Loop"].exists { + return + } + } + XCTFail("Could not skip onboarding; hierarchy: \(app.debugDescription)") + } + + /// Clears any blocking permission UI: Springboard system alerts (notifications, + /// Bluetooth, etc.) and the in-app HealthKit authorization sheet. + private func allowHealthAccessIfNeeded() { + let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") + + for _ in 0..<5 { + var handledSomething = false + sleep(1) + + // System permission alerts (hosted by Springboard) + let alert = springboard.alerts.firstMatch + if alert.exists { + for label in ["Allow", "Allow While Using App", "OK"] { + let button = alert.buttons[label] + if button.exists { + button.tap() + handledSomething = true + break + } + } + } + + // HealthKit authorization flow. iOS 27 presents multiple pages (grant + // toggles, then historical-data scope); every page has a "Don't Allow". + if app.staticTexts["Full History"].exists { + // Historical-data scope page: select Full History once, then wait + // for Allow to enable. Re-tapping would toggle the selection off. + let allow = app.buttons["Allow"] + if !(allow.exists && allow.isEnabled) { + app.staticTexts["Full History"].tap() + } + for _ in 0..<6 { + if allow.exists, allow.isEnabled { break } + sleep(1) + } + if allow.exists, allow.isEnabled { + allow.tap() + sleep(1) + } + handledSomething = true + } else if app.buttons["Don't Allow"].exists || app.staticTexts["Health Access"].exists { + for choice in [app.buttons["Turn On All"], app.staticTexts["Turn On All"], app.cells.staticTexts["Turn On All"]] where choice.exists && choice.isHittable { + choice.tap() + sleep(1) + handledSomething = true + break + } + for confirm in [app.buttons["Continue"], app.buttons["Allow"]] where confirm.exists && confirm.isHittable && confirm.isEnabled { + confirm.tap() + sleep(1) + handledSomething = true + break + } + } + + if !handledSomething { + return + } + } + } + + private func addCGMSimulatorIfNeeded() { + allowHealthAccessIfNeeded() + guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } + sleep(2) + + // The Add CGM row may be below the fold; scroll the settings list to find it + var opened = false + for _ in 0..<4 { + if tapAnything(labeled: "Add CGM", timeout: 3) { + opened = true + break + } + app.swipeUp() + sleep(1) + } + + if opened { + sleep(1) + snap("cgm-picker") + _ = scrollPopoverAndTap("CGM Simulator") + sleep(2) + allowHealthAccessIfNeeded() + dismissSheets() + } else { + let hierarchy = XCTAttachment(string: app.debugDescription) + hierarchy.name = "cgm-step-hierarchy" + hierarchy.lifetime = .keepAlways + add(hierarchy) + } + closeSettings() + } + + private func addPumpSimulatorIfNeeded() { + allowHealthAccessIfNeeded() + // The Settings screen's "Add Pump" row is the most reliable entry point + guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } + sleep(2) + if tapAnything(labeled: "Add Pump", timeout: 8) { + sleep(1) + snap("pump-action-sheet") + let hierarchy = XCTAttachment(string: app.debugDescription) + hierarchy.name = "pump-popover-hierarchy" + hierarchy.lifetime = .keepAlways + add(hierarchy) + _ = scrollPopoverAndTap("Pump Simulator") + sleep(2) + dismissSheets() + } + closeSettings() + } + + /// Taps an option in the device-picker popover, scrolling its list as needed + @discardableResult + private func scrollPopoverAndTap(_ label: String) -> Bool { + for _ in 0..<8 { + for query in [app.buttons[label], app.cells[label], app.staticTexts[label]] { + let el = query.firstMatch + if el.exists, el.isHittable { + el.tap() + return true + } + } + // Scroll the popover's option list + let popover = app.popovers.firstMatch + if popover.exists { + popover.swipeUp() + } else { + let start = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.17)) + let end = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.06)) + start.press(forDuration: 0.3, thenDragTo: end) + } + usleep(800_000) + } + return false + } + + private func backfillGlucose() { + allowHealthAccessIfNeeded() + // Open Settings -> CGM Simulator -> Backfill Glucose -> Save (3h default) + guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } + sleep(2) + guard tapAnything(labeled: "CGM Simulator", timeout: 8) else { + closeSettings() + return + } + sleep(2) + snap("cgm-settings-sheet") + if tapAnything(labeled: "Backfill Glucose", timeout: 8) { + sleep(1) + _ = tap(app.navigationBars.buttons["Save"], fallback: app.buttons["Save"], timeout: 5) + sleep(2) + } + // Dismiss the CGM settings sheet, then the Settings screen + _ = tap(app.navigationBars.buttons["Done"], fallback: app.buttons["Done"], timeout: 5) + sleep(1) + closeSettings() + } + + private func enableClosedLoop() { + allowHealthAccessIfNeeded() + guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } + sleep(2) + let toggle = app.switches["Closed Loop"].firstMatch + if toggle.waitForExistence(timeout: 5), (toggle.value as? String) == "0" { + toggle.tap() + sleep(1) + // Confirm any alert about enabling closed loop + for confirm in [app.alerts.buttons["Yes"], app.alerts.buttons["Continue"], app.alerts.buttons["OK"]] where confirm.exists { + confirm.tap() + break + } + } + closeSettings() + } + + private func logCarbEntry() { + allowHealthAccessIfNeeded() + guard tap(app.toolbars.buttons["Add Meal"], fallback: app.buttons["Add Meal"], timeout: 8) else { return } + sleep(2) + // The carb amount field: type a value + let field = app.textFields.firstMatch + if field.waitForExistence(timeout: 5) { + field.tap() + field.typeText("15") + } + snap("carb-entry") + if !tap(app.buttons["Continue"], fallback: app.staticTexts["Continue"], timeout: 5) { + cancelModal() + return + } + sleep(3) + // On the meal bolus screen, save without bolusing if possible + for label in ["Save without Bolusing", "Save and Deliver", "Save Carb Entry", "Save"] { + if tapAnything(labeled: label, timeout: 3) { + sleep(3) + return + } + } + cancelModal() + } + + private func deliverBolus() { + allowHealthAccessIfNeeded() + guard tap(app.toolbars.buttons["Bolus"], fallback: app.buttons["Bolus"], timeout: 8) else { return } + sleep(2) + let field = app.textFields.firstMatch + if field.waitForExistence(timeout: 5) { + // Retap until the keyboard actually appears; typing without focus throws + for _ in 0..<4 { + field.tap() + sleep(1) + if app.keyboards.count > 0 { break } + } + if app.keyboards.count > 0 { + field.typeText("0.5") + } + } + snap("bolus-entry") + for label in ["Deliver", "Save and Deliver"] { + if tapAnything(labeled: label, timeout: 3) { + sleep(4) + return + } + } + cancelModal() + } + + // MARK: - Helpers + + private func snap(_ name: String) { + let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot()) + attachment.name = name + attachment.lifetime = .keepAlways + add(attachment) + } + + @discardableResult + private func tap(_ primary: XCUIElement, fallback: XCUIElement? = nil, timeout: TimeInterval) -> Bool { + if primary.waitForExistence(timeout: timeout), primary.isHittable { + primary.tap() + return true + } + if let fallback = fallback, fallback.exists, fallback.isHittable { + fallback.tap() + return true + } + return false + } + + /// Taps the first hittable button, cell, static text, or other element with the given label + @discardableResult + private func tapAnything(labeled label: String, timeout: TimeInterval) -> Bool { + let deadline = Date(timeIntervalSinceNow: timeout) + repeat { + for query in [app.buttons, app.cells, app.staticTexts, app.otherElements] { + let el = query[label].firstMatch + if el.exists, el.isHittable { + el.tap() + return true + } + } + usleep(500_000) + } while Date() < deadline + return false + } + + private func dismissSheets() { + for _ in 0..<2 { + let done = app.navigationBars.buttons["Done"] + if done.exists, done.isHittable { + done.tap() + sleep(1) + } + } + } + + private func closeSettings() { + for label in ["Done", "Close"] { + for query in [app.navigationBars.buttons[label], app.buttons[label]] { + if query.exists, query.isHittable { + query.tap() + sleep(1) + return + } + } + } + // Fall back to tapping where the SwiftUI Settings sheet's Done pill sits + if app.staticTexts["Settings"].exists { + app.coordinate(withNormalizedOffset: CGVector(dx: 0.87, dy: 0.115)).tap() + sleep(1) + } + } + + /// Dismisses a leftover Settings sheet from a previous partial run + private func returnToHome() { + for _ in 0..<3 { + let done = app.buttons["Done"] + if app.staticTexts["Settings"].exists, done.exists, done.isHittable { + done.tap() + sleep(1) + continue + } + break + } + } + + private func cancelModal() { + for label in ["Cancel", "Close"] { + let button = app.navigationBars.buttons[label] + if button.exists, button.isHittable { + button.tap() + sleep(1) + return + } + } + } +} From 843796ce41ba0765f9ce4356aa7a0f463bc6ab87 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Sun, 19 Jul 2026 00:58:15 -0700 Subject: [PATCH 2/5] xcode 27 --- Loop/View Controllers/StatusTableViewController.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 736198bdf2..d8ed865e4a 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1786,7 +1786,6 @@ final class StatusTableViewController: LoopChartsTableViewController { let alert = UIAlertController(availablePumpManagers: availablePumpManagers) { [weak self] (identifier) in self?.addPumpManager(withIdentifier: identifier) } - alert.addCancelAction { _ in } present(alert, animated: true, completion: nil) } } @@ -1803,7 +1802,6 @@ final class StatusTableViewController: LoopChartsTableViewController { let alert = UIAlertController(availableCGMManagers: availableCGMManagers) { [weak self] identifier in self?.addCGMManager(withIdentifier: identifier) } - alert.addCancelAction { _ in } present(alert, animated: true, completion: nil) } } From 28889684cebca38239eeb640555dc27cfeaeec6d Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Sun, 19 Jul 2026 12:27:16 -0700 Subject: [PATCH 3/5] Delete LoopUI/Models/ChartLineModel.swift --- LoopUI/Models/ChartLineModel.swift | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 LoopUI/Models/ChartLineModel.swift diff --git a/LoopUI/Models/ChartLineModel.swift b/LoopUI/Models/ChartLineModel.swift deleted file mode 100644 index cfb69e0ac4..0000000000 --- a/LoopUI/Models/ChartLineModel.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// ChartLineModel.swift -// Loop -// -// Copyright © 2017 LoopKit Authors. All rights reserved. -// - -// The dashed prediction-line style now lives in LoopKitUI's ChartLineStyle, -// following the migration from the third-party SwiftCharts library to Apple's -// Swift Charts framework. From 7afdc5d45dee712e005a5aba66d68fdd4a2bed30 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Sun, 19 Jul 2026 12:29:53 -0700 Subject: [PATCH 4/5] xcode 27 --- Loop.xcodeproj/project.pbxproj | 22 -- Loop/Localizable.xcstrings | 138 +++++++- LoopUITests/LoopChartUITests.swift | 500 ----------------------------- 3 files changed, 127 insertions(+), 533 deletions(-) delete mode 100644 LoopUITests/LoopChartUITests.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 4ffe6b482f..cb819db00a 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - CAFE10000000000000000009 /* LoopChartUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE10000000000000000008 /* LoopChartUITests.swift */; }; 1419606928D9554E00BA86E0 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 437AFEE6203688CF008C4892 /* LoopKitUI.framework */; }; 1419606A28D955BC00BA86E0 /* MockKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C101947127DD473C004E7EB8 /* MockKitUI.framework */; }; 142CB7592A60BF2E0075748A /* EditMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142CB7582A60BF2E0075748A /* EditMode.swift */; }; @@ -650,13 +649,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - CAFE1000000000000000000B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 43776F841B8022E90074EA36 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43776F8B1B8022E90074EA36; - remoteInfo = Loop; - }; 1481F9BD28DA26F4004C5AEB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 43776F841B8022E90074EA36 /* Project object */; @@ -789,8 +781,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - CAFE10000000000000000002 /* LoopUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LoopUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - CAFE10000000000000000008 /* LoopChartUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopChartUITests.swift; sourceTree = ""; }; 142CB7582A60BF2E0075748A /* EditMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMode.swift; sourceTree = ""; }; 142CB75A2A60BFC30075748A /* FavoriteFoodsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteFoodsView.swift; sourceTree = ""; }; 1452F4A82A851C9400F8B9E4 /* FavoriteFoodAddEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteFoodAddEditViewModel.swift; sourceTree = ""; }; @@ -1461,13 +1451,6 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - CAFE10000000000000000007 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 14B1735928AED9EC006CCD7C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -3915,11 +3898,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - CAFE1000000000000000000A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43776F8B1B8022E90074EA36 /* Loop */; - targetProxy = CAFE1000000000000000000B /* PBXContainerItemProxy */; - }; 1481F9BE28DA26F4004C5AEB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4F75288A1DFE1DC600C322D6 /* LoopUI */; diff --git a/Loop/Localizable.xcstrings b/Loop/Localizable.xcstrings index 2b0f3d58b4..713c6d896b 100644 --- a/Loop/Localizable.xcstrings +++ b/Loop/Localizable.xcstrings @@ -3277,10 +3277,6 @@ "comment" : "A label displaying the playback speed. The argument is the playback speed.", "isCommentAutoGenerated" : true }, - "%lld mg/dL" : { - "comment" : "A label displaying the current glucose level, formatted as a monospaced digit with a secondary foreground color.", - "isCommentAutoGenerated" : true - }, "%lld min" : { "comment" : "A picker row that lets the user select a snooze interval. The argument is the number of minutes in the interval.", "isCommentAutoGenerated" : true @@ -3288,6 +3284,12 @@ "%lld." : { "comment" : "A number followed by a period, used to number a list of references.", "isCommentAutoGenerated" : true + }, + "%lld%%" : { + + }, + "%lldd" : { + }, "%lldx" : { "comment" : "A label that shows the playback speed of the audio. The argument is the playback speed.", @@ -4438,6 +4440,9 @@ } } }, + "Above range (>180): %d%% · goal <25%%" : { + "comment" : "TIR above-range summary" + }, "Absorption Time" : { "comment" : "Label for food absorption entry on add favorite food screen", "localizations" : { @@ -5443,9 +5448,6 @@ } } } - }, - "Add Carbs" : { - }, "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", @@ -6067,6 +6069,9 @@ }, "Afternoon Exercise" : { + }, + "Alert Identifier" : { + }, "Alert Management" : { "comment" : "Alert Permissions button text\nTitle of alert management screen", @@ -8856,6 +8861,9 @@ "Automation was unsuccessful" : { "comment" : "title for when automation was unsuccessful" }, + "Average" : { + "comment" : "Average glucose metric title" + }, "Average Glucose" : { }, @@ -9216,6 +9224,9 @@ }, "Before" : { + }, + "Below range (<70): %d%% · goal <4%%" : { + "comment" : "TIR below-range summary" }, "Bluetooth\nOff" : { "comment" : "Message to the user to that the bluetooth is off", @@ -11640,6 +11651,9 @@ }, "CAUTION - Investigational device. Limited by Federal (or United States) law to investigational use." : { + }, + "CGM Active" : { + "comment" : "Percent of time CGM data present metric title" }, "Change the pump battery immediately" : { "comment" : "The notification alert describing a low pump battery", @@ -15000,6 +15014,12 @@ } } }, + "Daily glucose pattern" : { + "comment" : "Accessibility label for the daily glucose pattern (AGP) chart" + }, + "Daily Glucose Pattern" : { + "comment" : "Daily glucose pattern (AGP) section header" + }, "Date" : { "comment" : "Date picker label", "localizations" : { @@ -15256,6 +15276,15 @@ }, "Decreased Delivery" : { + }, + "Delay 1st Alert" : { + + }, + "Delay for" : { + + }, + "Delay the first alert until your reading stays out of range for this long." : { + }, "Delete" : { "localizations" : { @@ -17355,6 +17384,12 @@ }, "During this kind of hard exercise, your body may release hormones that raise glucose. This is more common in the morning before eating." : { + }, + "Each 1% time in range ≈ 15 minutes." : { + "comment" : "TIR helper" + }, + "Each 5% increase in range is clinically beneficial." : { + "comment" : "TIR suggestion" }, "Easy breath. Can carry on a conversation." : { @@ -21168,6 +21203,9 @@ } } }, + "Glucose Metrics" : { + "comment" : "Glucose metrics section header" + }, "Glucose Momentum" : { "comment" : "Title of the prediction input effect for glucose momentum", "localizations" : { @@ -21287,6 +21325,9 @@ } } }, + "Glucose overview and ambulatory glucose profile" : { + "comment" : "Statistics settings descriptive text" + }, "Glucose Summary" : { }, @@ -21364,9 +21405,30 @@ "Glucose, Insulin, and Carb Data Access" : { "comment" : "Apple Health settings descriptive text" }, + "GMI" : { + "comment" : "Glucose Management Indicator metric title" + }, "Go to Settings > Focus." : { "comment" : "Focus modes step 1" }, + "Goal: <%@" : { + "comment" : "Average glucose goal" + }, + "Goal: <1%" : { + "comment" : "TIR goal for very low" + }, + "Goal: <5%" : { + "comment" : "TIR goal for very high" + }, + "Goal: <7%" : { + "comment" : "GMI goal" + }, + "Goal: >70%" : { + "comment" : "CGM active goal\nTIR goal for target range" + }, + "Goal: ≤36%" : { + "comment" : "Glucose variability goal" + }, "grams" : { }, @@ -21430,7 +21492,7 @@ }, "High" : { - + "comment" : "Time-in-range band label (181–250 mg/dL)" }, "High Glucose" : { "localizations" : { @@ -21988,6 +22050,9 @@ } } }, + "In Range" : { + "comment" : "Time-in-range band label (70–180 mg/dL)" + }, "Includes basal and automated boluses" : { }, @@ -26000,7 +26065,7 @@ } }, "Low" : { - + "comment" : "Time-in-range band label (54–69 mg/dL)" }, "Low Glucose" : { "comment" : "Title for bolus screen warning when glucose is below glucose warning limit.\nTitle for bolus screen warning when glucose is below suspend threshold, but a bolus is recommended", @@ -26177,6 +26242,9 @@ } } } + }, + "Manager Identifier" : { + }, "Managing Alerts" : { "comment" : "View title for how mute alerts work", @@ -26709,6 +26777,9 @@ }, "Meal Timing" : { + }, + "Median (line) with 25–75% and 5–95% bands, by time of day." : { + "comment" : "AGP chart explanation" }, "Medium Intensity (Aerobic)" : { @@ -28423,6 +28494,9 @@ }, "No Delivery" : { + }, + "No glucose data for this period." : { + "comment" : "Statistics empty state" }, "No Maximum Bolus Configured" : { "comment" : "Alert title for a missing maximum bolus setting error", @@ -29119,6 +29193,9 @@ }, "None in last 24 hours" : { + }, + "Not enough data to plot a profile." : { + "comment" : "AGP empty state" }, "Not Set" : { "comment" : "Health sharing status: not determined" @@ -32888,6 +32965,9 @@ } } }, + "Range" : { + "comment" : "Statistics date-range picker label" + }, "Rapid-Acting – Adults" : { "comment" : "Title of insulin model preset", "extractionState" : "manual", @@ -34061,6 +34141,15 @@ } } } + }, + "Reset Alert Settings" : { + + }, + "Reset all glucose alert settings to defaults?" : { + + }, + "Restores all glucose alert thresholds, schedules, and sounds to their defaults, and removes any extra profiles." : { + }, "Resume insulin if you wish for the app to restart insulin delivery.\n\nIf you wish for the app to automate your insulin, go to Settings and toggle Closed Loop to on." : { "comment" : "message when automation is off and insulin delivery is suspended" @@ -35103,6 +35192,9 @@ }, "She can do this by raising her **Overall Insulin** setting. This tells %@ to deliver more than her usual amount, making her insulin settings stronger." : { + }, + "Showing %1$d days of data — not enough for the full %2$d-day period yet." : { + "comment" : "Notice when the selected statistics range has incomplete data" }, "Shows last loop error" : { "comment" : "Loop Completion HUD accessibility hint", @@ -35759,6 +35851,9 @@ }, "Sort By" : { + }, + "Sound" : { + }, "Stable Glucose" : { @@ -36095,9 +36190,18 @@ }, "Starting your exercise with high active insulin" : { + }, + "Statistics" : { + "comment" : "Statistics screen title\nStatistics settings button text" }, "Stay hydrated" : { + }, + "Stop" : { + "comment" : "Stop button on the AlarmKit critical alarm" + }, + "Stop Glucose Alarm" : { + }, "Stop Time" : { "comment" : "A label displayed below the \"Stop Time\" text in the \"Scheduling\" section of an alert profile detail view.", @@ -38212,6 +38316,9 @@ } } } + }, + "The sound played when this alarm fires." : { + }, "Therapy Settings" : { "comment" : "Title text for button to Therapy Settings", @@ -38313,6 +38420,9 @@ }, "These recommendations should be used as a starting point. Checking your glucose during exercise will help you find the settings that work best for you." : { + }, + "This device keeps %d days of glucose. Increase the cache duration and rebuild to review the full period." : { + "comment" : "Notice when the local cache limits the statistics history" }, "This dose still has active insulin. Deleting it may cause Loop to make up for the reduced active insulin by dosing more." : { "comment" : "Warning when deleting a Loop-recorded dose that still has active insulin" @@ -38401,6 +38511,9 @@ }, "Time in\nAutomation" : { + }, + "Time in Range" : { + "comment" : "Time-in-range section header" }, "Time of Day" : { @@ -40867,6 +40980,9 @@ } } }, + "Variability" : { + "comment" : "Coefficient of variation metric title" + }, "Version" : { "comment" : "Label for the app version row in the Support section" }, @@ -40874,10 +40990,10 @@ }, "Very High" : { - + "comment" : "Time-in-range band label (>250 mg/dL)" }, "Very Low" : { - + "comment" : "Time-in-range band label (<54 mg/dL)" }, "Viewing entry %lld of %lld" : { "localizations" : { diff --git a/LoopUITests/LoopChartUITests.swift b/LoopUITests/LoopChartUITests.swift deleted file mode 100644 index e2df3f74b1..0000000000 --- a/LoopUITests/LoopChartUITests.swift +++ /dev/null @@ -1,500 +0,0 @@ -// -// LoopChartUITests.swift -// LoopUITests -// -// Drives a fresh Loop install into a chart-populated state on the simulator: -// skips onboarding via the debug logo long-press, adds the CGM and pump -// simulators, backfills glucose history, enables closed loop, and logs a carb -// entry and a small bolus so all four home-screen charts have content. -// - -import XCTest - -final class LoopChartUITests: XCTestCase { - - private var app: XCUIApplication! - - override func setUp() { - continueAfterFailure = true - app = XCUIApplication() - } - - func testSetupLoopForCharts() throws { - // Write the scenario before the app's first launch so the scenarios - // manager sees it when it scans its directory during startup. - injectScenarioFile() - - // XCUIApplication.launch() fails against this app's teamless bundle id - // ("com..loopkit.Loop"), so launch it from the home screen instead. - startAppViaSpringboard() - sleep(3) - - skipOnboardingIfNeeded() - allowHealthAccessIfNeeded() - returnToHome() - snap("01-home-initial") - - addCGMSimulatorIfNeeded() - snap("02-after-cgm") - - addPumpSimulatorIfNeeded() - snap("03-after-pump") - - backfillGlucose() - snap("04-after-backfill") - - enableClosedLoop() - snap("05-settings-closed-loop") - - logCarbEntry() - snap("06-after-carbs") - - deliverBolus() - snap("07-after-bolus") - - loadChartDemoScenario() - - sleep(5) - snap("08-final-home") - - visitDetailScreens() - } - - private static let chartDemoScenarioJSON = #"{"glucoseValues": [{"mgdlValue": 133.8, "dateOffset": -21600}, {"mgdlValue": 135.5, "dateOffset": -21300}, {"mgdlValue": 137.6, "dateOffset": -21000}, {"mgdlValue": 139.9, "dateOffset": -20700}, {"mgdlValue": 142.3, "dateOffset": -20400}, {"mgdlValue": 144.8, "dateOffset": -20100}, {"mgdlValue": 146.9, "dateOffset": -19800}, {"mgdlValue": 148.7, "dateOffset": -19500}, {"mgdlValue": 149.8, "dateOffset": -19200}, {"mgdlValue": 150.0, "dateOffset": -18900}, {"mgdlValue": 149.3, "dateOffset": -18600}, {"mgdlValue": 147.5, "dateOffset": -18300}, {"mgdlValue": 144.7, "dateOffset": -18000}, {"mgdlValue": 140.9, "dateOffset": -17700}, {"mgdlValue": 136.4, "dateOffset": -17400}, {"mgdlValue": 131.2, "dateOffset": -17100}, {"mgdlValue": 125.8, "dateOffset": -16800}, {"mgdlValue": 120.3, "dateOffset": -16500}, {"mgdlValue": 115.0, "dateOffset": -16200}, {"mgdlValue": 110.2, "dateOffset": -15900}, {"mgdlValue": 106.0, "dateOffset": -15600}, {"mgdlValue": 102.4, "dateOffset": -15300}, {"mgdlValue": 99.6, "dateOffset": -15000}, {"mgdlValue": 97.5, "dateOffset": -14700}, {"mgdlValue": 96.0, "dateOffset": -14400}, {"mgdlValue": 94.9, "dateOffset": -14100}, {"mgdlValue": 94.0, "dateOffset": -13800}, {"mgdlValue": 93.3, "dateOffset": -13500}, {"mgdlValue": 92.5, "dateOffset": -13200}, {"mgdlValue": 91.5, "dateOffset": -12900}, {"mgdlValue": 90.2, "dateOffset": -12600}, {"mgdlValue": 88.8, "dateOffset": -12300}, {"mgdlValue": 87.2, "dateOffset": -12000}, {"mgdlValue": 85.6, "dateOffset": -11700}, {"mgdlValue": 84.2, "dateOffset": -11400}, {"mgdlValue": 83.1, "dateOffset": -11100}, {"mgdlValue": 82.7, "dateOffset": -10800}, {"mgdlValue": 83.0, "dateOffset": -10500}, {"mgdlValue": 84.2, "dateOffset": -10200}, {"mgdlValue": 86.4, "dateOffset": -9900}, {"mgdlValue": 89.5, "dateOffset": -9600}, {"mgdlValue": 93.6, "dateOffset": -9300}, {"mgdlValue": 98.4, "dateOffset": -9000}, {"mgdlValue": 103.8, "dateOffset": -8700}, {"mgdlValue": 109.5, "dateOffset": -8400}, {"mgdlValue": 115.2, "dateOffset": -8100}, {"mgdlValue": 120.6, "dateOffset": -7800}, {"mgdlValue": 125.6, "dateOffset": -7500}, {"mgdlValue": 130.0, "dateOffset": -7200}, {"mgdlValue": 133.5, "dateOffset": -6900}, {"mgdlValue": 136.2, "dateOffset": -6600}, {"mgdlValue": 138.1, "dateOffset": -6300}, {"mgdlValue": 139.3, "dateOffset": -6000}, {"mgdlValue": 140.0, "dateOffset": -5700}, {"mgdlValue": 140.2, "dateOffset": -5400}, {"mgdlValue": 140.3, "dateOffset": -5100}, {"mgdlValue": 140.4, "dateOffset": -4800}, {"mgdlValue": 140.5, "dateOffset": -4500}, {"mgdlValue": 140.9, "dateOffset": -4200}, {"mgdlValue": 141.4, "dateOffset": -3900}, {"mgdlValue": 142.1, "dateOffset": -3600}, {"mgdlValue": 142.9, "dateOffset": -3300}, {"mgdlValue": 143.6, "dateOffset": -3000}, {"mgdlValue": 143.9, "dateOffset": -2700}, {"mgdlValue": 143.8, "dateOffset": -2400}, {"mgdlValue": 143.0, "dateOffset": -2100}, {"mgdlValue": 141.4, "dateOffset": -1800}, {"mgdlValue": 138.9, "dateOffset": -1500}, {"mgdlValue": 135.5, "dateOffset": -1200}, {"mgdlValue": 131.2, "dateOffset": -900}, {"mgdlValue": 126.3, "dateOffset": -600}, {"mgdlValue": 120.8, "dateOffset": -300}, {"mgdlValue": 115.0, "dateOffset": 0}], "basalDoses": [{"unitsPerHourValue": 0.5, "dateOffset": -21600, "duration": 1800}, {"unitsPerHourValue": 1.2, "dateOffset": -19800, "duration": 1800}, {"unitsPerHourValue": 0.0, "dateOffset": -18000, "duration": 1800}, {"unitsPerHourValue": 2.4, "dateOffset": -16200, "duration": 1800}, {"unitsPerHourValue": 1.8, "dateOffset": -14400, "duration": 1800}, {"unitsPerHourValue": 0.3, "dateOffset": -12600, "duration": 1800}, {"unitsPerHourValue": 1.0, "dateOffset": -10800, "duration": 1800}, {"unitsPerHourValue": 0.05, "dateOffset": -9000, "duration": 1800}, {"unitsPerHourValue": 1.6, "dateOffset": -7200, "duration": 1800}, {"unitsPerHourValue": 0.8, "dateOffset": -5400, "duration": 1800}, {"unitsPerHourValue": 2.0, "dateOffset": -3600, "duration": 1800}, {"unitsPerHourValue": 0.4, "dateOffset": -1800, "duration": 1800}], "bolusDoses": [{"unitsValue": 2.5, "dateOffset": -14400, "deliveryDuration": 100}, {"unitsValue": 1.2, "dateOffset": -7200, "deliveryDuration": 48}, {"unitsValue": 0.6, "dateOffset": -1800, "deliveryDuration": 24}], "carbEntries": [{"gramValue": 45, "dateOffset": -14400, "absorptionTime": 10800}, {"gramValue": 20, "dateOffset": -7200, "absorptionTime": 7200}, {"gramValue": 12, "dateOffset": -1500, "absorptionTime": 10800}]}"# - - /// Writes the chart-demo scenario into the app bundle's Scenarios directory. - /// The runner and app containers are siblings, and simulator processes can - /// write to host paths, so this survives xcodebuild's app reinstall. - private func injectScenarioFile() { - let fm = FileManager.default - let applicationsDir = Bundle.main.bundleURL.deletingLastPathComponent().deletingLastPathComponent() - guard let containers = try? fm.contentsOfDirectory(at: applicationsDir, includingPropertiesForKeys: nil) else { return } - for container in containers { - let loopApp = container.appendingPathComponent("Loop.app") - guard fm.fileExists(atPath: loopApp.path) else { continue } - let scenariosDir = loopApp.appendingPathComponent("Scenarios") - try? fm.createDirectory(at: scenariosDir, withIntermediateDirectories: true) - try? Self.chartDemoScenarioJSON.data(using: .utf8)?.write(to: scenariosDir.appendingPathComponent("chart-demo.json")) - } - } - - /// Opens the debug menu (six quick rotations) and loads the injected chart-demo scenario - private func loadChartDemoScenario() { - - let device = XCUIDevice.shared - for _ in 0..<4 { - device.orientation = .landscapeLeft - usleep(400_000) - device.orientation = .portrait - usleep(400_000) - } - sleep(2) - snap("debug-menu") - if tapAnything(labeled: "Scenarios", timeout: 6) { - sleep(1) - snap("scenario-list") - if tapAnything(labeled: "chart-demo", timeout: 6) { - sleep(1) - _ = tap(app.navigationBars.buttons["Load"], fallback: app.buttons["Load"], timeout: 5) - sleep(8) - } else { - cancelModal() - } - } - - // If the debug sheet is still up (e.g. Scenarios missing), dismiss it - let cancel = app.buttons["Cancel"] - if cancel.exists, cancel.isHittable { - cancel.tap() - sleep(1) - } - } - - private func visitDetailScreens() { - // Glucose chart row -> prediction screen - app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.33)).tap() - sleep(3) - snap("09-prediction") - goBack() - - // Carbs chart row -> carb absorption screen - app.swipeUp() - sleep(1) - app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.8)).tap() - sleep(3) - snap("10-carb-absorption") - goBack() - } - - private func goBack() { - let back = app.navigationBars.buttons.firstMatch - if back.exists, back.isHittable { - back.tap() - sleep(1) - } - } - - // MARK: - Steps - - private func startAppViaSpringboard() { - if app.state == .runningForeground { - return - } - - let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - springboard.activate() - sleep(2) - - let icon = springboard.icons["Loop"] - if icon.waitForExistence(timeout: 5), icon.isHittable { - icon.tap() - } else { - // The icon may be on another page; try the first page after a swipe - springboard.swipeRight() - sleep(1) - if icon.exists, icon.isHittable { - icon.tap() - } else { - XCTFail("Could not find Loop icon on home screen: \(springboard.debugDescription)") - return - } - } - - _ = app.wait(for: .runningForeground, timeout: 20) - } - - private func skipOnboardingIfNeeded() { - guard app.staticTexts["Welcome to Loop"].waitForExistence(timeout: 8) else { - return - } - - // The logo image is decorative (not in the accessibility tree); long-press by position. - // Attempt a few vertical offsets to be resilient to layout differences. - for dy in [0.37, 0.30, 0.44, 0.24] { - app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: dy)).press(forDuration: 2.8) - sleep(2) - if !app.staticTexts["Welcome to Loop"].exists { - return - } - } - XCTFail("Could not skip onboarding; hierarchy: \(app.debugDescription)") - } - - /// Clears any blocking permission UI: Springboard system alerts (notifications, - /// Bluetooth, etc.) and the in-app HealthKit authorization sheet. - private func allowHealthAccessIfNeeded() { - let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") - - for _ in 0..<5 { - var handledSomething = false - sleep(1) - - // System permission alerts (hosted by Springboard) - let alert = springboard.alerts.firstMatch - if alert.exists { - for label in ["Allow", "Allow While Using App", "OK"] { - let button = alert.buttons[label] - if button.exists { - button.tap() - handledSomething = true - break - } - } - } - - // HealthKit authorization flow. iOS 27 presents multiple pages (grant - // toggles, then historical-data scope); every page has a "Don't Allow". - if app.staticTexts["Full History"].exists { - // Historical-data scope page: select Full History once, then wait - // for Allow to enable. Re-tapping would toggle the selection off. - let allow = app.buttons["Allow"] - if !(allow.exists && allow.isEnabled) { - app.staticTexts["Full History"].tap() - } - for _ in 0..<6 { - if allow.exists, allow.isEnabled { break } - sleep(1) - } - if allow.exists, allow.isEnabled { - allow.tap() - sleep(1) - } - handledSomething = true - } else if app.buttons["Don't Allow"].exists || app.staticTexts["Health Access"].exists { - for choice in [app.buttons["Turn On All"], app.staticTexts["Turn On All"], app.cells.staticTexts["Turn On All"]] where choice.exists && choice.isHittable { - choice.tap() - sleep(1) - handledSomething = true - break - } - for confirm in [app.buttons["Continue"], app.buttons["Allow"]] where confirm.exists && confirm.isHittable && confirm.isEnabled { - confirm.tap() - sleep(1) - handledSomething = true - break - } - } - - if !handledSomething { - return - } - } - } - - private func addCGMSimulatorIfNeeded() { - allowHealthAccessIfNeeded() - guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } - sleep(2) - - // The Add CGM row may be below the fold; scroll the settings list to find it - var opened = false - for _ in 0..<4 { - if tapAnything(labeled: "Add CGM", timeout: 3) { - opened = true - break - } - app.swipeUp() - sleep(1) - } - - if opened { - sleep(1) - snap("cgm-picker") - _ = scrollPopoverAndTap("CGM Simulator") - sleep(2) - allowHealthAccessIfNeeded() - dismissSheets() - } else { - let hierarchy = XCTAttachment(string: app.debugDescription) - hierarchy.name = "cgm-step-hierarchy" - hierarchy.lifetime = .keepAlways - add(hierarchy) - } - closeSettings() - } - - private func addPumpSimulatorIfNeeded() { - allowHealthAccessIfNeeded() - // The Settings screen's "Add Pump" row is the most reliable entry point - guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } - sleep(2) - if tapAnything(labeled: "Add Pump", timeout: 8) { - sleep(1) - snap("pump-action-sheet") - let hierarchy = XCTAttachment(string: app.debugDescription) - hierarchy.name = "pump-popover-hierarchy" - hierarchy.lifetime = .keepAlways - add(hierarchy) - _ = scrollPopoverAndTap("Pump Simulator") - sleep(2) - dismissSheets() - } - closeSettings() - } - - /// Taps an option in the device-picker popover, scrolling its list as needed - @discardableResult - private func scrollPopoverAndTap(_ label: String) -> Bool { - for _ in 0..<8 { - for query in [app.buttons[label], app.cells[label], app.staticTexts[label]] { - let el = query.firstMatch - if el.exists, el.isHittable { - el.tap() - return true - } - } - // Scroll the popover's option list - let popover = app.popovers.firstMatch - if popover.exists { - popover.swipeUp() - } else { - let start = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.17)) - let end = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.06)) - start.press(forDuration: 0.3, thenDragTo: end) - } - usleep(800_000) - } - return false - } - - private func backfillGlucose() { - allowHealthAccessIfNeeded() - // Open Settings -> CGM Simulator -> Backfill Glucose -> Save (3h default) - guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } - sleep(2) - guard tapAnything(labeled: "CGM Simulator", timeout: 8) else { - closeSettings() - return - } - sleep(2) - snap("cgm-settings-sheet") - if tapAnything(labeled: "Backfill Glucose", timeout: 8) { - sleep(1) - _ = tap(app.navigationBars.buttons["Save"], fallback: app.buttons["Save"], timeout: 5) - sleep(2) - } - // Dismiss the CGM settings sheet, then the Settings screen - _ = tap(app.navigationBars.buttons["Done"], fallback: app.buttons["Done"], timeout: 5) - sleep(1) - closeSettings() - } - - private func enableClosedLoop() { - allowHealthAccessIfNeeded() - guard tap(app.toolbars.buttons["Settings"], fallback: app.buttons["Settings"], timeout: 8) else { return } - sleep(2) - let toggle = app.switches["Closed Loop"].firstMatch - if toggle.waitForExistence(timeout: 5), (toggle.value as? String) == "0" { - toggle.tap() - sleep(1) - // Confirm any alert about enabling closed loop - for confirm in [app.alerts.buttons["Yes"], app.alerts.buttons["Continue"], app.alerts.buttons["OK"]] where confirm.exists { - confirm.tap() - break - } - } - closeSettings() - } - - private func logCarbEntry() { - allowHealthAccessIfNeeded() - guard tap(app.toolbars.buttons["Add Meal"], fallback: app.buttons["Add Meal"], timeout: 8) else { return } - sleep(2) - // The carb amount field: type a value - let field = app.textFields.firstMatch - if field.waitForExistence(timeout: 5) { - field.tap() - field.typeText("15") - } - snap("carb-entry") - if !tap(app.buttons["Continue"], fallback: app.staticTexts["Continue"], timeout: 5) { - cancelModal() - return - } - sleep(3) - // On the meal bolus screen, save without bolusing if possible - for label in ["Save without Bolusing", "Save and Deliver", "Save Carb Entry", "Save"] { - if tapAnything(labeled: label, timeout: 3) { - sleep(3) - return - } - } - cancelModal() - } - - private func deliverBolus() { - allowHealthAccessIfNeeded() - guard tap(app.toolbars.buttons["Bolus"], fallback: app.buttons["Bolus"], timeout: 8) else { return } - sleep(2) - let field = app.textFields.firstMatch - if field.waitForExistence(timeout: 5) { - // Retap until the keyboard actually appears; typing without focus throws - for _ in 0..<4 { - field.tap() - sleep(1) - if app.keyboards.count > 0 { break } - } - if app.keyboards.count > 0 { - field.typeText("0.5") - } - } - snap("bolus-entry") - for label in ["Deliver", "Save and Deliver"] { - if tapAnything(labeled: label, timeout: 3) { - sleep(4) - return - } - } - cancelModal() - } - - // MARK: - Helpers - - private func snap(_ name: String) { - let attachment = XCTAttachment(screenshot: XCUIScreen.main.screenshot()) - attachment.name = name - attachment.lifetime = .keepAlways - add(attachment) - } - - @discardableResult - private func tap(_ primary: XCUIElement, fallback: XCUIElement? = nil, timeout: TimeInterval) -> Bool { - if primary.waitForExistence(timeout: timeout), primary.isHittable { - primary.tap() - return true - } - if let fallback = fallback, fallback.exists, fallback.isHittable { - fallback.tap() - return true - } - return false - } - - /// Taps the first hittable button, cell, static text, or other element with the given label - @discardableResult - private func tapAnything(labeled label: String, timeout: TimeInterval) -> Bool { - let deadline = Date(timeIntervalSinceNow: timeout) - repeat { - for query in [app.buttons, app.cells, app.staticTexts, app.otherElements] { - let el = query[label].firstMatch - if el.exists, el.isHittable { - el.tap() - return true - } - } - usleep(500_000) - } while Date() < deadline - return false - } - - private func dismissSheets() { - for _ in 0..<2 { - let done = app.navigationBars.buttons["Done"] - if done.exists, done.isHittable { - done.tap() - sleep(1) - } - } - } - - private func closeSettings() { - for label in ["Done", "Close"] { - for query in [app.navigationBars.buttons[label], app.buttons[label]] { - if query.exists, query.isHittable { - query.tap() - sleep(1) - return - } - } - } - // Fall back to tapping where the SwiftUI Settings sheet's Done pill sits - if app.staticTexts["Settings"].exists { - app.coordinate(withNormalizedOffset: CGVector(dx: 0.87, dy: 0.115)).tap() - sleep(1) - } - } - - /// Dismisses a leftover Settings sheet from a previous partial run - private func returnToHome() { - for _ in 0..<3 { - let done = app.buttons["Done"] - if app.staticTexts["Settings"].exists, done.exists, done.isHittable { - done.tap() - sleep(1) - continue - } - break - } - } - - private func cancelModal() { - for label in ["Cancel", "Close"] { - let button = app.navigationBars.buttons[label] - if button.exists, button.isHittable { - button.tap() - sleep(1) - return - } - } - } -} From 845bfe17ac83cb264e3bb4288131f7c0ee6158bb Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Sun, 19 Jul 2026 12:37:43 -0700 Subject: [PATCH 5/5] xcode 27 --- Loop.xcodeproj/project.pbxproj | 206 ++++++++++++++++++--------------- 1 file changed, 110 insertions(+), 96 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index cb819db00a..4d7dc14373 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 01414522FC033F2B7A9B223A /* Loop/Resources/Sounds/crying.caf in Resources */ = {isa = PBXBuildFile; fileRef = 85203550670F2E9BE92F27D8 /* Loop/Resources/Sounds/crying.caf */; }; + 0A02E77AD7536C322933D49B /* Loop/Resources/Sounds/minimal.caf in Resources */ = {isa = PBXBuildFile; fileRef = 74145BE3C46270883D295A1F /* Loop/Resources/Sounds/minimal.caf */; }; + 124B300527D84BE2A1233A91 /* Loop/Resources/Sounds/urgent_low.caf in Resources */ = {isa = PBXBuildFile; fileRef = C9EA3F48338B49BD9C6BD0F9 /* Loop/Resources/Sounds/urgent_low.caf */; }; 1419606928D9554E00BA86E0 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 437AFEE6203688CF008C4892 /* LoopKitUI.framework */; }; 1419606A28D955BC00BA86E0 /* MockKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C101947127DD473C004E7EB8 /* MockKitUI.framework */; }; 142CB7592A60BF2E0075748A /* EditMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142CB7582A60BF2E0075748A /* EditMode.swift */; }; @@ -56,6 +59,7 @@ 14C970862C5C358C00E8A01B /* FavoriteFoodInsightsChartsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C970852C5C358C00E8A01B /* FavoriteFoodInsightsChartsView.swift */; }; 14D906F42A846510006EB79A /* FavoriteFoodsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D906F32A846510006EB79A /* FavoriteFoodsViewModel.swift */; }; 14ED83F62C6421F9008B4A5C /* FavoriteFoodInsightsCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14ED83F52C6421F9008B4A5C /* FavoriteFoodInsightsCardView.swift */; }; + 16E5A3BC4D931CE6AED18518 /* Loop/Resources/Sounds/trill.caf in Resources */ = {isa = PBXBuildFile; fileRef = 21F13EB666EBFDD03F99CB7E /* Loop/Resources/Sounds/trill.caf */; }; 1D05219B2469E9DF000EBBDE /* StoredAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D05219A2469E9DF000EBBDE /* StoredAlert.swift */; }; 1D05219D2469F1F5000EBBDE /* AlertStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D05219C2469F1F5000EBBDE /* AlertStore.swift */; }; 1D080CBD2473214A00356610 /* AlertStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1D080CBB2473214A00356610 /* AlertStore.xcdatamodeld */; }; @@ -76,38 +80,15 @@ 1DA7A84224476EAD008257F0 /* AlertManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA7A84124476EAD008257F0 /* AlertManagerTests.swift */; }; 1DA7A84424477698008257F0 /* InAppModalAlertSchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA7A84324477698008257F0 /* InAppModalAlertSchedulerTests.swift */; }; 1DB1065124467E18005542BD /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB1065024467E18005542BD /* AlertManager.swift */; }; - C9A47B0180AAB1FC572D3EFE /* CriticalAlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A47B0175B13A9543390BC5 /* CriticalAlertAudioPlayer.swift */; }; - C1A1A00001000000000000A1 /* CriticalAlertAlarmScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A1A00001000000000000A2 /* CriticalAlertAlarmScheduler.swift */; }; - C9A47B0280AAB1FC572D3EFF /* GlucoseAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A47B0275B13A9543390BC6 /* GlucoseAlertManager.swift */; }; - C9A47B0380AAB1FC572D3F00 /* GlucoseAlertSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A47B0375B13A9543390BC7 /* GlucoseAlertSettingsView.swift */; }; - C9A47B0480AAB1FC572D3F01 /* critical.caf in Resources */ = {isa = PBXBuildFile; fileRef = C9A47B0475B13A9543390BC8 /* critical.caf */; }; - DE965737FE2DAE9FE692769E /* alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0FC34B523FE5A50C920506D5 /* alarm.caf */; }; - A413B305CB4C4009DA09BE57 /* bloom.caf in Resources */ = {isa = PBXBuildFile; fileRef = 8FB2394478FF679F436E8EF1 /* bloom.caf */; }; - 230BE095719B8D8969B32A9C /* bloop.caf in Resources */ = {isa = PBXBuildFile; fileRef = AF16A5EDEB9DF756890E0C09 /* bloop.caf */; }; - 9B8FCBDD6E3A9E603C4AABAD /* bright_alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 270F994CD56D566028B3CB25 /* bright_alarm.caf */; }; - 9960C6964DD51D81093FAD5D /* chime.caf in Resources */ = {isa = PBXBuildFile; fileRef = 3F7A1D72697082DC3A14587E /* chime.caf */; }; - 824A01CBE97A4F7A9D32F7D6 /* clear_chimes.caf in Resources */ = {isa = PBXBuildFile; fileRef = 4A645E0E2DDA534A635C9201 /* clear_chimes.caf */; }; - 01414522FC033F2B7A9B223A /* crying.caf in Resources */ = {isa = PBXBuildFile; fileRef = 85203550670F2E9BE92F27D8 /* crying.caf */; }; - 2C694BFD5806E573AFAB5E2E /* dings.caf in Resources */ = {isa = PBXBuildFile; fileRef = 62F7CB5D5DC337E705032AC7 /* dings.caf */; }; - A4574F416AD62A1FCDE36D7D /* high_chimes.caf in Resources */ = {isa = PBXBuildFile; fileRef = 491F8F0D89972E9D8903CFA7 /* high_chimes.caf */; }; - FA37F6EC10FF02D4F985D724 /* honk.caf in Resources */ = {isa = PBXBuildFile; fileRef = 6D5FCC7207E50ACC682E9E6E /* honk.caf */; }; - 0A02E77AD7536C322933D49B /* minimal.caf in Resources */ = {isa = PBXBuildFile; fileRef = 74145BE3C46270883D295A1F /* minimal.caf */; }; - 33644743F20B3706E3107FAC /* mood_synth.caf in Resources */ = {isa = PBXBuildFile; fileRef = BEC0BA802BE613F144AC3A70 /* mood_synth.caf */; }; - A2779286E117BD3627DA0618 /* simple.caf in Resources */ = {isa = PBXBuildFile; fileRef = 5181B60CBF3CB2DA537F0475 /* simple.caf */; }; - D6E28676C2C7751C77C7F2FB /* spring.caf in Resources */ = {isa = PBXBuildFile; fileRef = 9471E156FC10D6D0FF219C77 /* spring.caf */; }; - CB1AF8155B1761A30ACA5591 /* synth.caf in Resources */ = {isa = PBXBuildFile; fileRef = 83081FFB0497A4596F597830 /* synth.caf */; }; - 16E5A3BC4D931CE6AED18518 /* trill.caf in Resources */ = {isa = PBXBuildFile; fileRef = 21F13EB666EBFDD03F99CB7E /* trill.caf */; }; - 124B300527D84BE2A1233A91 /* urgent_low.caf in Resources */ = {isa = PBXBuildFile; fileRef = C9EA3F48338B49BD9C6BD0F9 /* urgent_low.caf */; }; 1DB1CA4D24A55F0000B3B94C /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB1CA4C24A55F0000B3B94C /* Image.swift */; }; 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB619AB270BAD3D006C9D07 /* VersionUpdateViewModel.swift */; }; 1DDE273D24AEA4B000796622 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB1CA4E24A56D7600B3B94C /* SettingsViewModel.swift */; }; - FEEDB0000000000000000001 /* StatisticsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000001 /* StatisticsViewModel.swift */; }; 1DDE273E24AEA4B000796622 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE09BA824A3E23F009EE9F9 /* SettingsView.swift */; }; - FEEDB0000000000000000004 /* TimeInRangeBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000004 /* TimeInRangeBar.swift */; }; - FEEDB0000000000000000003 /* AGPChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000003 /* AGPChartView.swift */; }; - FEEDB0000000000000000002 /* StatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000002 /* StatisticsView.swift */; }; 1DDE274024AEA4F200796622 /* NotificationsCriticalAlertPermissionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA46B5F2492E2E300D71A63 /* NotificationsCriticalAlertPermissionsView.swift */; }; 1DFE9E172447B6270082C280 /* UserNotificationAlertSchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFE9E162447B6270082C280 /* UserNotificationAlertSchedulerTests.swift */; }; + 230BE095719B8D8969B32A9C /* Loop/Resources/Sounds/bloop.caf in Resources */ = {isa = PBXBuildFile; fileRef = AF16A5EDEB9DF756890E0C09 /* Loop/Resources/Sounds/bloop.caf */; }; + 2C694BFD5806E573AFAB5E2E /* Loop/Resources/Sounds/dings.caf in Resources */ = {isa = PBXBuildFile; fileRef = 62F7CB5D5DC337E705032AC7 /* Loop/Resources/Sounds/dings.caf */; }; + 33644743F20B3706E3107FAC /* Loop/Resources/Sounds/mood_synth.caf in Resources */ = {isa = PBXBuildFile; fileRef = BEC0BA802BE613F144AC3A70 /* Loop/Resources/Sounds/mood_synth.caf */; }; 3ED3198A2EB659E600820BCF /* GlucoseLiveActivityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED319882EB659E600820BCF /* GlucoseLiveActivityConfiguration.swift */; }; 3ED3198B2EB659E600820BCF /* ChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED319872EB659E600820BCF /* ChartView.swift */; }; 3ED3198C2EB659E600820BCF /* BasalViewActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED319862EB659E600820BCF /* BasalViewActivity.swift */; }; @@ -130,7 +111,6 @@ 430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430D85881F44037000AF2D4F /* HUDViewTableViewCell.swift */; }; 4311FB9B1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */; }; 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431A8C3F1EC6E8AB00823B9C /* CircleMaskView.swift */; }; - 4326BA641F3A44D9007CCAD4 /* ChartLineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4326BA631F3A44D9007CCAD4 /* ChartLineModel.swift */; }; 4328E0281CFBE2C500E199AA /* CLKComplicationTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0221CFBE2C500E199AA /* CLKComplicationTemplate.swift */; }; 4328E02A1CFBE2C500E199AA /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0241CFBE2C500E199AA /* UIColor.swift */; }; 4328E02B1CFBE2C500E199AA /* WKAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0251CFBE2C500E199AA /* WKAlertAction.swift */; }; @@ -158,7 +138,6 @@ 4374B5EF209D84BF00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; 4374B5F0209D857E00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43776F8F1B8022E90074EA36 /* AppDelegate.swift */; }; - C9SCENED0000000000000001 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9SCENED0000000000000002 /* SceneDelegate.swift */; }; 43776F971B8022E90074EA36 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43776F951B8022E90074EA36 /* Main.storyboard */; }; 43785E932120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */; }; 43785E972120E4500057DED1 /* INRelevantShortcutStore+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E952120E4010057DED1 /* INRelevantShortcutStore+Loop.swift */; }; @@ -253,6 +232,7 @@ 4FF4D0F81E1725B000846527 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434F54561D287FDB002A9274 /* NibLoadable.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 */; }; 841306BB2F7F0D9C00AF0320 /* ReferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841306BA2F7F0D9C00AF0320 /* ReferencesView.swift */; }; 84213C752D932F0F00642E78 /* InsulinDeliveryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84213C742D932F0F00642E78 /* InsulinDeliveryLog.swift */; }; 84213C892D94941000642E78 /* InsulinDeliveryLogEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84213C882D94941000642E78 /* InsulinDeliveryLogEvent.swift */; }; @@ -348,6 +328,12 @@ 89F9119424358E4500ECCAF3 /* CarbAbsorptionTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119324358E4500ECCAF3 /* CarbAbsorptionTime.swift */; }; 89F9119624358E6900ECCAF3 /* BolusPickerValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */; }; 89FE21AD24AC57E30033F501 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21AC24AC57E30033F501 /* Collection.swift */; }; + 9960C6964DD51D81093FAD5D /* Loop/Resources/Sounds/chime.caf in Resources */ = {isa = PBXBuildFile; fileRef = 3F7A1D72697082DC3A14587E /* Loop/Resources/Sounds/chime.caf */; }; + 9B8FCBDD6E3A9E603C4AABAD /* Loop/Resources/Sounds/bright_alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 270F994CD56D566028B3CB25 /* Loop/Resources/Sounds/bright_alarm.caf */; }; + A1B2C3D40000000000000002 /* GlucoseAlertManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000001 /* GlucoseAlertManagerTests.swift */; }; + A2779286E117BD3627DA0618 /* Loop/Resources/Sounds/simple.caf in Resources */ = {isa = PBXBuildFile; fileRef = 5181B60CBF3CB2DA537F0475 /* Loop/Resources/Sounds/simple.caf */; }; + A413B305CB4C4009DA09BE57 /* Loop/Resources/Sounds/bloom.caf in Resources */ = {isa = PBXBuildFile; fileRef = 8FB2394478FF679F436E8EF1 /* Loop/Resources/Sounds/bloom.caf */; }; + A4574F416AD62A1FCDE36D7D /* Loop/Resources/Sounds/high_chimes.caf in Resources */ = {isa = PBXBuildFile; fileRef = 491F8F0D89972E9D8903CFA7 /* Loop/Resources/Sounds/high_chimes.caf */; }; A91D2A3F26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */; }; A91E4C2124F867A700BE9213 /* StoredAlertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91E4C2024F867A700BE9213 /* StoredAlertTests.swift */; }; A91E4C2324F86F1000BE9213 /* CriticalEventLogExportManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91E4C2224F86F1000BE9213 /* CriticalEventLogExportManagerTests.swift */; }; @@ -484,7 +470,6 @@ C151634E2FA2C9C800FEECE8 /* RequiredVersionUpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C151634D2FA2C9C800FEECE8 /* RequiredVersionUpdateView.swift */; }; C152B9F52C9C7D4A00ACBC06 /* AutomationHistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C152B9F42C9C7D4A00ACBC06 /* AutomationHistoryEntry.swift */; }; C152B9F72C9C7EF100ACBC06 /* AutomationHistoryEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C152B9F62C9C7EF100ACBC06 /* AutomationHistoryEntryTests.swift */; }; - FEEDB0000000000000000005 /* GlucoseStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */; }; C1550B0C2E6F249A009369DC /* LoopCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1550B0B2E6F249A009369DC /* LoopCircleView.swift */; }; C165756F2534C468004AE16E /* SimpleBolusViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165756E2534C468004AE16E /* SimpleBolusViewModelTests.swift */; }; C16575712538A36B004AE16E /* CGMStalenessMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16575702538A36B004AE16E /* CGMStalenessMonitor.swift */; }; @@ -531,6 +516,7 @@ C19E23B62E83513400C20D83 /* PresetActivateCrownConfirm.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19E23B52E83512A00C20D83 /* PresetActivateCrownConfirm.swift */; }; C19E23B82E83566700C20D83 /* CircularProgressWithCheckmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19E23B72E83566300C20D83 /* CircularProgressWithCheckmark.swift */; }; C19F48742560ABFB003632D7 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; + C1A1A00001000000000000A1 /* CriticalAlertAlarmScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A1A00001000000000000A2 /* CriticalAlertAlarmScheduler.swift */; }; C1AD4200256D61E500164DDD /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AD41FF256D61E500164DDD /* Comparable.swift */; }; C1AF062329426300002C1B19 /* ManualGlucoseEntryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */; }; C1B80D632AF97D7200AB7705 /* LoopDataManager+CarbAbsorption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B80D622AF97D7200AB7705 /* LoopDataManager+CarbAbsorption.swift */; }; @@ -593,13 +579,20 @@ C1FAD5192E7E0C3400F7FAD9 /* ChartPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FAD5182E7E0C3100F7FAD9 /* ChartPageView.swift */; }; C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428B217806A300FAB378 /* StateColorPalette.swift */; }; C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; + C9A47B0180AAB1FC572D3EFE /* CriticalAlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A47B0175B13A9543390BC5 /* CriticalAlertAudioPlayer.swift */; }; + C9A47B0280AAB1FC572D3EFF /* GlucoseAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A47B0275B13A9543390BC6 /* GlucoseAlertManager.swift */; }; + C9A47B0380AAB1FC572D3F00 /* GlucoseAlertSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A47B0375B13A9543390BC7 /* GlucoseAlertSettingsView.swift */; }; + C9A47B0480AAB1FC572D3F01 /* Loop/Resources/Sounds/critical.caf in Resources */ = {isa = PBXBuildFile; fileRef = C9A47B0475B13A9543390BC8 /* Loop/Resources/Sounds/critical.caf */; }; + C9SCENED0000000000000001 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9SCENED0000000000000002 /* SceneDelegate.swift */; }; + CB1AF8155B1761A30ACA5591 /* Loop/Resources/Sounds/synth.caf in Resources */ = {isa = PBXBuildFile; fileRef = 83081FFB0497A4596F597830 /* Loop/Resources/Sounds/synth.caf */; }; + D6E28676C2C7751C77C7F2FB /* Loop/Resources/Sounds/spring.caf in Resources */ = {isa = PBXBuildFile; fileRef = 9471E156FC10D6D0FF219C77 /* Loop/Resources/Sounds/spring.caf */; }; DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */; }; DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */; }; - FEEDB0000000000000000000 /* GlucoseStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000000 /* GlucoseStatistics.swift */; }; DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */; }; DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */; }; DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */; }; DDC389FE2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */; }; + DE965737FE2DAE9FE692769E /* Loop/Resources/Sounds/alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0FC34B523FE5A50C920506D5 /* Loop/Resources/Sounds/alarm.caf */; }; E93E865424DB6CBA00FF40C8 /* retrospective_output.json in Resources */ = {isa = PBXBuildFile; fileRef = E93E865324DB6CBA00FF40C8 /* retrospective_output.json */; }; E93E865624DB731900FF40C8 /* predicted_glucose_without_retrospective.json in Resources */ = {isa = PBXBuildFile; fileRef = E93E865524DB731900FF40C8 /* predicted_glucose_without_retrospective.json */; }; E93E865824DB75BE00FF40C8 /* predicted_glucose_very_negative.json in Resources */ = {isa = PBXBuildFile; fileRef = E93E865724DB75BD00FF40C8 /* predicted_glucose_very_negative.json */; }; @@ -639,13 +632,19 @@ E9C00EF324C6222400628F35 /* LoopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C00EEF24C620EF00628F35 /* LoopSettings.swift */; }; E9C00EF524C623EF00628F35 /* LoopSettings+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C00EF424C623EF00628F35 /* LoopSettings+Loop.swift */; }; E9C58A7324DB4A2700487A17 /* LoopDataManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C58A7124DB489100487A17 /* LoopDataManagerTests.swift */; }; - A1B2C3D40000000000000002 /* GlucoseAlertManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D40000000000000001 /* GlucoseAlertManagerTests.swift */; }; E9C58A7C24DB529A00487A17 /* momentum_effect_bouncing.json in Resources */ = {isa = PBXBuildFile; fileRef = E9C58A7724DB529A00487A17 /* momentum_effect_bouncing.json */; }; E9C58A7D24DB529A00487A17 /* basal_profile.json in Resources */ = {isa = PBXBuildFile; fileRef = E9C58A7824DB529A00487A17 /* basal_profile.json */; }; E9C58A7E24DB529A00487A17 /* dynamic_glucose_effect_partially_observed.json in Resources */ = {isa = PBXBuildFile; fileRef = E9C58A7924DB529A00487A17 /* dynamic_glucose_effect_partially_observed.json */; }; E9C58A7F24DB529A00487A17 /* counteraction_effect_falling_glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = E9C58A7A24DB529A00487A17 /* counteraction_effect_falling_glucose.json */; }; E9C58A8024DB529A00487A17 /* insulin_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E9C58A7B24DB529A00487A17 /* insulin_effect.json */; }; + FA37F6EC10FF02D4F985D724 /* Loop/Resources/Sounds/honk.caf in Resources */ = {isa = PBXBuildFile; fileRef = 6D5FCC7207E50ACC682E9E6E /* Loop/Resources/Sounds/honk.caf */; }; FADE0001000000000000DE02 /* DevelopmentBranchAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */; }; + FEEDB0000000000000000000 /* GlucoseStatistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000000 /* GlucoseStatistics.swift */; }; + FEEDB0000000000000000001 /* StatisticsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000001 /* StatisticsViewModel.swift */; }; + FEEDB0000000000000000002 /* Statistics/StatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000002 /* Statistics/StatisticsView.swift */; }; + FEEDB0000000000000000003 /* Statistics/AGPChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000003 /* Statistics/AGPChartView.swift */; }; + FEEDB0000000000000000004 /* Statistics/TimeInRangeBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000004 /* Statistics/TimeInRangeBar.swift */; }; + FEEDB0000000000000000005 /* GlucoseStatisticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -781,6 +780,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0FC34B523FE5A50C920506D5 /* Loop/Resources/Sounds/alarm.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/alarm.caf; sourceTree = SOURCE_ROOT; }; 142CB7582A60BF2E0075748A /* EditMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditMode.swift; sourceTree = ""; }; 142CB75A2A60BFC30075748A /* FavoriteFoodsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteFoodsView.swift; sourceTree = ""; }; 1452F4A82A851C9400F8B9E4 /* FavoriteFoodAddEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteFoodAddEditViewModel.swift; sourceTree = ""; }; @@ -834,38 +834,14 @@ 1DA7A84124476EAD008257F0 /* AlertManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManagerTests.swift; sourceTree = ""; }; 1DA7A84324477698008257F0 /* InAppModalAlertSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppModalAlertSchedulerTests.swift; sourceTree = ""; }; 1DB1065024467E18005542BD /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; - C9A47B0175B13A9543390BC5 /* CriticalAlertAudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CriticalAlertAudioPlayer.swift; sourceTree = ""; }; - C1A1A00001000000000000A2 /* CriticalAlertAlarmScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CriticalAlertAlarmScheduler.swift; sourceTree = ""; }; - C9A47B0275B13A9543390BC6 /* GlucoseAlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseAlertManager.swift; sourceTree = ""; }; - C9A47B0375B13A9543390BC7 /* GlucoseAlertSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseAlertSettingsView.swift; sourceTree = ""; }; - C9A47B0475B13A9543390BC8 /* critical.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/critical.caf"; sourceTree = SOURCE_ROOT; }; - 0FC34B523FE5A50C920506D5 /* alarm.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/alarm.caf"; sourceTree = SOURCE_ROOT; }; - 8FB2394478FF679F436E8EF1 /* bloom.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/bloom.caf"; sourceTree = SOURCE_ROOT; }; - AF16A5EDEB9DF756890E0C09 /* bloop.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/bloop.caf"; sourceTree = SOURCE_ROOT; }; - 270F994CD56D566028B3CB25 /* bright_alarm.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/bright_alarm.caf"; sourceTree = SOURCE_ROOT; }; - 3F7A1D72697082DC3A14587E /* chime.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/chime.caf"; sourceTree = SOURCE_ROOT; }; - 4A645E0E2DDA534A635C9201 /* clear_chimes.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/clear_chimes.caf"; sourceTree = SOURCE_ROOT; }; - 85203550670F2E9BE92F27D8 /* crying.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/crying.caf"; sourceTree = SOURCE_ROOT; }; - 62F7CB5D5DC337E705032AC7 /* dings.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/dings.caf"; sourceTree = SOURCE_ROOT; }; - 491F8F0D89972E9D8903CFA7 /* high_chimes.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/high_chimes.caf"; sourceTree = SOURCE_ROOT; }; - 6D5FCC7207E50ACC682E9E6E /* honk.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/honk.caf"; sourceTree = SOURCE_ROOT; }; - 74145BE3C46270883D295A1F /* minimal.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/minimal.caf"; sourceTree = SOURCE_ROOT; }; - BEC0BA802BE613F144AC3A70 /* mood_synth.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/mood_synth.caf"; sourceTree = SOURCE_ROOT; }; - 5181B60CBF3CB2DA537F0475 /* simple.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/simple.caf"; sourceTree = SOURCE_ROOT; }; - 9471E156FC10D6D0FF219C77 /* spring.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/spring.caf"; sourceTree = SOURCE_ROOT; }; - 83081FFB0497A4596F597830 /* synth.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/synth.caf"; sourceTree = SOURCE_ROOT; }; - 21F13EB666EBFDD03F99CB7E /* trill.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/trill.caf"; sourceTree = SOURCE_ROOT; }; - C9EA3F48338B49BD9C6BD0F9 /* urgent_low.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop/Resources/Sounds/urgent_low.caf"; sourceTree = SOURCE_ROOT; }; 1DB1CA4C24A55F0000B3B94C /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; 1DB1CA4E24A56D7600B3B94C /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; - FEEDF0000000000000000001 /* StatisticsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatisticsViewModel.swift"; sourceTree = ""; }; 1DB619AB270BAD3D006C9D07 /* VersionUpdateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateViewModel.swift; sourceTree = ""; }; 1DC63E7325351BDF004605DA /* TrueTime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TrueTime.framework; path = Carthage/Build/iOS/TrueTime.framework; sourceTree = ""; }; 1DE09BA824A3E23F009EE9F9 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - FEEDF0000000000000000004 /* TimeInRangeBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Statistics/TimeInRangeBar.swift"; sourceTree = ""; }; - FEEDF0000000000000000003 /* AGPChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Statistics/AGPChartView.swift"; sourceTree = ""; }; - FEEDF0000000000000000002 /* StatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Statistics/StatisticsView.swift"; sourceTree = ""; }; 1DFE9E162447B6270082C280 /* UserNotificationAlertSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationAlertSchedulerTests.swift; sourceTree = ""; }; + 21F13EB666EBFDD03F99CB7E /* Loop/Resources/Sounds/trill.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/trill.caf; sourceTree = SOURCE_ROOT; }; + 270F994CD56D566028B3CB25 /* Loop/Resources/Sounds/bright_alarm.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/bright_alarm.caf; sourceTree = SOURCE_ROOT; }; 3ED319862EB659E600820BCF /* BasalViewActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalViewActivity.swift; sourceTree = ""; }; 3ED319872EB659E600820BCF /* ChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = ""; }; 3ED319882EB659E600820BCF /* GlucoseLiveActivityConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseLiveActivityConfiguration.swift; sourceTree = ""; }; @@ -879,6 +855,7 @@ 3ED3199E2EB65AFE00820BCF /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; 3ED319A02EB65B4100820BCF /* Bootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bootstrap.swift; sourceTree = ""; }; 3ED319A22EB65DA300820BCF /* LiveActivityManagerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManagerProxy.swift; sourceTree = ""; }; + 3F7A1D72697082DC3A14587E /* Loop/Resources/Sounds/chime.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/chime.caf; sourceTree = SOURCE_ROOT; }; 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewController.swift; sourceTree = ""; }; 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDeliveryTableViewController.swift; sourceTree = ""; }; 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSUserDefaults.swift; sourceTree = ""; }; @@ -888,7 +865,6 @@ 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleSubtitleTextFieldTableViewCell.swift; sourceTree = ""; }; 431A8C3F1EC6E8AB00823B9C /* CircleMaskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMaskView.swift; sourceTree = ""; }; 431E73471FF95A900069B5F7 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; - 4326BA631F3A44D9007CCAD4 /* ChartLineModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartLineModel.swift; sourceTree = ""; }; 4328E0221CFBE2C500E199AA /* CLKComplicationTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLKComplicationTemplate.swift; sourceTree = ""; }; 4328E0231CFBE2C500E199AA /* NSUserDefaults+WatchApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+WatchApp.swift"; sourceTree = ""; }; 4328E0241CFBE2C500E199AA /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -920,7 +896,6 @@ 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewCell.swift; sourceTree = ""; }; 43776F8C1B8022E90074EA36 /* Loop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Loop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43776F8F1B8022E90074EA36 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - C9SCENED0000000000000002 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 43776F961B8022E90074EA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43776F9B1B8022E90074EA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NewCarbEntryIntent+Loop.swift"; sourceTree = ""; }; @@ -1014,6 +989,8 @@ 43F89CA222BDFBBC006BB54E /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; 43FCEEA8221A615B0013DD30 /* StatusChartsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusChartsManager.swift; sourceTree = ""; }; 43FCEEAC221A66780013DD30 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = ""; }; + 491F8F0D89972E9D8903CFA7 /* Loop/Resources/Sounds/high_chimes.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/high_chimes.caf; sourceTree = SOURCE_ROOT; }; + 4A645E0E2DDA534A635C9201 /* Loop/Resources/Sounds/clear_chimes.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/clear_chimes.caf; sourceTree = SOURCE_ROOT; }; 4D3B40021D4A9DFE00BC6334 /* G4ShareSpy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = G4ShareSpy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4F08DE8E1E7BB871006741EA /* CollectionType+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CollectionType+Loop.swift"; sourceTree = ""; }; 4F11D3BF20DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBackfillRequestUserInfo.swift; sourceTree = ""; }; @@ -1037,6 +1014,10 @@ 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+StatusExtension.swift"; sourceTree = ""; }; 4FDDD23620DC51DF00D04B16 /* LoopDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopDataManager.swift; sourceTree = ""; }; 4FF4D0FF1E18374700846527 /* WatchContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchContext.swift; sourceTree = ""; }; + 5181B60CBF3CB2DA537F0475 /* Loop/Resources/Sounds/simple.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/simple.caf; sourceTree = SOURCE_ROOT; }; + 62F7CB5D5DC337E705032AC7 /* Loop/Resources/Sounds/dings.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/dings.caf; sourceTree = SOURCE_ROOT; }; + 6D5FCC7207E50ACC682E9E6E /* Loop/Resources/Sounds/honk.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/honk.caf; sourceTree = SOURCE_ROOT; }; + 74145BE3C46270883D295A1F /* Loop/Resources/Sounds/minimal.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/minimal.caf; sourceTree = SOURCE_ROOT; }; 7D23667C21250C7E0028B67D /* LocalizedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LocalizedString.swift; path = LoopUI/Common/LocalizedString.swift; sourceTree = SOURCE_ROOT; }; 7D9BEF132335EC4B005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Intents.strings; sourceTree = ""; }; 7D9BEF292335EC58005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Intents.strings"; sourceTree = ""; }; @@ -1046,6 +1027,7 @@ 7D9BEF812335EC8B005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Intents.strings; sourceTree = ""; }; 7D9BF13A23370E8B005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Intents.strings; sourceTree = ""; }; 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetLoopManager.swift; sourceTree = ""; }; + 83081FFB0497A4596F597830 /* Loop/Resources/Sounds/synth.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/synth.caf; sourceTree = SOURCE_ROOT; }; 840A2F0D2C0F978E003D5E90 /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 841306BA2F7F0D9C00AF0320 /* ReferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferencesView.swift; sourceTree = ""; }; 84213C742D932F0F00642E78 /* InsulinDeliveryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinDeliveryLog.swift; sourceTree = ""; }; @@ -1089,6 +1071,7 @@ 84EC162D2C9115CA00D220C5 /* DIYLoopUnitTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = DIYLoopUnitTestPlan.xctestplan; sourceTree = ""; }; 84F20DFC2D0B9C3A0089DF02 /* EditPresetDurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPresetDurationView.swift; sourceTree = ""; }; 84FA9D322CF7FD0D004162B4 /* PresetsHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetsHistoryView.swift; sourceTree = ""; }; + 85203550670F2E9BE92F27D8 /* Loop/Resources/Sounds/crying.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/crying.caf; sourceTree = SOURCE_ROOT; }; 891B508424342BE1005DA578 /* CarbAndBolusFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbAndBolusFlowViewModel.swift; sourceTree = ""; }; 892A5D29222EF60A008961AB /* MockKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = MockKit.framework; path = Carthage/Build/iOS/MockKit.framework; sourceTree = SOURCE_ROOT; }; 892A5D2B222EF60A008961AB /* MockKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = MockKitUI.framework; path = Carthage/Build/iOS/MockKitUI.framework; sourceTree = SOURCE_ROOT; }; @@ -1146,6 +1129,9 @@ 89F9119324358E4500ECCAF3 /* CarbAbsorptionTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbAbsorptionTime.swift; sourceTree = ""; }; 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusPickerValues.swift; sourceTree = ""; }; 89FE21AC24AC57E30033F501 /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; + 8FB2394478FF679F436E8EF1 /* Loop/Resources/Sounds/bloom.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/bloom.caf; sourceTree = SOURCE_ROOT; }; + 9471E156FC10D6D0FF219C77 /* Loop/Resources/Sounds/spring.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/spring.caf; sourceTree = SOURCE_ROOT; }; + A1B2C3D40000000000000001 /* GlucoseAlertManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertManagerTests.swift; sourceTree = ""; }; A900531A28D60862000BC15B /* Loop.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop.shortcut; sourceTree = ""; }; A900531B28D608CA000BC15B /* Cancel Override.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cancel Override.shortcut"; sourceTree = ""; }; A900531C28D6090D000BC15B /* Loop Remote Overrides.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop Remote Overrides.shortcut"; sourceTree = ""; }; @@ -1196,6 +1182,7 @@ A9F703742489C9A000C98AD8 /* GlucoseStore+SimulatedCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlucoseStore+SimulatedCoreData.swift"; sourceTree = ""; }; A9F703762489D8AA00C98AD8 /* PersistentDeviceLog+SimulatedCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistentDeviceLog+SimulatedCoreData.swift"; sourceTree = ""; }; A9FB75F0252BE320004C7D3F /* BolusDosingDecision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusDosingDecision.swift; sourceTree = ""; }; + AF16A5EDEB9DF756890E0C09 /* Loop/Resources/Sounds/bloop.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/bloop.caf; sourceTree = SOURCE_ROOT; }; B4001CED28CBBC82002FB414 /* AlertManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManagementView.swift; sourceTree = ""; }; B40D07C6251A89D500C1C6D7 /* GlucoseDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseDisplay.swift; sourceTree = ""; }; B429CAB22E97C7F300FA988E /* LoopStatusModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatusModalView.swift; sourceTree = ""; }; @@ -1253,6 +1240,7 @@ B66D1F422E6A5D6600471149 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/Main.xcstrings; sourceTree = ""; }; B6F22EF72E95A03800CCA05F /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Intents.strings; sourceTree = ""; }; B6F22EF92E95A03C00CCA05F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Intents.strings; sourceTree = ""; }; + BEC0BA802BE613F144AC3A70 /* Loop/Resources/Sounds/mood_synth.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop/Resources/Sounds/mood_synth.caf; sourceTree = SOURCE_ROOT; }; C1004DEF2981F5B700B8CF94 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; C1004DFD2981F67A00B8CF94 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; }; C1004E052981F6A100B8CF94 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1306,7 +1294,6 @@ C151634D2FA2C9C800FEECE8 /* RequiredVersionUpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredVersionUpdateView.swift; sourceTree = ""; }; C152B9F42C9C7D4A00ACBC06 /* AutomationHistoryEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationHistoryEntry.swift; sourceTree = ""; }; C152B9F62C9C7EF100ACBC06 /* AutomationHistoryEntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationHistoryEntryTests.swift; sourceTree = ""; }; - FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlucoseStatisticsTests.swift"; sourceTree = ""; }; C1550B0B2E6F249A009369DC /* LoopCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopCircleView.swift; sourceTree = ""; }; C159C8192867857000A86EC0 /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C159C8212867859800A86EC0 /* MockKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MockKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1354,6 +1341,7 @@ C19E23B72E83566300C20D83 /* CircularProgressWithCheckmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressWithCheckmark.swift; sourceTree = ""; }; C19E23B92E83607C00C20D83 /* DerivedAssetsBase.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssetsBase.xcassets; sourceTree = ""; }; C19F496225630504003632D7 /* Minizip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Minizip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C1A1A00001000000000000A2 /* CriticalAlertAlarmScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CriticalAlertAlarmScheduler.swift; sourceTree = ""; }; C1ABA1602E281D330049DF41 /* NotificationActionSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationActionSelection.swift; sourceTree = ""; }; C1AD41FF256D61E500164DDD /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = ""; }; C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualGlucoseEntryRow.swift; sourceTree = ""; }; @@ -1395,9 +1383,14 @@ C1FAD5182E7E0C3100F7FAD9 /* ChartPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartPageView.swift; sourceTree = ""; }; C1FB428B217806A300FAB378 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; C1FB428E217921D600FAB378 /* PumpManagerUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerUI.swift; sourceTree = ""; }; + C9A47B0175B13A9543390BC5 /* CriticalAlertAudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CriticalAlertAudioPlayer.swift; sourceTree = ""; }; + C9A47B0275B13A9543390BC6 /* GlucoseAlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseAlertManager.swift; sourceTree = ""; }; + C9A47B0375B13A9543390BC7 /* GlucoseAlertSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseAlertSettingsView.swift; sourceTree = ""; }; + 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 = ""; }; DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrectionSelectionView.swift; sourceTree = ""; }; DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationFactorStrategy.swift; sourceTree = ""; }; - FEEDF0000000000000000000 /* GlucoseStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GlucoseStatistics.swift"; sourceTree = ""; }; DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorStrategy.swift; sourceTree = ""; }; DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantApplicationFactorStrategy.swift; sourceTree = ""; }; DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+algorithmExperimentsSection.swift"; sourceTree = ""; }; @@ -1439,7 +1432,6 @@ E9C00EEF24C620EF00628F35 /* LoopSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSettings.swift; sourceTree = ""; }; E9C00EF424C623EF00628F35 /* LoopSettings+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoopSettings+Loop.swift"; sourceTree = ""; }; E9C58A7124DB489100487A17 /* LoopDataManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopDataManagerTests.swift; sourceTree = ""; }; - A1B2C3D40000000000000001 /* GlucoseAlertManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertManagerTests.swift; sourceTree = ""; }; E9C58A7724DB529A00487A17 /* momentum_effect_bouncing.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = momentum_effect_bouncing.json; sourceTree = ""; }; E9C58A7824DB529A00487A17 /* basal_profile.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = basal_profile.json; sourceTree = ""; }; E9C58A7924DB529A00487A17 /* dynamic_glucose_effect_partially_observed.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = dynamic_glucose_effect_partially_observed.json; sourceTree = ""; }; @@ -1448,6 +1440,12 @@ F5D9C01727DABBE0002E48F6 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Intents.strings; sourceTree = ""; }; F5E0BDD327E1D71C0033557E /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Intents.strings; sourceTree = ""; }; FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentBranchAlerter.swift; sourceTree = ""; }; + FEEDF0000000000000000000 /* GlucoseStatistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStatistics.swift; sourceTree = ""; }; + FEEDF0000000000000000001 /* StatisticsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsViewModel.swift; sourceTree = ""; }; + FEEDF0000000000000000002 /* Statistics/StatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics/StatisticsView.swift; sourceTree = ""; }; + FEEDF0000000000000000003 /* Statistics/AGPChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics/AGPChartView.swift; sourceTree = ""; }; + FEEDF0000000000000000004 /* Statistics/TimeInRangeBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics/TimeInRangeBar.swift; sourceTree = ""; }; + FEEDF0000000000000000005 /* GlucoseStatisticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseStatisticsTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1734,6 +1732,7 @@ B4C6D2412EA7E38C006F5755 /* LocalizablePlural.xcstrings */, 437D9BA11D7B5203007245E8 /* Loop.xcconfig */, A951C5FF23E8AB51003E26DC /* Version.xcconfig */, + 84957A16300D5CDF0040E2EF /* Recovered References */, ); sourceTree = ""; }; @@ -2032,9 +2031,9 @@ B43B5C532EAFBF110096A6AE /* RecentGlucoseTableViewCell.xib */, C151634D2FA2C9C800FEECE8 /* RequiredVersionUpdateView.swift */, 1DE09BA824A3E23F009EE9F9 /* SettingsView.swift */, - FEEDF0000000000000000004 /* TimeInRangeBar.swift */, - FEEDF0000000000000000003 /* AGPChartView.swift */, - FEEDF0000000000000000002 /* StatisticsView.swift */, + FEEDF0000000000000000004 /* Statistics/TimeInRangeBar.swift */, + FEEDF0000000000000000003 /* Statistics/AGPChartView.swift */, + FEEDF0000000000000000002 /* Statistics/StatisticsView.swift */, E4854073AAE5C2CB97167F63 /* HealthAccessView.swift */, DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */, C1DE5D22251BFC4D00439E49 /* SimpleBolusView.swift */, @@ -2120,7 +2119,6 @@ B66D1F3D2E6A5D6600471149 /* Localizable.xcstrings */, B66D1F202E6A5D6500471149 /* InfoPlist.xcstrings */, 4FB76FC41E8C576800B39636 /* Extensions */, - 4F7528A61DFE20AE00C322D6 /* Models */, B42C950F24A3C44F00857C73 /* ViewModel */, 4F7528931DFE1E1600C322D6 /* Views */, 4F75288D1DFE1DC600C322D6 /* LoopUI.h */, @@ -2154,14 +2152,6 @@ path = Views; sourceTree = ""; }; - 4F7528A61DFE20AE00C322D6 /* Models */ = { - isa = PBXGroup; - children = ( - 4326BA631F3A44D9007CCAD4 /* ChartLineModel.swift */, - ); - path = Models; - sourceTree = ""; - }; 4F75F0052100146B00B5570E /* Scenes */ = { isa = PBXGroup; children = ( @@ -2290,6 +2280,31 @@ path = Training; sourceTree = ""; }; + 84957A16300D5CDF0040E2EF /* Recovered References */ = { + isa = PBXGroup; + children = ( + C9A47B0475B13A9543390BC8 /* Loop/Resources/Sounds/critical.caf */, + 0FC34B523FE5A50C920506D5 /* Loop/Resources/Sounds/alarm.caf */, + 8FB2394478FF679F436E8EF1 /* Loop/Resources/Sounds/bloom.caf */, + AF16A5EDEB9DF756890E0C09 /* Loop/Resources/Sounds/bloop.caf */, + 270F994CD56D566028B3CB25 /* Loop/Resources/Sounds/bright_alarm.caf */, + 3F7A1D72697082DC3A14587E /* Loop/Resources/Sounds/chime.caf */, + 4A645E0E2DDA534A635C9201 /* Loop/Resources/Sounds/clear_chimes.caf */, + 85203550670F2E9BE92F27D8 /* Loop/Resources/Sounds/crying.caf */, + 62F7CB5D5DC337E705032AC7 /* Loop/Resources/Sounds/dings.caf */, + 491F8F0D89972E9D8903CFA7 /* Loop/Resources/Sounds/high_chimes.caf */, + 6D5FCC7207E50ACC682E9E6E /* Loop/Resources/Sounds/honk.caf */, + 74145BE3C46270883D295A1F /* Loop/Resources/Sounds/minimal.caf */, + BEC0BA802BE613F144AC3A70 /* Loop/Resources/Sounds/mood_synth.caf */, + 5181B60CBF3CB2DA537F0475 /* Loop/Resources/Sounds/simple.caf */, + 9471E156FC10D6D0FF219C77 /* Loop/Resources/Sounds/spring.caf */, + 83081FFB0497A4596F597830 /* Loop/Resources/Sounds/synth.caf */, + 21F13EB666EBFDD03F99CB7E /* Loop/Resources/Sounds/trill.caf */, + C9EA3F48338B49BD9C6BD0F9 /* Loop/Resources/Sounds/urgent_low.caf */, + ); + name = "Recovered References"; + sourceTree = ""; + }; 84AA81D12A4A2778000B658B /* Components */ = { isa = PBXGroup; children = ( @@ -3085,24 +3100,24 @@ C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */, C1EE9E812A38D0FB0064784A /* BuildDetails.plist in Resources */, 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */, - C9A47B0480AAB1FC572D3F01 /* critical.caf in Resources */, - DE965737FE2DAE9FE692769E /* alarm.caf in Resources */, - A413B305CB4C4009DA09BE57 /* bloom.caf in Resources */, - 230BE095719B8D8969B32A9C /* bloop.caf in Resources */, - 9B8FCBDD6E3A9E603C4AABAD /* bright_alarm.caf in Resources */, - 9960C6964DD51D81093FAD5D /* chime.caf in Resources */, - 824A01CBE97A4F7A9D32F7D6 /* clear_chimes.caf in Resources */, - 01414522FC033F2B7A9B223A /* crying.caf in Resources */, - 2C694BFD5806E573AFAB5E2E /* dings.caf in Resources */, - A4574F416AD62A1FCDE36D7D /* high_chimes.caf in Resources */, - FA37F6EC10FF02D4F985D724 /* honk.caf in Resources */, - 0A02E77AD7536C322933D49B /* minimal.caf in Resources */, - 33644743F20B3706E3107FAC /* mood_synth.caf in Resources */, - A2779286E117BD3627DA0618 /* simple.caf in Resources */, - D6E28676C2C7751C77C7F2FB /* spring.caf in Resources */, - CB1AF8155B1761A30ACA5591 /* synth.caf in Resources */, - 16E5A3BC4D931CE6AED18518 /* trill.caf in Resources */, - 124B300527D84BE2A1233A91 /* urgent_low.caf in Resources */, + C9A47B0480AAB1FC572D3F01 /* Loop/Resources/Sounds/critical.caf in Resources */, + DE965737FE2DAE9FE692769E /* Loop/Resources/Sounds/alarm.caf in Resources */, + A413B305CB4C4009DA09BE57 /* Loop/Resources/Sounds/bloom.caf in Resources */, + 230BE095719B8D8969B32A9C /* Loop/Resources/Sounds/bloop.caf in Resources */, + 9B8FCBDD6E3A9E603C4AABAD /* Loop/Resources/Sounds/bright_alarm.caf in Resources */, + 9960C6964DD51D81093FAD5D /* Loop/Resources/Sounds/chime.caf in Resources */, + 824A01CBE97A4F7A9D32F7D6 /* Loop/Resources/Sounds/clear_chimes.caf in Resources */, + 01414522FC033F2B7A9B223A /* Loop/Resources/Sounds/crying.caf in Resources */, + 2C694BFD5806E573AFAB5E2E /* Loop/Resources/Sounds/dings.caf in Resources */, + A4574F416AD62A1FCDE36D7D /* Loop/Resources/Sounds/high_chimes.caf in Resources */, + FA37F6EC10FF02D4F985D724 /* Loop/Resources/Sounds/honk.caf in Resources */, + 0A02E77AD7536C322933D49B /* Loop/Resources/Sounds/minimal.caf in Resources */, + 33644743F20B3706E3107FAC /* Loop/Resources/Sounds/mood_synth.caf in Resources */, + A2779286E117BD3627DA0618 /* Loop/Resources/Sounds/simple.caf in Resources */, + D6E28676C2C7751C77C7F2FB /* Loop/Resources/Sounds/spring.caf in Resources */, + CB1AF8155B1761A30ACA5591 /* Loop/Resources/Sounds/synth.caf in Resources */, + 16E5A3BC4D931CE6AED18518 /* Loop/Resources/Sounds/trill.caf in Resources */, + 124B300527D84BE2A1233A91 /* Loop/Resources/Sounds/urgent_low.caf in Resources */, B405E35A24D2B1A400DD058D /* HUDAssets.xcassets in Resources */, A966152623EA5A26005D8B29 /* DefaultAssets.xcassets in Resources */, A966152723EA5A26005D8B29 /* DerivedAssets.xcassets in Resources */, @@ -3540,9 +3555,9 @@ 1D12D3B92548EFDD00B53E8B /* main.swift in Sources */, A9DCF32A25B0FABF00C89088 /* LoopUIColorPalette+Default.swift in Sources */, 1DDE273E24AEA4B000796622 /* SettingsView.swift in Sources */, - FEEDB0000000000000000004 /* TimeInRangeBar.swift in Sources */, - FEEDB0000000000000000003 /* AGPChartView.swift in Sources */, - FEEDB0000000000000000002 /* StatisticsView.swift in Sources */, + FEEDB0000000000000000004 /* Statistics/TimeInRangeBar.swift in Sources */, + FEEDB0000000000000000003 /* Statistics/AGPChartView.swift in Sources */, + FEEDB0000000000000000002 /* Statistics/StatisticsView.swift in Sources */, BAF043BF0BC70EC99AE79F71 /* HealthAccessView.swift in Sources */, A9B607B0247F000F00792BE4 /* UserNotifications+Loop.swift in Sources */, 43F89CA322BDFBBD006BB54E /* UIActivityIndicatorView.swift in Sources */, @@ -3842,7 +3857,6 @@ files = ( 7D23667D21250C7E0028B67D /* LocalizedString.swift in Sources */, 4FF4D0F81E1725B000846527 /* NibLoadable.swift in Sources */, - 4326BA641F3A44D9007CCAD4 /* ChartLineModel.swift in Sources */, 4374B5F0209D857E00D17AA8 /* OSLog.swift in Sources */, B4E96D4F248A6E20002DABAD /* CGMStatusHUDView.swift in Sources */, B4E96D4B248A6B6E002DABAD /* DeviceStatusHUDView.swift in Sources */,