DynamicSettingsRootView.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import SwiftUI
  2. import Swinject
  3. extension DynamicSettings {
  4. struct RootView: BaseView {
  5. let resolver: Resolver
  6. @StateObject var state = StateModel()
  7. @State private var shouldDisplayHint: Bool = false
  8. @State var hintDetent = PresentationDetent.large
  9. @State var selectedVerboseHint: AnyView?
  10. @State var hintLabel: String?
  11. @State private var decimalPlaceholder: Decimal = 0.0
  12. @State private var booleanPlaceholder: Bool = false
  13. private var conversionFormatter: NumberFormatter {
  14. let formatter = NumberFormatter()
  15. formatter.numberStyle = .decimal
  16. formatter.maximumFractionDigits = 1
  17. return formatter
  18. }
  19. @Environment(\.colorScheme) var colorScheme
  20. @Environment(AppState.self) var appState
  21. private var formatter: NumberFormatter {
  22. let formatter = NumberFormatter()
  23. formatter.numberStyle = .decimal
  24. return formatter
  25. }
  26. private var glucoseFormatter: NumberFormatter {
  27. let formatter = NumberFormatter()
  28. formatter.numberStyle = .decimal
  29. if state.units == .mmolL {
  30. formatter.maximumFractionDigits = 1
  31. } else { formatter.maximumFractionDigits = 0 }
  32. formatter.roundingMode = .halfUp
  33. return formatter
  34. }
  35. var body: some View {
  36. List {
  37. Section(
  38. header: Text("Dynamic Insulin Sensitivity"),
  39. content: {
  40. VStack(alignment: .leading) {
  41. Picker(
  42. selection: $state.dynamicSensitivityType,
  43. label: Text("Dynamic ISF").multilineTextAlignment(.leading)
  44. ) {
  45. ForEach(DynamicSensitivityType.allCases) { selection in
  46. Text(selection.displayName).tag(selection)
  47. }
  48. }
  49. .disabled(!state.hasValidTDD)
  50. .padding(.top)
  51. HStack(alignment: .center) {
  52. let miniHintText = state.hasValidTDD ?
  53. String(
  54. localized: "Dynamically adjust insulin sensitivity using Dynamic Ratio rather than Autosens Ratio."
  55. ) :
  56. String(
  57. localized: "Trio has only been actively used and looping for less than seven days. Cannot enable dynamic ISF."
  58. )
  59. let miniHintTextColorForDisabled: Color = colorScheme == .dark ? .orange :
  60. .accentColor
  61. let miniHintTextColor: Color = state.hasValidTDD ? .secondary : miniHintTextColorForDisabled
  62. Text(miniHintText)
  63. .font(.footnote)
  64. .foregroundColor(miniHintTextColor)
  65. .lineLimit(nil)
  66. Spacer()
  67. Button(
  68. action: {
  69. hintLabel = String(localized: "Time in Range Chart Style")
  70. selectedVerboseHint =
  71. AnyView(
  72. VStack(alignment: .leading, spacing: 10) {
  73. Text("Default: Disabled").bold()
  74. Text(
  75. "Enabling this feature allows Trio to calculate a new Insulin Sensitivity Factor with each loop cycle dynamically. Trio offers two dynamic formulas:"
  76. )
  77. VStack(alignment: .leading, spacing: 10) {
  78. Text("Logarithmic Dynamic ISF").bold()
  79. Text(
  80. "Enabling this feature allows Trio to calculate a new Insulin Sensitivity Factor with each loop cycle by considering your current glucose, the weighted total daily dose of insulin, the set adjustment factor, and a few other data points. This helps tailor your insulin response more accurately in real time."
  81. )
  82. Text(
  83. "Dynamic ISF produces a Dynamic Ratio, replacing the Autosens Ratio, determining how much your profile ISF will be adjusted every loop cycle, ensuring it stays within safe limits set by your Autosens Min/Max settings. It provides more precise insulin dosing by responding to changes in insulin needs throughout the day."
  84. )
  85. Text(
  86. "You can influence the adjustments made by Dynamic ISF primarily by adjusting Autosens Max, Autosens Min, and Adjustment Factor. Other settings also influence Dynamic ISF's response, such as Glucose Target, Profile ISF, Peak Insulin Time, and Weighted Average of TDD."
  87. )
  88. Text(
  89. "Warning: Before adjusting these settings, make sure you are fully aware of the impact those changes will have."
  90. )
  91. .bold()
  92. }
  93. VStack(alignment: .leading, spacing: 10) {
  94. Text("Sigmoid Dynamic ISF").bold()
  95. Text(
  96. "Turning on the Sigmoid Formula setting alters how your Dynamic Ratio, and thus your New ISF, are calculated using a sigmoid curve."
  97. )
  98. Text(
  99. "The curve's steepness is influenced by the Adjustment Factor, while the Autosens Min/Max settings determine the limits of the ratio adjustment, which can also influence the steepness of the sigmoid curve."
  100. )
  101. Text(
  102. "When using the Sigmoid Formula, the weighted Total Daily Dose has a much lower impact on the dynamic adjustments to sensitivity."
  103. )
  104. Text(
  105. "Careful tuning is essential to avoid overly aggressive insulin changes."
  106. )
  107. Text(
  108. "It is not recommended to set Autosens Max above 150% to maintain safe insulin dosing."
  109. )
  110. Text(
  111. "There has been no empirical data analysis to support the use of the Sigmoid Formula for dynamic sensitivity determination."
  112. ).bold()
  113. }
  114. }
  115. )
  116. shouldDisplayHint.toggle()
  117. },
  118. label: {
  119. HStack {
  120. Image(systemName: "questionmark.circle")
  121. }
  122. }
  123. ).buttonStyle(BorderlessButtonStyle())
  124. }.padding(.top)
  125. }.padding(.bottom)
  126. }
  127. ).listRowBackground(Color.chart)
  128. if state.dynamicSensitivityType != .disabled {
  129. if state.dynamicSensitivityType == .logarithmic {
  130. SettingInputSection(
  131. decimalValue: $state.adjustmentFactor,
  132. booleanValue: $booleanPlaceholder,
  133. shouldDisplayHint: $shouldDisplayHint,
  134. selectedVerboseHint: Binding(
  135. get: { selectedVerboseHint },
  136. set: {
  137. selectedVerboseHint = $0.map { AnyView($0) }
  138. hintLabel = String(localized: "Adjustment Factor (AF)")
  139. }
  140. ),
  141. // TODO?: include conditional links to Desmos logarithmic graphs based on which .glucose setting is used
  142. units: state.units,
  143. type: .decimal("adjustmentFactor"),
  144. label: String(localized: "Adjustment Factor (AF)"),
  145. miniHint: String(localized: "Alter the rate of Dynamic ISF (Sensitivity) adjustments."),
  146. verboseHint:
  147. VStack(alignment: .leading, spacing: 10) {
  148. Text("Default: 80%").bold()
  149. Text(
  150. "The Adjustment Factor (AF) allows you to control how quickly and effectively Dynamic ISF responds to changes in glucose levels."
  151. )
  152. Text(
  153. "Adjusting this value not only can adjust how quickly your sensitivity will respond to changing glucose readings, but also at what glucose readings you reach your Autosens Max/Min limits."
  154. )
  155. Text(
  156. "Increasing this setting can make ISF adjustments quicker, but will also change the glucose value that coincides with the ISF used at your Autosens Max and Autosens Min limits. Likewise, decreasing this setting can make ISF adjustments slower and will also change the glucose value that coincides with the ISF used when it reaches the Autosens Max and Autosens Min limits. It is best to utilize the Desmos graphs from TrioDocs.org to optimize all Dynamic Settings."
  157. )
  158. }
  159. )
  160. } else {
  161. SettingInputSection(
  162. decimalValue: $state.adjustmentFactorSigmoid,
  163. booleanValue: $booleanPlaceholder,
  164. shouldDisplayHint: $shouldDisplayHint,
  165. selectedVerboseHint: Binding(
  166. get: { selectedVerboseHint },
  167. set: {
  168. selectedVerboseHint = $0.map { AnyView($0) }
  169. hintLabel = String(localized: "Sigmoid Adjustment Factor")
  170. }
  171. ),
  172. units: state.units,
  173. type: .decimal("adjustmentFactorSigmoid"),
  174. label: String(localized: "Sigmoid Adjustment Factor"),
  175. miniHint: String(localized: "Alter the rate of dynamic sensitivity adjustments for Sigmoid."),
  176. verboseHint:
  177. VStack(alignment: .leading, spacing: 10) {
  178. Text("Default: 50%").bold()
  179. Text(
  180. "The Sigmoid Adjustment Factor (AF) allows you to control how quickly Sigmoid Dynamic ISF responds to changes in glucose levels and at what glucose value you will reach your Autosens Max and Autosens Min limits."
  181. )
  182. Text(
  183. "Sigmoid Adjustment Factor influences both how fast your ISF values will change and how quickly you will reach your Autosens Max and Min limits set. Increasing Sigmoid Adjustment Factor increases the rate of change of your ISF and reduces the range of glucose values between your Autosens Max and Min limits."
  184. )
  185. Text(
  186. "This setting allows for a more responsive system, but the effects are restricted by the Autosens Min/Max settings."
  187. )
  188. Text(
  189. "Due to how the curve is calculated when using the Sigmoid Formula, increasing this setting has a different impact on the steepness of the curve than in the standard logarithmic Dynamic ISF calculation. Use caution when adjusting this setting."
  190. )
  191. }
  192. )
  193. }
  194. SettingInputSection(
  195. decimalValue: $state.weightPercentage,
  196. booleanValue: $booleanPlaceholder,
  197. shouldDisplayHint: $shouldDisplayHint,
  198. selectedVerboseHint: Binding(
  199. get: { selectedVerboseHint },
  200. set: {
  201. selectedVerboseHint = $0.map { AnyView($0) }
  202. hintLabel = String(localized: "Weighted Average of TDD")
  203. }
  204. ),
  205. units: state.units,
  206. type: .decimal("weightPercentage"),
  207. label: String(localized: "Weighted Average of TDD"),
  208. miniHint: String(localized: "Weight of 24-hr TDD against 10-day TDD."),
  209. verboseHint:
  210. VStack(alignment: .leading, spacing: 10) {
  211. Text("Default: 35%").bold()
  212. Text(
  213. "This setting adjusts how much weight is given to your recent total daily insulin dose when calculating Dynamic ISF and Dynamic CR."
  214. )
  215. Text(
  216. "At the default setting, 35% of the calculation is based on the last 24 hours of insulin use, with the remaining 65% considering the last 10 days of data."
  217. )
  218. Text("Setting this to 100% means only the past 24 hours will be used.")
  219. Text("A lower value smooths out these variations for more stability.")
  220. }
  221. )
  222. SettingInputSection(
  223. decimalValue: $decimalPlaceholder,
  224. booleanValue: $state.tddAdjBasal,
  225. shouldDisplayHint: $shouldDisplayHint,
  226. selectedVerboseHint: Binding(
  227. get: { selectedVerboseHint },
  228. set: {
  229. selectedVerboseHint = $0.map { AnyView($0) }
  230. hintLabel = String(localized: "Adjust Basal")
  231. }
  232. ),
  233. units: state.units,
  234. type: .boolean,
  235. label: String(localized: "Adjust Basal"),
  236. miniHint: String(localized: "Use Dynamic Ratio to adjust basal rates."),
  237. verboseHint: VStack(alignment: .leading, spacing: 10) {
  238. Text("Default: OFF").bold()
  239. Text(
  240. "Turn this setting on to give basal adjustments more agility. Keep this setting off if your basal needs are not highly variable."
  241. )
  242. Text(
  243. "Enabling Adjust Basal replaces the standard Autosens Ratio calculation with its own Autosens Ratio calculated as such:"
  244. )
  245. Text("Autosens Ratio =\n(Weighted Average of TDD) / (10-day Average of TDD)")
  246. Text("New Basal Profile =\n(Current Basal Profile) × (Autosens Ratio)")
  247. },
  248. headerText: String(localized: "Dynamic-dependent Features")
  249. )
  250. }
  251. }
  252. .listSectionSpacing(sectionSpacing)
  253. .sheet(isPresented: $shouldDisplayHint) {
  254. SettingInputHintView(
  255. hintDetent: $hintDetent,
  256. shouldDisplayHint: $shouldDisplayHint,
  257. hintLabel: hintLabel ?? "",
  258. hintText: selectedVerboseHint ?? AnyView(EmptyView()),
  259. sheetTitle: String(localized: "Help", comment: "Help sheet title")
  260. )
  261. }
  262. .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
  263. .onAppear(perform: configureView)
  264. .navigationBarTitle("Dynamic Settings")
  265. .navigationBarTitleDisplayMode(.automatic)
  266. }
  267. }
  268. }