ISFEditorRootView.swift 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import SwiftUI
  2. import Swinject
  3. extension ISFEditor {
  4. struct RootView: BaseView {
  5. let resolver: Resolver
  6. @StateObject var state = StateModel()
  7. @State private var editMode = EditMode.inactive
  8. @Environment(\.colorScheme) var colorScheme
  9. var color: LinearGradient {
  10. colorScheme == .dark ? LinearGradient(
  11. gradient: Gradient(colors: [
  12. Color("Background_1"),
  13. Color("Background_1"),
  14. Color("Background_2")
  15. // Color("Background_1")
  16. ]),
  17. startPoint: .top,
  18. endPoint: .bottom
  19. )
  20. :
  21. LinearGradient(
  22. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  23. startPoint: .top,
  24. endPoint: .bottom
  25. )
  26. }
  27. private var dateFormatter: DateFormatter {
  28. let formatter = DateFormatter()
  29. formatter.timeZone = TimeZone(secondsFromGMT: 0)
  30. formatter.timeStyle = .short
  31. return formatter
  32. }
  33. private var rateFormatter: NumberFormatter {
  34. let formatter = NumberFormatter()
  35. formatter.numberStyle = .decimal
  36. formatter.maximumFractionDigits = 2
  37. return formatter
  38. }
  39. var body: some View {
  40. Form {
  41. if let autotune = state.autotune, !state.settingsManager.settings.onlyAutotuneBasals {
  42. Section(header: Text("Autotune")) {
  43. HStack {
  44. Text("Calculated Sensitivity")
  45. Spacer()
  46. if state.units == .mmolL {
  47. Text(rateFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
  48. } else {
  49. Text(rateFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
  50. }
  51. Text(state.units.rawValue + "/U").foregroundColor(.secondary)
  52. }
  53. }
  54. }
  55. if let newISF = state.autosensISF {
  56. Section(
  57. header: !state.settingsManager.preferences
  58. .useNewFormula ? Text("Autosens") : Text("Dynamic Sensitivity")
  59. ) {
  60. let dynamicRatio = state.provider.suggestion?.sensitivityRatio ?? 0
  61. let dynamicISF = state.provider.suggestion?.isf ?? 0
  62. HStack {
  63. Text("Sensitivity Ratio")
  64. Spacer()
  65. Text(
  66. rateFormatter
  67. .string(from: (
  68. !state.settingsManager.preferences.useNewFormula ? state
  69. .autosensRatio : dynamicRatio
  70. ) as NSNumber) ?? "1"
  71. )
  72. }
  73. HStack {
  74. Text("Calculated Sensitivity")
  75. Spacer()
  76. Text(
  77. rateFormatter
  78. .string(from: (
  79. !state.settingsManager.preferences
  80. .useNewFormula ? newISF : dynamicISF
  81. ) as NSNumber) ?? "0"
  82. )
  83. Text(state.units.rawValue + "/U").foregroundColor(.secondary)
  84. }
  85. }
  86. }
  87. Section(header: Text("Schedule")) {
  88. list
  89. addButton
  90. }
  91. Section {
  92. Button {
  93. let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
  94. impactHeavy.impactOccurred()
  95. state.save()
  96. }
  97. label: {
  98. Text("Save")
  99. }
  100. .disabled(state.items.isEmpty)
  101. }
  102. }
  103. .scrollContentBackground(.hidden).background(color)
  104. .onAppear(perform: configureView)
  105. .navigationTitle("Insulin Sensitivities")
  106. .navigationBarTitleDisplayMode(.automatic)
  107. .navigationBarItems(
  108. trailing: EditButton()
  109. )
  110. .environment(\.editMode, $editMode)
  111. .onAppear {
  112. state.validate()
  113. }
  114. }
  115. private func pickers(for index: Int) -> some View {
  116. GeometryReader { geometry in
  117. VStack {
  118. HStack {
  119. Text("Rate").frame(width: geometry.size.width / 2)
  120. Text("Time").frame(width: geometry.size.width / 2)
  121. }
  122. HStack(spacing: 0) {
  123. Picker(selection: $state.items[index].rateIndex, label: EmptyView()) {
  124. ForEach(0 ..< state.rateValues.count, id: \.self) { i in
  125. Text(
  126. (
  127. self.rateFormatter
  128. .string(from: state.rateValues[i] as NSNumber) ?? ""
  129. ) + " \(state.units.rawValue)/U"
  130. ).tag(i)
  131. }
  132. }
  133. .frame(maxWidth: geometry.size.width / 2)
  134. .clipped()
  135. Picker(selection: $state.items[index].timeIndex, label: EmptyView()) {
  136. ForEach(0 ..< state.timeValues.count, id: \.self) { i in
  137. Text(
  138. self.dateFormatter
  139. .string(from: Date(
  140. timeIntervalSince1970: state
  141. .timeValues[i]
  142. ))
  143. ).tag(i)
  144. }
  145. }
  146. .frame(maxWidth: geometry.size.width / 2)
  147. .clipped()
  148. }
  149. }
  150. }
  151. }
  152. private var list: some View {
  153. List {
  154. ForEach(state.items.indexed(), id: \.1.id) { index, item in
  155. NavigationLink(destination: pickers(for: index)) {
  156. HStack {
  157. Text("Rate").foregroundColor(.secondary)
  158. Text(
  159. "\(rateFormatter.string(from: state.rateValues[item.rateIndex] as NSNumber) ?? "0") \(state.units.rawValue)/U"
  160. )
  161. Spacer()
  162. Text("starts at").foregroundColor(.secondary)
  163. Text(
  164. "\(dateFormatter.string(from: Date(timeIntervalSince1970: state.timeValues[item.timeIndex])))"
  165. )
  166. }
  167. }
  168. .moveDisabled(true)
  169. }
  170. .onDelete(perform: onDelete)
  171. }
  172. }
  173. private var addButton: some View {
  174. guard state.canAdd else {
  175. return AnyView(EmptyView())
  176. }
  177. switch editMode {
  178. case .inactive:
  179. return AnyView(Button(action: onAdd) { Text("Add") })
  180. default:
  181. return AnyView(EmptyView())
  182. }
  183. }
  184. func onAdd() {
  185. state.add()
  186. }
  187. private func onDelete(offsets: IndexSet) {
  188. state.items.remove(atOffsets: offsets)
  189. state.validate()
  190. }
  191. }
  192. }