From 84ebeb3874cd1158155b6a0a25d678c06a8a7326 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Mon, 9 Dec 2024 09:49:06 -0800 Subject: [PATCH] [LOOP-4754] Presets Storage (#729) --- Loop.xcodeproj/project.pbxproj | 10 ++-- Loop/Managers/LoopAppManager.swift | 3 +- Loop/Managers/TemporaryPresetsManager.swift | 8 +-- Loop/Views/Presets/PresetsHistoryView.swift | 52 +++++++++++++++++++ Loop/Views/Presets/PresetsView.swift | 5 +- LoopCore/NSUserDefaults.swift | 12 ----- .../TemporaryPresetsManagerTests.swift | 2 +- 7 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 Loop/Views/Presets/PresetsHistoryView.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 142f6f6eb..2eb821b88 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -269,6 +269,7 @@ 84E8BBCE2CCA1E070078E6CF /* PresetsTrainingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BBCD2CCA1E070078E6CF /* PresetsTrainingCard.swift */; }; 84E8BBD02CCA279B0078E6CF /* Image+Exists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8BBCF2CCA27960078E6CF /* Image+Exists.swift */; }; 84EC162E2C9115CA00D220C5 /* DIYLoopUnitTestPlan.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 84EC162D2C9115CA00D220C5 /* DIYLoopUnitTestPlan.xctestplan */; }; + 84FA9D332CF7FD0D004162B4 /* PresetsHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FA9D322CF7FD0D004162B4 /* PresetsHistoryView.swift */; }; 891B508524342BE1005DA578 /* CarbAndBolusFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891B508424342BE1005DA578 /* CarbAndBolusFlowViewModel.swift */; }; 892A5D59222F0A27008961AB /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D58222F0A27008961AB /* Debug.swift */; }; 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */; }; @@ -1145,6 +1146,7 @@ 84E8BBCD2CCA1E070078E6CF /* PresetsTrainingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetsTrainingCard.swift; sourceTree = ""; }; 84E8BBCF2CCA27960078E6CF /* Image+Exists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Exists.swift"; sourceTree = ""; }; 84EC162D2C9115CA00D220C5 /* DIYLoopUnitTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = DIYLoopUnitTestPlan.xctestplan; sourceTree = ""; }; + 84FA9D322CF7FD0D004162B4 /* PresetsHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetsHistoryView.swift; sourceTree = ""; }; 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; }; @@ -2484,6 +2486,7 @@ isa = PBXGroup; children = ( 84E8BBC92CCA16290078E6CF /* PresetsView.swift */, + 84FA9D322CF7FD0D004162B4 /* PresetsHistoryView.swift */, 84E8BBB02CC9793C0078E6CF /* PresetsTrainingView.swift */, 84E8BBC22CC9B9780078E6CF /* Components */, 84E8BBB62CC990480078E6CF /* Training Content */, @@ -3657,6 +3660,7 @@ 84E8BBC82CC9D34B0078E6CF /* TherapySettingsExampleView.swift in Sources */, 1D05219B2469E9DF000EBBDE /* StoredAlert.swift in Sources */, E9B0802B253BBDFF00BAD8F8 /* IntentExtensionInfo.swift in Sources */, + 84FA9D332CF7FD0D004162B4 /* PresetsHistoryView.swift in Sources */, C1E3862628247C6100F561A4 /* StoredLoopNotRunningNotification.swift in Sources */, A97F250825E056D500F0EE19 /* OnboardingManager.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, @@ -4751,7 +4755,7 @@ TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = "-Wall"; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; }; name = Debug; }; @@ -4861,7 +4865,7 @@ VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = "-Wall"; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; }; name = Release; }; @@ -5297,7 +5301,7 @@ TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = "-Wall"; - WATCHOS_DEPLOYMENT_TARGET = 8.0; + WATCHOS_DEPLOYMENT_TARGET = 10.6; }; name = Testflight; }; diff --git a/Loop/Managers/LoopAppManager.swift b/Loop/Managers/LoopAppManager.swift index c83414ef5..317606c22 100644 --- a/Loop/Managers/LoopAppManager.swift +++ b/Loop/Managers/LoopAppManager.swift @@ -877,7 +877,8 @@ extension LoopAppManager: UNUserNotificationCenterDelegate { extension LoopAppManager: TemporaryScheduleOverrideHistoryDelegate { func temporaryScheduleOverrideHistoryDidUpdate(_ history: TemporaryScheduleOverrideHistory) { - UserDefaults.appGroup?.overrideHistory = history + TemporaryScheduleOverrideHistoryContainer.shared.deleteAll() + TemporaryScheduleOverrideHistoryContainer.shared.context.insert(history) remoteDataServicesManager.triggerUpload(for: .overrides) } } diff --git a/Loop/Managers/TemporaryPresetsManager.swift b/Loop/Managers/TemporaryPresetsManager.swift index 31bb4af3a..08fc19892 100644 --- a/Loop/Managers/TemporaryPresetsManager.swift +++ b/Loop/Managers/TemporaryPresetsManager.swift @@ -22,16 +22,18 @@ class TemporaryPresetsManager { private var settingsProvider: SettingsProvider - var overrideHistory = UserDefaults.appGroup?.overrideHistory ?? TemporaryScheduleOverrideHistory.init() + var overrideHistory: TemporaryScheduleOverrideHistory private var presetActivationObservers: [PresetActivationObserver] = [] private var overrideIntentObserver: NSKeyValueObservation? = nil + @MainActor init(settingsProvider: SettingsProvider) { self.settingsProvider = settingsProvider - - self.overrideHistory.relevantTimeWindow = LoopCoreConstants.defaultCarbAbsorptionTimes.slow * 2 + + self.overrideHistory = TemporaryScheduleOverrideHistoryContainer.shared.fetch() + TemporaryScheduleOverrideHistory.relevantTimeWindow = Bundle.main.localCacheDuration scheduleOverride = overrideHistory.activeOverride(at: Date()) diff --git a/Loop/Views/Presets/PresetsHistoryView.swift b/Loop/Views/Presets/PresetsHistoryView.swift new file mode 100644 index 000000000..265f0f6f4 --- /dev/null +++ b/Loop/Views/Presets/PresetsHistoryView.swift @@ -0,0 +1,52 @@ +// +// PresetsHistoryView.swift +// Loop +// +// Created by Cameron Ingham on 11/27/24. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import LoopKit +import SwiftUI + +struct PresetsHistoryView: View { + + @State var history: TemporaryScheduleOverrideHistory + + init () { + self.history = TemporaryScheduleOverrideHistoryContainer.shared.fetch() + } + + let formatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.hour, .minute, .second] + formatter.unitsStyle = .short + return formatter + }() + + var body: some View { + List { + Section("Recent Events") { + ForEach(history.recentEvents.sorted(by: { $0.override.actualEndDate > $1.override.actualEndDate }), id: \.self) { recentEvent in + + let scheduledDuration = recentEvent.override.duration.timeInterval + let actualDuration = recentEvent.override.actualDuration.timeInterval + + let value = scheduledDuration == actualDuration ? "\(formatter.string(from: scheduledDuration) ?? "")" : "\(formatter.string(from: actualDuration) ?? "") / \(formatter.string(from: scheduledDuration) ?? "")" + + LabeledContent { + Text(value) + } label: { + Text(recentEvent.override.presetId) + + Text(recentEvent.override.startDate.formatted(date: .abbreviated, time: .shortened)) + } + } + } + } + } +} + +#Preview { + PresetsHistoryView() +} diff --git a/Loop/Views/Presets/PresetsView.swift b/Loop/Views/Presets/PresetsView.swift index ed97af3f2..8c6564b96 100644 --- a/Loop/Views/Presets/PresetsView.swift +++ b/Loop/Views/Presets/PresetsView.swift @@ -6,8 +6,9 @@ // Copyright © 2024 LoopKit Authors. All rights reserved. // -import SwiftUI import Foundation +import LoopKit +import SwiftUI enum PresetSortOption: Int, CaseIterable { case name @@ -107,7 +108,7 @@ struct PresetsView: View { Text("Support") .font(.title2.bold()) - NavigationLink(destination: EmptyView()) { + NavigationLink(destination: PresetsHistoryView()) { HStack { Image(systemName: "list.bullet") .foregroundColor(.white) diff --git a/LoopCore/NSUserDefaults.swift b/LoopCore/NSUserDefaults.swift index beb944606..1f0428bd9 100644 --- a/LoopCore/NSUserDefaults.swift +++ b/LoopCore/NSUserDefaults.swift @@ -77,18 +77,6 @@ extension UserDefaults { } } - public var overrideHistory: TemporaryScheduleOverrideHistory? { - get { - if let rawValue = object(forKey: Key.overrideHistory.rawValue) as? TemporaryScheduleOverrideHistory.RawValue { - return TemporaryScheduleOverrideHistory(rawValue: rawValue) - } else { - return nil - } - } - set { - set(newValue?.rawValue, forKey: Key.overrideHistory.rawValue) - } - } public var lastBedtimeQuery: Date? { get { diff --git a/LoopTests/Managers/TemporaryPresetsManagerTests.swift b/LoopTests/Managers/TemporaryPresetsManagerTests.swift index 492762864..a1e097c76 100644 --- a/LoopTests/Managers/TemporaryPresetsManagerTests.swift +++ b/LoopTests/Managers/TemporaryPresetsManagerTests.swift @@ -30,7 +30,7 @@ class TemporaryPresetsManagerTests: XCTestCase { override func setUp() async throws { let settingsProvider = MockSettingsProvider(settings: settings) - manager = TemporaryPresetsManager(settingsProvider: settingsProvider) + manager = await TemporaryPresetsManager(settingsProvider: settingsProvider) } func testPreMealOverride() {