Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Trio.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,12 @@
FE41E4D629463EE20047FD55 /* NightscoutPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */; };
FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */; };
FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */; };
4A46020CE79D50DE354388D2 /* ProfilePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454A047D1EAFCC939EE5F78F /* ProfilePreset.swift */; };
14D41039DA2596F4C61FF78D /* ProfilePresetStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1483D2092478350967C2F187 /* ProfilePresetStorage.swift */; };
431717ABC2142AA702DBE4BA /* ProfilePresetsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE7ED830845881A9B7BFB13 /* ProfilePresetsDataFlow.swift */; };
A631E9E032EA6B20DC562273 /* ProfilePresetsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088BDB99FF13DF5C230E6505 /* ProfilePresetsProvider.swift */; };
562F7BCF655FE3E27FFDEAD7 /* ProfilePresetsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4206D510BF1723B0AE70D025 /* ProfilePresetsStateModel.swift */; };
1251C1C60CFB17F89B01BB17 /* ProfilePresetsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB73A32687A94A200129336 /* ProfilePresetsRootView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -1584,6 +1590,12 @@
FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutPreferences.swift; sourceTree = "<group>"; };
FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
454A047D1EAFCC939EE5F78F /* ProfilePreset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePreset.swift; sourceTree = "<group>"; };
1483D2092478350967C2F187 /* ProfilePresetStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePresetStorage.swift; sourceTree = "<group>"; };
FCE7ED830845881A9B7BFB13 /* ProfilePresetsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePresetsDataFlow.swift; sourceTree = "<group>"; };
088BDB99FF13DF5C230E6505 /* ProfilePresetsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePresetsProvider.swift; sourceTree = "<group>"; };
4206D510BF1723B0AE70D025 /* ProfilePresetsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePresetsStateModel.swift; sourceTree = "<group>"; };
6EB73A32687A94A200129336 /* ProfilePresetsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePresetsRootView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
Expand Down Expand Up @@ -1939,6 +1951,7 @@
19D466A129AA2B0A004D5F33 /* MealSettings */,
D533BF261CDC1C3F871E7BFD /* NightscoutConfig */,
BD47FD142D88AACC0043966B /* Onboarding */,
618FF40ED4142BF1E47414B8 /* ProfilePresets */,
99C01B871ACAB3F32CE755C7 /* PumpConfig */,
DD9ECB6B2CA99FA400AA7C45 /* RemoteControlConfig */,
3811DE3825C9D4A100A708ED /* Settings */,
Expand Down Expand Up @@ -2460,6 +2473,7 @@
BDF530D72B40F8AC002CAF43 /* LockScreenView.swift */,
583684072BD195A700070A60 /* Determination.swift */,
BDC2EA462C3045AD00E5BBD0 /* Override.swift */,
454A047D1EAFCC939EE5F78F /* ProfilePreset.swift */,
DD21FCB42C6952AD00AF2C25 /* DecimalPickerSettings.swift */,
DD6B7CB32C7B71F700B75029 /* ForecastDisplayType.swift */,
DD9ECB692CA99F6C00AA7C45 /* CommandPayload.swift */,
Expand Down Expand Up @@ -3833,6 +3847,25 @@
path = View;
sourceTree = "<group>";
};
618FF40ED4142BF1E47414B8 /* ProfilePresets */ = {
isa = PBXGroup;
children = (
F801374D2248527BC2D92195 /* View */,
FCE7ED830845881A9B7BFB13 /* ProfilePresetsDataFlow.swift */,
088BDB99FF13DF5C230E6505 /* ProfilePresetsProvider.swift */,
4206D510BF1723B0AE70D025 /* ProfilePresetsStateModel.swift */,
);
path = ProfilePresets;
sourceTree = "<group>";
};
F801374D2248527BC2D92195 /* View */ = {
isa = PBXGroup;
children = (
6EB73A32687A94A200129336 /* ProfilePresetsRootView.swift */,
);
path = View;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -4200,6 +4233,12 @@
files = (
DDA40BBA2F4DB18800257798 /* AlgorithmGlucose.swift in Sources */,
DD5DC9F12CF3D97C00AB8703 /* AdjustmentsStateModel+Overrides.swift in Sources */,
4A46020CE79D50DE354388D2 /* ProfilePreset.swift in Sources */,
14D41039DA2596F4C61FF78D /* ProfilePresetStorage.swift in Sources */,
431717ABC2142AA702DBE4BA /* ProfilePresetsDataFlow.swift in Sources */,
A631E9E032EA6B20DC562273 /* ProfilePresetsProvider.swift in Sources */,
562F7BCF655FE3E27FFDEAD7 /* ProfilePresetsStateModel.swift in Sources */,
1251C1C60CFB17F89B01BB17 /* ProfilePresetsRootView.swift in Sources */,
3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
C2A0A42F2CE03131003B98E8 /* ConstantValues.swift in Sources */,
BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Trio/Sources/APS/OpenAPS/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@ extension OpenAPS {
static let settings = "freeaps/freeaps_settings.json"
static let tempTargetsPresets = "freeaps/temptargets_presets.json"
static let calibrations = "freeaps/calibrations.json"
static let profilePresets = "freeaps/profile_presets.json"
}
}
98 changes: 98 additions & 0 deletions Trio/Sources/APS/Storage/ProfilePresetStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import Foundation
import Swinject

protocol ProfilePresetStorage {
func presets() -> [ProfilePreset]
func savePresets(_ presets: [ProfilePreset])
func saveCurrentProfileAsPreset(name: String) -> ProfilePreset?
func activatePreset(_ preset: ProfilePreset) -> Bool
func deletePreset(id: String)
}

final class BaseProfilePresetStorage: ProfilePresetStorage, Injectable {
@Injected() private var storage: FileStorage!
@Injected() private var broadcaster: Broadcaster!

init(resolver: Resolver) {
injectServices(resolver)
}

func presets() -> [ProfilePreset] {
storage.retrieve(OpenAPS.Trio.profilePresets, as: [ProfilePreset].self) ?? []
}

func savePresets(_ presets: [ProfilePreset]) {
storage.save(presets, as: OpenAPS.Trio.profilePresets)
}

func saveCurrentProfileAsPreset(name: String) -> ProfilePreset? {
let basalProfile = storage.retrieve(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
?? [BasalProfileEntry](from: OpenAPS.defaults(for: OpenAPS.Settings.basalProfile))
?? []

let insulinSensitivities = storage.retrieve(OpenAPS.Settings.insulinSensitivities, as: InsulinSensitivities.self)
?? InsulinSensitivities(from: OpenAPS.defaults(for: OpenAPS.Settings.insulinSensitivities))
?? InsulinSensitivities(units: .mgdL, userPreferredUnits: .mgdL, sensitivities: [])

let carbRatios = storage.retrieve(OpenAPS.Settings.carbRatios, as: CarbRatios.self)
?? CarbRatios(from: OpenAPS.defaults(for: OpenAPS.Settings.carbRatios))
?? CarbRatios(units: .grams, schedule: [])

let bgTargets = storage.retrieve(OpenAPS.Settings.bgTargets, as: BGTargets.self)
?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])

guard !basalProfile.isEmpty,
!insulinSensitivities.sensitivities.isEmpty,
!carbRatios.schedule.isEmpty,
!bgTargets.targets.isEmpty
else {
return nil
}

let preset = ProfilePreset(
name: name,
basalProfile: basalProfile,
insulinSensitivities: insulinSensitivities,
carbRatios: carbRatios,
bgTargets: bgTargets
)

var existingPresets = presets()
existingPresets.append(preset)
savePresets(existingPresets)

return preset
}

func activatePreset(_ preset: ProfilePreset) -> Bool {
guard !preset.basalProfile.isEmpty,
!preset.insulinSensitivities.sensitivities.isEmpty,
!preset.carbRatios.schedule.isEmpty,
!preset.bgTargets.targets.isEmpty
else {
return false
}

storage.save(preset.basalProfile, as: OpenAPS.Settings.basalProfile)
storage.save(preset.insulinSensitivities, as: OpenAPS.Settings.insulinSensitivities)
storage.save(preset.carbRatios, as: OpenAPS.Settings.carbRatios)
storage.save(preset.bgTargets, as: OpenAPS.Settings.bgTargets)

broadcaster.notify(BasalProfileObserver.self, on: .main) {
$0.basalProfileDidChange(preset.basalProfile)
}

broadcaster.notify(BGTargetsObserver.self, on: .main) {
$0.bgTargetsDidChange(preset.bgTargets)
}

return true
}

func deletePreset(id: String) {
var existingPresets = presets()
existingPresets.removeAll { $0.id == id }
savePresets(existingPresets)
}
}
1 change: 1 addition & 0 deletions Trio/Sources/Assemblies/StorageAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ final class StorageAssembly: Assembly {
container.register(SettingsManager.self) { r in BaseSettingsManager(resolver: r) }
container.register(Keychain.self) { _ in BaseKeychain() }
container.register(AlertHistoryStorage.self) { r in BaseAlertHistoryStorage(resolver: r) }
container.register(ProfilePresetStorage.self) { r in BaseProfilePresetStorage(resolver: r) }
}
}
55 changes: 55 additions & 0 deletions Trio/Sources/Models/ProfilePreset.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Foundation

struct ProfilePreset: JSON, Identifiable, Equatable {
let id: String
var name: String
var basalProfile: [BasalProfileEntry]
var insulinSensitivities: InsulinSensitivities
var carbRatios: CarbRatios
var bgTargets: BGTargets

init(
id: String = UUID().uuidString,
name: String,
basalProfile: [BasalProfileEntry],
insulinSensitivities: InsulinSensitivities,
carbRatios: CarbRatios,
bgTargets: BGTargets
) {
self.id = id
self.name = name
self.basalProfile = basalProfile
self.insulinSensitivities = insulinSensitivities
self.carbRatios = carbRatios
self.bgTargets = bgTargets
}

static func == (lhs: ProfilePreset, rhs: ProfilePreset) -> Bool {
lhs.id == rhs.id
}
}

extension ProfilePreset {
private enum CodingKeys: String, CodingKey {
case id
case name
case basalProfile = "basal_profile"
case insulinSensitivities = "insulin_sensitivities"
case carbRatios = "carb_ratios"
case bgTargets = "bg_targets"
}

var totalDailyBasal: Decimal {
basalProfile.enumerated().reduce(Decimal.zero) { result, entry in
let current = entry.element
let nextMinutes: Int
if entry.offset + 1 < basalProfile.count {
nextMinutes = basalProfile[entry.offset + 1].minutes
} else {
nextMinutes = 24 * 60
}
let durationHours = Decimal(nextMinutes - current.minutes) / 60
return result + current.rate * durationHours
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

enum ProfilePresets {
enum Config {}
}

protocol ProfilePresetsProvider: Provider {
func loadPresets() -> [ProfilePreset]
func saveCurrentAsPreset(name: String) -> ProfilePreset?
func activatePreset(_ preset: ProfilePreset) -> Bool
func deletePreset(id: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Foundation
import Swinject

extension ProfilePresets {
final class Provider: BaseProvider, ProfilePresetsProvider {
@Injected() private var profilePresetStorage: ProfilePresetStorage!

func loadPresets() -> [ProfilePreset] {
profilePresetStorage.presets()
}

func saveCurrentAsPreset(name: String) -> ProfilePreset? {
profilePresetStorage.saveCurrentProfileAsPreset(name: name)
}

func activatePreset(_ preset: ProfilePreset) -> Bool {
profilePresetStorage.activatePreset(preset)
}

func deletePreset(id: String) {
profilePresetStorage.deletePreset(id: id)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation
import Observation
import Swinject

extension ProfilePresets {
@Observable final class StateModel: BaseStateModel<Provider> {
var presets: [ProfilePreset] = []
var newPresetName: String = ""
var showingSaveDialog: Bool = false
var showingActivateConfirmation: Bool = false
var showingSaveError: Bool = false
var showingActivateError: Bool = false
var selectedPreset: ProfilePreset?
var units: GlucoseUnits = .mgdL

override func subscribe() {
units = settingsManager.settings.units
presets = provider.loadPresets()
}

func saveCurrentProfileAsPreset() {
guard !newPresetName.trimmingCharacters(in: .whitespaces).isEmpty else { return }
if let preset = provider.saveCurrentAsPreset(name: newPresetName.trimmingCharacters(in: .whitespaces)) {
presets.append(preset)
} else {
showingSaveError = true
}
newPresetName = ""
}

func activatePreset(_ preset: ProfilePreset) {
if !provider.activatePreset(preset) {
showingActivateError = true
}
}

func deletePreset(_ preset: ProfilePreset) {
provider.deletePreset(id: preset.id)
presets.removeAll { $0.id == preset.id }
}

func formattedBasalTotal(_ preset: ProfilePreset) -> String {
String(format: "%.2f", NSDecimalNumber(decimal: preset.totalDailyBasal).doubleValue)
}
}
}
Loading