|
|
@@ -1,11 +1,12 @@
|
|
|
import CoreData
|
|
|
+import Foundation
|
|
|
import SwiftUI
|
|
|
import Swinject
|
|
|
|
|
|
extension OverrideProfilesConfig {
|
|
|
struct RootView: BaseView {
|
|
|
let resolver: Resolver
|
|
|
-
|
|
|
+
|
|
|
@StateObject var state = StateModel()
|
|
|
@State private var isEditing = false
|
|
|
@State private var showAlert = false
|
|
|
@@ -14,24 +15,25 @@ extension OverrideProfilesConfig {
|
|
|
@State private var isEditSheetPresented: Bool = false
|
|
|
@State private var alertSring = ""
|
|
|
@State var isSheetPresented: Bool = false
|
|
|
-
|
|
|
+ @State private var originalPreset: OverridePresets?
|
|
|
+
|
|
|
@Environment(\.dismiss) var dismiss
|
|
|
@Environment(\.managedObjectContext) var moc
|
|
|
-
|
|
|
+
|
|
|
@FetchRequest(
|
|
|
entity: OverridePresets.entity(),
|
|
|
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)], predicate: NSPredicate(
|
|
|
format: "name != %@", "" as String
|
|
|
)
|
|
|
) var fetchedProfiles: FetchedResults<OverridePresets>
|
|
|
-
|
|
|
+
|
|
|
private var formatter: NumberFormatter {
|
|
|
let formatter = NumberFormatter()
|
|
|
formatter.numberStyle = .decimal
|
|
|
formatter.maximumFractionDigits = 0
|
|
|
return formatter
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private var glucoseFormatter: NumberFormatter {
|
|
|
let formatter = NumberFormatter()
|
|
|
formatter.numberStyle = .decimal
|
|
|
@@ -42,7 +44,7 @@ extension OverrideProfilesConfig {
|
|
|
formatter.roundingMode = .halfUp
|
|
|
return formatter
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
var presetPopover: some View {
|
|
|
Form {
|
|
|
nameSection(header: "Enter a name")
|
|
|
@@ -56,17 +58,18 @@ extension OverrideProfilesConfig {
|
|
|
state.profileName.isEmpty || fetchedProfiles
|
|
|
.contains(where: { $0.name == state.profileName })
|
|
|
)
|
|
|
-
|
|
|
+
|
|
|
Button("Cancel") {
|
|
|
isSheetPresented = false
|
|
|
}
|
|
|
+ .tint(.red)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
var editPresetPopover: some View {
|
|
|
Form {
|
|
|
- nameSection(header: "Keep or change name?")
|
|
|
+ nameSection(header: "Change name?")
|
|
|
settingsConfig(header: "Change settings")
|
|
|
Section {
|
|
|
Button("Save") {
|
|
|
@@ -74,15 +77,22 @@ extension OverrideProfilesConfig {
|
|
|
state.updatePreset(selectedPreset)
|
|
|
isEditSheetPresented = false
|
|
|
}
|
|
|
- .disabled(state.profileName.isEmpty)
|
|
|
-
|
|
|
+ .disabled(!hasChanges())
|
|
|
+
|
|
|
Button("Cancel") {
|
|
|
isEditSheetPresented = false
|
|
|
}
|
|
|
+ .tint(.red)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .onAppear {
|
|
|
+ if let preset = selectedPreset {
|
|
|
+ originalPreset = preset
|
|
|
+ state.populateSettings(from: preset)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@ViewBuilder private func nameSection(header: String) -> some View {
|
|
|
Section {
|
|
|
TextField("Profile override name", text: $state.profileName)
|
|
|
@@ -90,7 +100,7 @@ extension OverrideProfilesConfig {
|
|
|
Text(header)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@ViewBuilder private func settingsConfig(header: String) -> some View {
|
|
|
Section {
|
|
|
VStack {
|
|
|
@@ -121,7 +131,7 @@ extension OverrideProfilesConfig {
|
|
|
Text("minutes").foregroundColor(.secondary)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
HStack {
|
|
|
Toggle(isOn: $state.override_target) {
|
|
|
Text("Override Profile Target")
|
|
|
@@ -206,7 +216,7 @@ extension OverrideProfilesConfig {
|
|
|
Text(header)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@ViewBuilder private func settingsSection(header: String) -> some View {
|
|
|
Section(header: Text(header)) {
|
|
|
let percentString = Text("Override: \(Int(state.percentage))%")
|
|
|
@@ -222,7 +232,7 @@ extension OverrideProfilesConfig {
|
|
|
.smbMinutes != 0 ? Text("\(state.smbMinutes.formatted()) SMB Basal minutes") : Text("")
|
|
|
let maxMinutesUAMString = state
|
|
|
.uamMinutes != 0 ? Text("\(state.uamMinutes.formatted()) UAM Basal minutes") : Text("")
|
|
|
-
|
|
|
+
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
|
percentString
|
|
|
if targetString != Text("") { targetString }
|
|
|
@@ -238,7 +248,7 @@ extension OverrideProfilesConfig {
|
|
|
.font(.caption)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
var body: some View {
|
|
|
Form {
|
|
|
if state.presets.isNotEmpty {
|
|
|
@@ -252,7 +262,7 @@ extension OverrideProfilesConfig {
|
|
|
} label: {
|
|
|
Label("Delete", systemImage: "trash")
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
Button {
|
|
|
selectedPreset = preset
|
|
|
state.populateSettings(from: preset)
|
|
|
@@ -263,11 +273,11 @@ extension OverrideProfilesConfig {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- header: { Text("Activate profile override") }
|
|
|
- footer: { VStack(alignment: .leading) {
|
|
|
- Text("Swipe left on a profile to edit or delete it.")
|
|
|
- }
|
|
|
- }
|
|
|
+ header: { Text("Activate profile override") }
|
|
|
+ footer: { VStack(alignment: .leading) {
|
|
|
+ Text("Swipe left on a profile to edit or delete it.")
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
settingsConfig(header: "Insulin")
|
|
|
Section {
|
|
|
@@ -275,37 +285,37 @@ extension OverrideProfilesConfig {
|
|
|
Button("Start new Profile") {
|
|
|
showAlert.toggle()
|
|
|
alertSring = "\(state.percentage.formatted(.number)) %, " +
|
|
|
- (
|
|
|
- state.duration > 0 || !state
|
|
|
- ._indefinite ?
|
|
|
+ (
|
|
|
+ state.duration > 0 || !state
|
|
|
+ ._indefinite ?
|
|
|
+ (
|
|
|
+ state
|
|
|
+ .duration
|
|
|
+ .formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) +
|
|
|
+ " min."
|
|
|
+ ) :
|
|
|
+ NSLocalizedString(" infinite duration.", comment: "")
|
|
|
+ ) +
|
|
|
+ (
|
|
|
+ (state.target == 0 || !state.override_target) ? "" :
|
|
|
+ (" Target: " + state.target.formatted() + " " + state.units.rawValue + ".")
|
|
|
+ )
|
|
|
+ +
|
|
|
(
|
|
|
state
|
|
|
- .duration
|
|
|
- .formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) +
|
|
|
- " min."
|
|
|
- ) :
|
|
|
- NSLocalizedString(" infinite duration.", comment: "")
|
|
|
- ) +
|
|
|
- (
|
|
|
- (state.target == 0 || !state.override_target) ? "" :
|
|
|
- (" Target: " + state.target.formatted() + " " + state.units.rawValue + ".")
|
|
|
- )
|
|
|
- +
|
|
|
- (
|
|
|
- state
|
|
|
- .smbIsOff ?
|
|
|
+ .smbIsOff ?
|
|
|
+ NSLocalizedString(
|
|
|
+ " SMBs are disabled either by schedule or during the entire duration.",
|
|
|
+ comment: ""
|
|
|
+ ) : ""
|
|
|
+ )
|
|
|
+ +
|
|
|
+ "\n\n"
|
|
|
+ +
|
|
|
NSLocalizedString(
|
|
|
- " SMBs are disabled either by schedule or during the entire duration.",
|
|
|
+ "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.",
|
|
|
comment: ""
|
|
|
- ) : ""
|
|
|
- )
|
|
|
- +
|
|
|
- "\n\n"
|
|
|
- +
|
|
|
- NSLocalizedString(
|
|
|
- "Starting this override will change your Profiles and/or your Target Glucose used for looping during the entire selected duration. Tapping ”Start Profile” will start your new profile or edit your current active profile.",
|
|
|
- comment: ""
|
|
|
- )
|
|
|
+ )
|
|
|
}
|
|
|
.disabled(unChanged())
|
|
|
.buttonStyle(BorderlessButtonStyle())
|
|
|
@@ -330,7 +340,7 @@ extension OverrideProfilesConfig {
|
|
|
Button {
|
|
|
isSheetPresented = true
|
|
|
}
|
|
|
- label: { Text("Save as Profile") }
|
|
|
+ label: { Text("Save as Profile") }
|
|
|
.tint(.orange)
|
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
|
.buttonStyle(BorderlessButtonStyle())
|
|
|
@@ -342,12 +352,12 @@ extension OverrideProfilesConfig {
|
|
|
presetPopover
|
|
|
}
|
|
|
}
|
|
|
- footer: {
|
|
|
- Text(
|
|
|
- "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage."
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
+ footer: {
|
|
|
+ Text(
|
|
|
+ "Your profile basal insulin will be adjusted with the override percentage and your profile ISF and CR will be inversly adjusted with the percentage."
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
Button("Return to Normal") {
|
|
|
state.cancelProfile()
|
|
|
dismiss()
|
|
|
@@ -367,7 +377,7 @@ extension OverrideProfilesConfig {
|
|
|
.padding()
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@ViewBuilder private func profilesView(for preset: OverridePresets) -> some View {
|
|
|
let target = state.units == .mmolL ? (((preset.target ?? 0) as NSDecimalNumber) as Decimal)
|
|
|
.asMmolL : (preset.target ?? 0) as Decimal
|
|
|
@@ -385,7 +395,7 @@ extension OverrideProfilesConfig {
|
|
|
let crString = preset.cr ? "CR" : ""
|
|
|
let dash = crString != "" ? "/" : ""
|
|
|
let isfAndCRstring = isfString + dash + crString
|
|
|
-
|
|
|
+
|
|
|
if name != "" {
|
|
|
HStack {
|
|
|
VStack {
|
|
|
@@ -421,17 +431,51 @@ extension OverrideProfilesConfig {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private func unChanged() -> Bool {
|
|
|
let defaultProfile = state.percentage == 100 && !state.override_target && !state.advancedSettings
|
|
|
let noDurationSpecified = !state._indefinite && state.duration == 0
|
|
|
let targetZeroWithOverride = state.override_target && state.target == 0
|
|
|
let allSettingsDefault = state.percentage == 100 && !state.override_target && !state.smbIsOff && !state
|
|
|
.smbIsScheduledOff && state.smbMinutes == state.defaultSmbMinutes && state.uamMinutes == state.defaultUamMinutes
|
|
|
-
|
|
|
+
|
|
|
return defaultProfile || noDurationSpecified || targetZeroWithOverride || allSettingsDefault
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ private func hasChanges() -> Bool {
|
|
|
+ guard let originalPreset = originalPreset else { return false }
|
|
|
+
|
|
|
+ let targetInStateUnits: Decimal
|
|
|
+ let targetInPresetUnits: Decimal
|
|
|
+
|
|
|
+ if state.units == .mmolL {
|
|
|
+ targetInStateUnits = state.target
|
|
|
+ targetInPresetUnits = Decimal(Double(truncating: originalPreset.target ?? 0) * 0.0555)
|
|
|
+ } else {
|
|
|
+ targetInStateUnits = state.target
|
|
|
+ targetInPresetUnits = (originalPreset.target ?? 0) as Decimal
|
|
|
+ }
|
|
|
+
|
|
|
+ let hasChanges = state.profileName != originalPreset.name ||
|
|
|
+ state.percentage != originalPreset.percentage ||
|
|
|
+ state.duration != (originalPreset.duration ?? 0) as Decimal ||
|
|
|
+ state._indefinite != originalPreset.indefinite ||
|
|
|
+ state.override_target != (originalPreset.target != nil) ||
|
|
|
+ (state.override_target && targetInStateUnits != targetInPresetUnits) ||
|
|
|
+ //state.advancedSettings != originalPreset.advancedSettings ||
|
|
|
+ state.smbIsOff != originalPreset.smbIsOff ||
|
|
|
+ state.smbIsScheduledOff != originalPreset.smbIsScheduledOff ||
|
|
|
+ state.isf != originalPreset.isf ||
|
|
|
+ state.cr != originalPreset.cr ||
|
|
|
+ state.smbMinutes != (originalPreset.smbMinutes ?? 0) as Decimal ||
|
|
|
+ state.uamMinutes != (originalPreset.uamMinutes ?? 0) as Decimal ||
|
|
|
+ state.isfAndCr != originalPreset.isfAndCr ||
|
|
|
+ state.start != (originalPreset.start ?? 0) as Decimal ||
|
|
|
+ state.end != (originalPreset.end ?? 0) as Decimal
|
|
|
+
|
|
|
+ return hasChanges
|
|
|
+ }
|
|
|
+
|
|
|
private func removeProfile(at offsets: IndexSet) {
|
|
|
for index in offsets {
|
|
|
let language = fetchedProfiles[index]
|
|
|
@@ -454,7 +498,7 @@ extension OverrideProfilesConfig.StateModel {
|
|
|
_indefinite = preset.indefinite
|
|
|
override_target = preset.target != nil
|
|
|
if let targetValue = preset.target as Decimal? {
|
|
|
- target = units == .mmolL ? targetValue.asMmolL : targetValue
|
|
|
+ target = units == .mmolL ? Decimal(Double(truncating: targetValue as NSNumber) * 0.0555) : targetValue
|
|
|
} else {
|
|
|
target = 0
|
|
|
}
|