diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index e94e7da8db..8254ab53fc 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 */; }; @@ -252,6 +233,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 */; }; @@ -270,7 +252,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 */; }; @@ -348,6 +329,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 */; }; @@ -485,7 +472,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 */; }; C15A8C492A7305B1009D736B /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C1E3DC4628595FAA00CA19FF /* SwiftCharts */; }; C165756F2534C468004AE16E /* SimpleBolusViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165756E2534C468004AE16E /* SimpleBolusViewModelTests.swift */; }; @@ -533,6 +519,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 */; }; @@ -598,13 +585,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 */; }; @@ -644,13 +638,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 */ @@ -788,6 +788,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 = ""; }; @@ -841,38 +842,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 = ""; }; @@ -886,6 +863,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 = ""; }; @@ -1020,6 +998,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 = ""; }; @@ -1043,6 +1023,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 = ""; }; @@ -1052,6 +1036,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 = ""; }; @@ -1071,7 +1056,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 = ""; }; @@ -1096,6 +1080,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; }; @@ -1153,6 +1138,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 = ""; }; @@ -1203,6 +1191,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 = ""; }; @@ -1260,6 +1249,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 = ""; }; @@ -1313,7 +1303,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; }; @@ -1361,6 +1350,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 = ""; }; @@ -1402,9 +1392,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 = ""; }; @@ -1446,7 +1441,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 = ""; }; @@ -1455,6 +1449,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 */ @@ -1744,6 +1744,7 @@ B4C6D2412EA7E38C006F5755 /* LocalizablePlural.xcstrings */, 437D9BA11D7B5203007245E8 /* Loop.xcconfig */, A951C5FF23E8AB51003E26DC /* Version.xcconfig */, + 84957A16300D5CDF0040E2EF /* Recovered References */, ); sourceTree = ""; }; @@ -1772,6 +1773,7 @@ 43F5C2D41B92A4A6003EB13D /* Info.plist */, C1EE9E802A38D0FB0064784A /* BuildDetails.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, + C9SCENED0000000000000002 /* SceneDelegate.swift */, 1D12D3B82548EFDD00B53E8B /* main.swift */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, @@ -1983,7 +1985,6 @@ C13DA2AF24F6C7690098BB29 /* UIViewController.swift */, 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */, A9B607AF247F000F00792BE4 /* UserNotifications+Loop.swift */, - 84A7B54F2D2D972C00B6D202 /* Image+Optional.swift */, ); path = Extensions; sourceTree = ""; @@ -2042,9 +2043,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 */, @@ -2300,6 +2301,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 = ( @@ -3099,24 +3125,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 */, @@ -3390,6 +3416,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 */, @@ -3553,9 +3580,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 */, @@ -3612,7 +3639,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 */, 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/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..70e22f3046 100644 --- a/Loop/Extensions/UIAlertController.swift +++ b/Loop/Extensions/UIAlertController.swift @@ -12,27 +12,6 @@ import LoopKitUI extension UIAlertController { - /** - Initializes an ActionSheet-styled controller for selecting a pre-meal preset duration - - - parameter handler: A closure to execute when the sheet is dismissed after selection. The closure has a single argument: - - duration: The duration for which the pre-meal preset is to be enabled - */ - internal convenience init(premealDurationSelectionHandler handler: @escaping (_ duration: TimeInterval) -> Void) { - self.init( - title: NSLocalizedString("Use Pre-Meal Preset", comment: "The title of the alert controller used to select a duration for pre-meal targets"), - message: nil, - preferredStyle: .actionSheet - ) - - let distantFuture = NSLocalizedString("Until I enter carbs", comment: "The title of a target alert action specifying pre-meal targets duration for 1 hour or until the user enters carbs (whichever comes first).") - addAction(UIAlertAction(title: distantFuture, style: .default) { _ in - handler(.hours(1)) - }) - - addCancelAction() - } - /// Initializes an action sheet-styled controller for selecting a PumpManager /// /// - Parameters: @@ -55,6 +34,8 @@ extension UIAlertController { } )) } + + addCancelAction() } /// Initializes an action sheet-styled controller for selecting a CGMManager @@ -79,6 +60,8 @@ extension UIAlertController { } )) } + + addCancelAction() } internal convenience init(deleteCGMManagerHandler handler: @escaping (_ isDeleted: Bool) -> Void) { @@ -108,10 +91,12 @@ extension UIAlertController { /// - selectionHandler: A closure to execute when a service is selected. /// - identifier: The identifier of the selected service. internal convenience init(availableServices: [ServiceDescriptor], selectionHandler: @escaping (_ identifier: String) -> Void) { + let preferredStyle: UIAlertController.Style = .alert + self.init( title: NSLocalizedString("Add Service", comment: "Action sheet title selecting service"), message: nil, - preferredStyle: .actionSheet + preferredStyle: preferredStyle ) for availableService in availableServices { @@ -123,11 +108,15 @@ extension UIAlertController { } )) } + + if #available(iOS 26.0, *) { + addCancelAction() + } } internal func addCancelAction(handler: ((UIAlertAction) -> Void)? = nil) { let cancel = NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet") - addAction(UIAlertAction(title: cancel, style: .cancel, handler: handler)) + addAction(UIAlertAction(title: cancel, style: .destructive, handler: handler)) } } 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/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/Loop/Managers/AlertPermissionsChecker.swift b/Loop/Managers/AlertPermissionsChecker.swift index fe9409b8d9..d691048196 100644 --- a/Loop/Managers/AlertPermissionsChecker.swift +++ b/Loop/Managers/AlertPermissionsChecker.swift @@ -259,7 +259,7 @@ extension AlertPermissionsChecker { message: alert.alertBody, preferredStyle: .alert) let titleImageAttachment = NSTextAttachment() - titleImageAttachment.image = UIImage(systemName: "exclamationmark.triangle.fill")?.withTintColor(.critical) + titleImageAttachment.image = UIImage(systemName: "exclamationmark.triangle.fill")?.withTintColor(.critical, renderingMode: .alwaysOriginal) titleImageAttachment.bounds = CGRect(x: titleImageAttachment.bounds.origin.x, y: -10, width: 40, height: 35) let titleWithImage = NSMutableAttributedString(attachment: titleImageAttachment) titleWithImage.append(NSMutableAttributedString(string: "\n\n", attributes: [.font: UIFont.systemFont(ofSize: 8)])) 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/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/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index db0511d6b2..edfae4f643 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -227,7 +227,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() @@ -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: ActionTabBarMetrics.tableContentInset, right: 0) + redrawCharts() reloading = false @@ -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: @@ -1381,7 +1382,7 @@ final class StatusTableViewController: LoopChartsTableViewController { let manualEntryDoseView = ManualEntryDoseView(viewModel: viewModel) let hostingController = DismissibleHostingController(rootView: manualEntryDoseView, isModalInPresentation: false) let navigationWrapper = UINavigationController(rootViewController: hostingController) - hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: navigationWrapper, action: #selector(dismissWithAnimation)) + hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Cancel", comment: ""), style: .plain, target: navigationWrapper, action: #selector(dismissWithAnimation)) present(navigationWrapper, animated: true) } @@ -1475,7 +1476,7 @@ final class StatusTableViewController: LoopChartsTableViewController { let bolusEntryView = SimpleBolusView(viewModel: viewModel).environmentObject(deviceManager.displayGlucosePreference) let hostingController = DismissibleHostingController(rootView: bolusEntryView, isModalInPresentation: false) navigationWrapper = UINavigationController(rootViewController: hostingController) - hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: navigationWrapper, action: #selector(dismissWithAnimation)) + hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Cancel", comment: ""), style: .plain, target: navigationWrapper, action: #selector(dismissWithAnimation)) present(navigationWrapper, animated: true) } else { let viewModel = CarbEntryViewModel(delegate: loopManager) @@ -1535,7 +1536,7 @@ final class StatusTableViewController: LoopChartsTableViewController { ) let navigationWrapper = UINavigationController(rootViewController: hostingController) - hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: navigationWrapper, action: #selector(dismissWithAnimation)) + hostingController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Cancel", comment: ""), style: .plain, target: navigationWrapper, action: #selector(dismissWithAnimation)) present(navigationWrapper, animated: true) analyticsServicesManager?.didDisplayBolusScreen() } @@ -1744,15 +1745,15 @@ final class StatusTableViewController: LoopChartsTableViewController { @objc private func pumpStatusTapped(_ sender: UIGestureRecognizer) { if let pumpStatusView = sender.view as? PumpStatusHUDView { - executeHUDTapAction(deviceManager.didTapOnPumpStatus(pumpStatusView.pumpManagerProvidedHUD)) + executeHUDTapAction(deviceManager.didTapOnPumpStatus(pumpStatusView.pumpManagerProvidedHUD), from: sender.view) } } @objc private func cgmStatusTapped( _ sender: UIGestureRecognizer) { - executeHUDTapAction(deviceManager.didTapOnCGMStatus()) + executeHUDTapAction(deviceManager.didTapOnCGMStatus(), from: sender.view) } - private func executeHUDTapAction(_ action: HUDTapAction?) { + private func executeHUDTapAction(_ action: HUDTapAction?, from sourceView: UIView?) { guard let action = action else { return } @@ -1765,15 +1766,15 @@ final class StatusTableViewController: LoopChartsTableViewController { case .openAppURL(let url): UIApplication.shared.open(url) case .setupNewCGM: - addNewCGMManager() + addNewCGMManager(from: sourceView) case .setupNewPump: - addNewPumpManager() + addNewPumpManager(from: sourceView) default: return } } - private func addNewPumpManager() { + private func addNewPumpManager(from sourceView: UIView?) { let availablePumpManagers = deviceManager.availablePumpManagers switch availablePumpManagers.count { @@ -1785,12 +1786,13 @@ final class StatusTableViewController: LoopChartsTableViewController { let alert = UIAlertController(availablePumpManagers: availablePumpManagers) { [weak self] (identifier) in self?.addPumpManager(withIdentifier: identifier) } - alert.addCancelAction { _ in } + alert.popoverPresentationController?.sourceView = sourceView ?? view + alert.popoverPresentationController?.sourceRect = sourceView?.bounds ?? view.bounds present(alert, animated: true, completion: nil) } } - private func addNewCGMManager() { + private func addNewCGMManager(from sourceView: UIView?) { let availableCGMManagers = deviceManager.availableCGMManagers switch availableCGMManagers.count { @@ -1802,7 +1804,8 @@ final class StatusTableViewController: LoopChartsTableViewController { let alert = UIAlertController(availableCGMManagers: availableCGMManagers) { [weak self] identifier in self?.addCGMManager(withIdentifier: identifier) } - alert.addCancelAction { _ in } + alert.popoverPresentationController?.sourceView = sourceView ?? view + alert.popoverPresentationController?.sourceRect = sourceView?.bounds ?? view.bounds present(alert, animated: true, completion: nil) } } diff --git a/Loop/Views/AlertManagementView.swift b/Loop/Views/AlertManagementView.swift index a33633ce92..4fce6bde7c 100644 --- a/Loop/Views/AlertManagementView.swift +++ b/Loop/Views/AlertManagementView.swift @@ -129,7 +129,7 @@ struct AlertManagementView: View { muteAlertsButton } else { unmuteAlertsButton - .listRowSeparator(.visible, edges: .all) + .listRowSeparator(.visible, edges: .bottom) muteAlertsSummary } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 14efe111bd..687624f696 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -12,6 +12,7 @@ import LoopKitUI import MockKit import SwiftUI import LoopUI +import UIKit struct SettingsView: View { @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference @@ -118,25 +119,6 @@ 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(item: $alert) { alert in switch alert { case .deleteCGMData: @@ -477,19 +459,35 @@ extension SettingsView { imageView: plusImage, label: NSLocalizedString("Add Pump", comment: "Title text for button to add pump device"), descriptiveText: NSLocalizedString("Tap here to set up a pump", comment: "Descriptive text for button to add pump device")) + .background( + PluginPopover( + isPresented: pickerBinding(.pumpPicker), + title: NSLocalizedString("Add Pump", comment: "The title of the pump chooser in settings"), + actions: pumpChoices + ) + ) } } - - 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 + } + } + ) + } + + private var pumpChoices: [PluginPopover.Action] { + viewModel.pumpManagerSettingsViewModel.availableDevices.map { availableDevice in + .init(title: availableDevice.localizedTitle) { self.viewModel.pumpManagerSettingsViewModel.didTapAdd(availableDevice) } } - result.append(.cancel()) - return result } - + @ViewBuilder private var cgmSection: some View { if viewModel.cgmManagerSettingsViewModel.isSetUp() { @@ -504,6 +502,13 @@ extension SettingsView { imageView: plusImage, label: NSLocalizedString("Add CGM", comment: "Title text for button to add CGM device"), descriptiveText: NSLocalizedString("Tap here to set up a CGM", comment: "Descriptive text for button to add CGM device")) + .background( + PluginPopover( + isPresented: pickerBinding(.cgmPicker), + title: NSLocalizedString("Add CGM", comment: "The title of the CGM chooser in settings"), + actions: cgmChoices + ) + ) } } @@ -517,18 +522,16 @@ extension SettingsView { } } - private var cgmChoices: [ActionSheet.Button] { - var result = viewModel.cgmManagerSettingsViewModel.availableDevices + private var cgmChoices: [PluginPopover.Action] { + viewModel.cgmManagerSettingsViewModel.availableDevices .sorted(by: {$0.localizedTitle < $1.localizedTitle}) .map { availableDevice in - ActionSheet.Button.default(Text(availableDevice.localizedTitle)) { + .init(title: availableDevice.localizedTitle) { self.viewModel.cgmManagerSettingsViewModel.didTapAdd(availableDevice) + } } - } - result.append(.cancel()) - return result } - + private var servicesSection: some View { Section(header: SectionHeader(label: NSLocalizedString("Services", comment: "The title of the services section in settings"))) { ForEach(viewModel.servicesViewModel.activeServices().indices, id: \.self) { index in @@ -544,20 +547,25 @@ extension SettingsView { imageView: plusImage, label: NSLocalizedString("Add Service", comment: "The title of the add service button in settings"), descriptiveText: NSLocalizedString("Tap here to set up a Service", comment: "The descriptive text of the add service button in settings")) + .background( + PluginPopover( + isPresented: pickerBinding(.servicePicker), + title: NSLocalizedString("Add Service", comment: "The title of the add service action sheet in settings"), + actions: serviceChoices + ) + ) } } } - - private var serviceChoices: [ActionSheet.Button] { - var result = viewModel.servicesViewModel.inactiveServices().map { availableService in - ActionSheet.Button.default(Text(availableService.localizedTitle)) { + + private var serviceChoices: [PluginPopover.Action] { + viewModel.servicesViewModel.inactiveServices().map { availableService in + .init(title: availableService.localizedTitle) { self.viewModel.servicesViewModel.didTapAddService(availableService) } } - result.append(.cancel()) - return result } - + private var deleteDataSection: some View { Section { if viewModel.pumpManagerSettingsViewModel.isTestingDevice { @@ -775,6 +783,77 @@ fileprivate struct LargeButton: View { } } +/// Presents the plugin chooser as a UIKit action sheet anchored to the tapped row. +/// +/// A SwiftUI `.actionSheet`/`.confirmationDialog` presented from a `List` row breaks +/// under the Liquid Glass design, so the sheet is presented from a hosted +/// `UIViewController` positioned behind the row instead. +struct PluginPopover: UIViewControllerRepresentable { + struct Action { + let title: String + let handler: () -> Void + } + + @Binding var isPresented: Bool + let title: String + let actions: [Action] + + func makeCoordinator() -> Coordinator { + Coordinator() + } + + func makeUIViewController(context: Context) -> UIViewController { + UIViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + let coordinator = context.coordinator + + if !isPresented { + coordinator.didPresent = false + return + } + + guard !coordinator.didPresent else { return } + guard uiViewController.presentedViewController == nil else { return } + + coordinator.didPresent = true + coordinator.onDismiss = { self.isPresented = false } + + let alert = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet) + for action in actions { + alert.addAction(UIAlertAction(title: action.title, style: .default) { _ in + self.isPresented = false + action.handler() + }) + } + + alert.addAction(UIAlertAction( + title: NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet"), + style: .destructive + ) { _ in + self.isPresented = false + }) + + if let popover = alert.popoverPresentationController { + popover.sourceView = uiViewController.view + popover.sourceRect = uiViewController.view.bounds + popover.delegate = coordinator + } + + uiViewController.present(alert, animated: true) + } + + final class Coordinator: NSObject, UIPopoverPresentationControllerDelegate { + var didPresent = false + var onDismiss: (() -> Void)? + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + onDismiss?() + } + } +} + public struct SettingsView_Previews: PreviewProvider { public static var previews: some View { diff --git a/Loop/Views/StatusTableView.swift b/Loop/Views/StatusTableView.swift index 56c1425907..220e16eefe 100644 --- a/Loop/Views/StatusTableView.swift +++ b/Loop/Views/StatusTableView.swift @@ -87,14 +87,7 @@ private struct WrappedStatusTableViewController: UIViewControllerRepresentable { } struct StatusTableView: View { - - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @Environment(\.verticalSizeClass) private var verticalSizeClass - - private var isLandscape: Bool { - UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height - } - + private let wrapped: WrappedStatusTableViewController var viewController: StatusTableViewController { @@ -132,136 +125,225 @@ 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() } - .toolbar(isLandscape ? .hidden : .visible, for: .bottomBar) - .toolbarBackground(.visible, for: .bottomBar) + + ActionTab( + title: "Settings", + icon: "settings", + tintColor: .secondaryLabel + ) { + viewController.presentSettings() + } + } } - - 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) +} + +struct ActionTab: Identifiable { + let id = UUID() + let title: String + let icon: String + let tintColor: UIColor + let action: () -> Void +} + +struct ActionTabBar: UIViewRepresentable { - Text("Add Carbs") - .font(.footnote) - .foregroundStyle(.secondary) - .fixedSize() + let items: [ActionTab] + var isHidden: Bool = false + + func makeUIView(context: Context) -> UITabBar { + let bar = UITabBar() + bar.delegate = context.coordinator + let appearance = UITabBarAppearance() + appearance.configureWithOpaqueBackground() + let titleAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.label] + for layout in [appearance.stackedLayoutAppearance, appearance.inlineLayoutAppearance, appearance.compactInlineLayoutAppearance] { + for state in [layout.normal, layout.selected] { + state.titleTextAttributes = titleAttributes } } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewControllerCarbsButton") + bar.standardAppearance = appearance + bar.scrollEdgeAppearance = appearance + return bar } - - 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 updateUIView(_ uiView: UITabBar, context: Context) { + uiView.isHidden = isHidden + 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 + ) + } + } + + 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") } - - 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 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 } } +} + +enum ActionTabBarMetrics { + + static let barHeight: CGFloat = 49 + + static var bottomSafeAreaInset: CGFloat { + UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow }? + .safeAreaInsets.bottom ?? 0 + } + + static var interfaceOrientation: UIInterfaceOrientation { + let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } + let scene = scenes.first { $0.windows.contains { $0.isKeyWindow } } + ?? scenes.first { $0.activationState == .foregroundActive } + ?? scenes.first + return scene?.interfaceOrientation ?? .portrait + } + + static var tableContentInset: CGFloat { + guard !interfaceOrientation.isLandscape else { return 0 } + if #available(iOS 26.0, *), bottomSafeAreaInset == 0 { + return barHeight + 40 + } + return 52 + } +} + +struct LegacyTabBarBackground: ViewModifier { + + var isVisible: Bool = true + + func body(content: Content) -> some View { + if !isVisible { + content + .frame(height: 0) + } else if #available(iOS 26.0, *) { + if ActionTabBarMetrics.bottomSafeAreaInset == 0 { + content + .frame(height: ActionTabBarMetrics.barHeight) + .padding(.bottom, 16) + } else { + content + .frame(height: 0) + .padding(.bottom, 6) } + } else { + content + .frame(height: ActionTabBarMetrics.barHeight) + .background( + Color(UIColor.systemBackground) + .ignoresSafeArea(edges: .bottom) + ) } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewPresetsButton") } +} + +struct ActionTabView: View { + + @State private var orientation: UIInterfaceOrientation + + private let content: Content + private let tabs: [ActionTab] - 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() + init( + @ViewBuilder content: @escaping () -> Content, + @ActionTabBuilder tabs: @escaping () -> [ActionTab], + ) { + self.content = content() + self.tabs = tabs() + self.orientation = ActionTabBarMetrics.interfaceOrientation + } + + var body: some View { + content + .safeAreaInset(edge: .bottom, spacing: 0) { + ActionTabBar(items: tabs, isHidden: !orientation.isPortrait) + .modifier(LegacyTabBarBackground(isVisible: orientation.isPortrait)) + } + .onAppear { + UIDevice.current.beginGeneratingDeviceOrientationNotifications() + orientation = ActionTabBarMetrics.interfaceOrientation + } + .onDisappear { + UIDevice.current.endGeneratingDeviceOrientationNotifications() + } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + orientation = ActionTabBarMetrics.interfaceOrientation } - } - .buttonStyle(.plain) - .accessibilityIdentifier("statusTableViewControllerSettingsButton") } }