|
|
@@ -54,8 +54,10 @@ extension CGMSettings {
|
|
|
if cgmCurrent.type != .none {
|
|
|
if cgmCurrent.type == .nightscout {
|
|
|
nightscoutSection
|
|
|
- } else {
|
|
|
- customCGMSection
|
|
|
+ } else if cgmCurrent.type == .xdrip {
|
|
|
+ xDripConfigurationSection
|
|
|
+ } else if cgmCurrent.type == .simulator {
|
|
|
+ simulatorConfigurationSection
|
|
|
}
|
|
|
|
|
|
if let appURL = cgmCurrent.type.appURL {
|
|
|
@@ -97,7 +99,7 @@ extension CGMSettings {
|
|
|
}
|
|
|
.safeAreaInset(
|
|
|
edge: .bottom,
|
|
|
- spacing: 30
|
|
|
+ spacing: 0
|
|
|
) {
|
|
|
stickyDeleteButton
|
|
|
}
|
|
|
@@ -133,7 +135,7 @@ extension CGMSettings {
|
|
|
"To configure your CGM, tap the button below. In the form that opens, enter your Nightscout credentials to connect to your instance." :
|
|
|
"Tap the button below to open your Nightscout instance in your iPhone's default browser."
|
|
|
).font(.footnote)
|
|
|
- .foregroundColor(.secondary)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
.lineLimit(nil)
|
|
|
.padding(.vertical)
|
|
|
}
|
|
|
@@ -174,36 +176,30 @@ extension CGMSettings {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- var customCGMSection: some View {
|
|
|
- Group {
|
|
|
- Section(
|
|
|
- header: Text("Configuration"),
|
|
|
- content: {
|
|
|
- if cgmCurrent.type == .xdrip {
|
|
|
- VStack(alignment: .leading) {
|
|
|
- if let cgmTransmitterDeviceAddress = UserDefaults.standard
|
|
|
- .cgmTransmitterDeviceAddress
|
|
|
- {
|
|
|
- Text("CGM address :").padding(.top)
|
|
|
- Text(cgmTransmitterDeviceAddress)
|
|
|
- } else {
|
|
|
- Text("CGM is not used as heartbeat.").padding(.top)
|
|
|
- }
|
|
|
-
|
|
|
- HStack(alignment: .center) {
|
|
|
- Text(
|
|
|
- "A heartbeat tells Trio to start a loop cycle. This is required for closed loop."
|
|
|
- )
|
|
|
- .font(.footnote)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- .lineLimit(nil)
|
|
|
- Spacer()
|
|
|
- }.padding(.vertical)
|
|
|
- }
|
|
|
- } else if cgmCurrent.type == .simulator {
|
|
|
- simulatorConfigurationSection
|
|
|
+ var xDripConfigurationSection: some View {
|
|
|
+ Section(
|
|
|
+ header: Text("Configuration"),
|
|
|
+ content: {
|
|
|
+ VStack(alignment: .leading) {
|
|
|
+ if let cgmTransmitterDeviceAddress = UserDefaults.standard
|
|
|
+ .cgmTransmitterDeviceAddress
|
|
|
+ {
|
|
|
+ Text("CGM address :").padding(.top)
|
|
|
+ Text(cgmTransmitterDeviceAddress)
|
|
|
+ } else {
|
|
|
+ Text("CGM is not used as heartbeat.").padding(.top)
|
|
|
}
|
|
|
|
|
|
+ HStack(alignment: .center) {
|
|
|
+ Text(
|
|
|
+ "A heartbeat tells Trio to start a loop cycle. This is required for closed loop."
|
|
|
+ )
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+ Spacer()
|
|
|
+ }.padding(.vertical)
|
|
|
+
|
|
|
if let link = cgmCurrent.type.externalLink {
|
|
|
Button {
|
|
|
UIApplication.shared.open(link, options: [:], completionHandler: nil)
|
|
|
@@ -217,121 +213,205 @@ extension CGMSettings {
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
}
|
|
|
}
|
|
|
- ).listRowBackground(Color.chart)
|
|
|
- }
|
|
|
+ }
|
|
|
+ ).listRowBackground(Color.chart)
|
|
|
}
|
|
|
|
|
|
var simulatorConfigurationSection: some View {
|
|
|
- VStack(alignment: .leading, spacing: 16) {
|
|
|
- Text("Simulator Settings")
|
|
|
- .font(.headline)
|
|
|
- .padding(.top, 8)
|
|
|
+ Group {
|
|
|
+ Section(
|
|
|
+ header: Text("Configuration"),
|
|
|
+ content: {
|
|
|
+ VStack(alignment: .leading, spacing: 12) {
|
|
|
+ Text("CGM is not used as heartbeat.").lineLimit(nil)
|
|
|
+ .padding(.top)
|
|
|
|
|
|
- Toggle(isOn: $produceStaleValues) {
|
|
|
- VStack(alignment: .leading) {
|
|
|
- Text("Produce Stale Values")
|
|
|
- }
|
|
|
- }
|
|
|
- .padding(.vertical, 4)
|
|
|
- .onChange(of: produceStaleValues) { _, newValue in
|
|
|
- UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_ProduceStaleValues")
|
|
|
- }
|
|
|
+ Text("Glucose trace WILL NOT be affected by any insulin or carb entries.").lineLimit(nil)
|
|
|
+ .bold()
|
|
|
+ }
|
|
|
|
|
|
- if !produceStaleValues {
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
- Text("Center Value: \(Int(centerValue)) mg/dL")
|
|
|
- Text("The average glucose level around which values will oscillate.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- Slider(value: $centerValue, in: 80 ... 200, step: 1)
|
|
|
- .accentColor(.accentColor)
|
|
|
- .onChange(of: centerValue) { _, newValue in
|
|
|
- UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_CenterValue")
|
|
|
- }
|
|
|
- }
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
+ Text(
|
|
|
+ "The simulator creates a wave-like pattern that mimics natural glucose fluctuations throughout the day."
|
|
|
+ ).lineLimit(nil)
|
|
|
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
- Text("Amplitude: ±\(Int(amplitude)) mg/dL")
|
|
|
- Text("Range: \(Int(centerValue - amplitude))–\(Int(centerValue + amplitude)) mg/dL")
|
|
|
- .font(.subheadline)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- Text("The maximum deviation from the center value. Higher values create wider swings.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- Slider(value: $amplitude, in: 10 ... 100, step: 5)
|
|
|
- .accentColor(.accentColor)
|
|
|
- .onChange(of: amplitude) { _, newValue in
|
|
|
- UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_Amplitude")
|
|
|
- }
|
|
|
+ Text("Configuration changes will take effect on the next glucose reading.")
|
|
|
+ .padding(.bottom).lineLimit(nil)
|
|
|
+ }.foregroundStyle(Color.secondary).font(.footnote)
|
|
|
}
|
|
|
+ ).listRowBackground(Color.chart)
|
|
|
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
- Text("Period: \(Int(period / 3600)) hours")
|
|
|
- Text("The time it takes to complete one full cycle from high to low and back to high.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- Slider(value: $period, in: 3600 ... 21600, step: 1800)
|
|
|
- .accentColor(.accentColor)
|
|
|
- .onChange(of: period) { _, newValue in
|
|
|
- UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_Period")
|
|
|
+ Section {
|
|
|
+ VStack(alignment: .leading, spacing: 10) {
|
|
|
+ Toggle(isOn: $produceStaleValues) {
|
|
|
+ VStack(alignment: .leading) {
|
|
|
+ Text("Produce Stale Values")
|
|
|
}
|
|
|
- }
|
|
|
+ }
|
|
|
+ .padding(.top)
|
|
|
+ .onChange(of: produceStaleValues) { _, newValue in
|
|
|
+ UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_ProduceStaleValues")
|
|
|
+ }
|
|
|
|
|
|
- VStack(alignment: .leading, spacing: 8) {
|
|
|
- Text("Noise: ±\(Int(noiseAmplitude)) mg/dL")
|
|
|
- Text("Random variation added to each reading to simulate real-world sensor noise.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- Slider(value: $noiseAmplitude, in: 0 ... 20, step: 1)
|
|
|
- .accentColor(.accentColor)
|
|
|
- .onChange(of: noiseAmplitude) { _, newValue in
|
|
|
- UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_NoiseAmplitude")
|
|
|
- }
|
|
|
+ Text(
|
|
|
+ "When stale values are enabled, the simulator will repeatedly output the last generated glucose value."
|
|
|
+ )
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+ .padding(.bottom)
|
|
|
}
|
|
|
- } else {
|
|
|
- Text("When stale values are enabled, the simulator will repeatedly output the last generated glucose value.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- .padding(.vertical, 8)
|
|
|
- }
|
|
|
+ }.listRowBackground(Color.chart)
|
|
|
+
|
|
|
+ if !produceStaleValues {
|
|
|
+ Section {
|
|
|
+ VStack(alignment: .leading, spacing: 10) {
|
|
|
+ HStack {
|
|
|
+ Text("Center Value:").bold()
|
|
|
+
|
|
|
+ Spacer()
|
|
|
+
|
|
|
+ Text(state.units == .mgdL ? centerValue.description : centerValue.formattedAsMmolL).bold()
|
|
|
|
|
|
- Button("Reset to Defaults") {
|
|
|
- centerValue = OscillatingGenerator.Defaults.centerValue
|
|
|
- amplitude = OscillatingGenerator.Defaults.amplitude
|
|
|
- period = OscillatingGenerator.Defaults.period
|
|
|
- noiseAmplitude = OscillatingGenerator.Defaults.noiseAmplitude
|
|
|
- produceStaleValues = OscillatingGenerator.Defaults.produceStaleValues
|
|
|
- saveSimulatorSettings()
|
|
|
+ Text(state.units.rawValue).foregroundStyle(Color.secondary)
|
|
|
+ }.padding(.top)
|
|
|
+
|
|
|
+ Slider(value: $centerValue, in: 80 ... 200, step: 1)
|
|
|
+ .accentColor(.accentColor)
|
|
|
+ .onChange(of: centerValue) { _, newValue in
|
|
|
+ UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_CenterValue")
|
|
|
+ }
|
|
|
+ .padding(.vertical)
|
|
|
+
|
|
|
+ Text("The average glucose level around which values will oscillate.")
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+ .padding(.bottom)
|
|
|
+ }
|
|
|
+ }.listRowBackground(Color.chart)
|
|
|
+
|
|
|
+ Section {
|
|
|
+ VStack(alignment: .leading, spacing: 10) {
|
|
|
+ HStack {
|
|
|
+ Text("Amplitude:").bold()
|
|
|
+
|
|
|
+ Spacer()
|
|
|
+
|
|
|
+ Text("±")
|
|
|
+ Text(state.units == .mgdL ? amplitude.description : amplitude.formattedAsMmolL).bold()
|
|
|
+
|
|
|
+ Text(state.units.rawValue).foregroundStyle(Color.secondary)
|
|
|
+ }.padding(.top)
|
|
|
+
|
|
|
+ Slider(value: $amplitude, in: 10 ... 100, step: 5)
|
|
|
+ .accentColor(.accentColor)
|
|
|
+ .onChange(of: amplitude) { _, newValue in
|
|
|
+ UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_Amplitude")
|
|
|
+ }
|
|
|
+ .padding(.vertical)
|
|
|
+
|
|
|
+ Text(
|
|
|
+ "Range: \(state.units == .mgdL ? (centerValue - amplitude).description : (centerValue - amplitude).formattedAsMmolL)–\(state.units == .mgdL ? (centerValue + amplitude).description : (centerValue + amplitude).formattedAsMmolL) \(state.units.rawValue)"
|
|
|
+ )
|
|
|
+ .bold()
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+
|
|
|
+ Text("The maximum deviation from the center value. Higher values create wider swings.")
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+ .padding(.bottom)
|
|
|
+ }
|
|
|
+ }.listRowBackground(Color.chart)
|
|
|
+
|
|
|
+ Section {
|
|
|
+ VStack(alignment: .leading, spacing: 10) {
|
|
|
+ HStack {
|
|
|
+ Text("Period:").bold()
|
|
|
+
|
|
|
+ Spacer()
|
|
|
+
|
|
|
+ Text(Int(period / 3600).description).bold()
|
|
|
+
|
|
|
+ Text("hours").foregroundStyle(Color.secondary)
|
|
|
+ }.padding(.top)
|
|
|
+
|
|
|
+ Slider(value: $period, in: 3600 ... 21600, step: 1800)
|
|
|
+ .accentColor(.accentColor)
|
|
|
+ .onChange(of: period) { _, newValue in
|
|
|
+ UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_Period")
|
|
|
+ }
|
|
|
+ .padding(.vertical)
|
|
|
+
|
|
|
+ Text("The time it takes to complete one full cycle from high to low and back to high.")
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+ .padding(.bottom)
|
|
|
+ }
|
|
|
+ }.listRowBackground(Color.chart)
|
|
|
+
|
|
|
+ Section {
|
|
|
+ VStack(alignment: .leading, spacing: 10) {
|
|
|
+ HStack {
|
|
|
+ Text("Noise:").bold()
|
|
|
+
|
|
|
+ Spacer()
|
|
|
+
|
|
|
+ Text("±")
|
|
|
+
|
|
|
+ Text(state.units == .mgdL ? noiseAmplitude.description : noiseAmplitude.formattedAsMmolL).bold()
|
|
|
+
|
|
|
+ Text(state.units.rawValue).foregroundStyle(Color.secondary)
|
|
|
+ }.padding(.top)
|
|
|
+
|
|
|
+ Slider(value: $noiseAmplitude, in: 0 ... 20, step: 1)
|
|
|
+ .accentColor(.accentColor)
|
|
|
+ .onChange(of: noiseAmplitude) { _, newValue in
|
|
|
+ UserDefaults.standard.set(newValue, forKey: "GlucoseSimulator_NoiseAmplitude")
|
|
|
+ }
|
|
|
+ .padding(.vertical)
|
|
|
+
|
|
|
+ Text("Random variation added to each reading to simulate real-world sensor noise.")
|
|
|
+ .font(.footnote)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(nil)
|
|
|
+ .padding(.bottom)
|
|
|
+ }
|
|
|
+ }.listRowBackground(Color.chart)
|
|
|
}
|
|
|
- .buttonStyle(.bordered)
|
|
|
- .padding(.top, 8)
|
|
|
-
|
|
|
- Text("Changes will take effect on the next glucose reading.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- .padding(.top, 4)
|
|
|
-
|
|
|
- Text("Glucose trace WILL NOT be affected by any insulin or carb entries.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- .padding(.top, 4)
|
|
|
-
|
|
|
- Text("The simulator creates a wave-like pattern that mimics natural glucose fluctuations throughout the day.")
|
|
|
- .font(.caption)
|
|
|
- .foregroundColor(.secondary)
|
|
|
- .padding(.top, 4)
|
|
|
- }
|
|
|
- .padding(.vertical, 8)
|
|
|
+
|
|
|
+ Section {
|
|
|
+ Button(action: {
|
|
|
+ centerValue = OscillatingGenerator.Defaults.centerValue
|
|
|
+ amplitude = OscillatingGenerator.Defaults.amplitude
|
|
|
+ period = OscillatingGenerator.Defaults.period
|
|
|
+ noiseAmplitude = OscillatingGenerator.Defaults.noiseAmplitude
|
|
|
+ produceStaleValues = OscillatingGenerator.Defaults.produceStaleValues
|
|
|
+ saveSimulatorSettings()
|
|
|
+ }, label: {
|
|
|
+ Text("Reset to Defaults")
|
|
|
+
|
|
|
+ })
|
|
|
+ .frame(maxWidth: .infinity, alignment: .center)
|
|
|
+ .tint(.white)
|
|
|
+ }.listRowBackground(Color.accentColor)
|
|
|
+
|
|
|
+ }.listSectionSpacing(sectionSpacing)
|
|
|
}
|
|
|
|
|
|
var stickyDeleteButton: some View {
|
|
|
ZStack {
|
|
|
Rectangle()
|
|
|
- .frame(width: UIScreen.main.bounds.width, height: 65)
|
|
|
+ .frame(width: UIScreen.main.bounds.width, height: 120)
|
|
|
.foregroundStyle(colorScheme == .dark ? Color.bgDarkerDarkBlue : Color.white)
|
|
|
.background(.thinMaterial)
|
|
|
.opacity(0.8)
|
|
|
.clipShape(Rectangle())
|
|
|
+ .padding(.bottom, -55)
|
|
|
|
|
|
Button(action: {
|
|
|
shouldDisplayDeletionConfirmation.toggle()
|