AddTempTargetRootView.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import SwiftUI
  2. import Swinject
  3. extension AddTempTarget {
  4. struct RootView: BaseView {
  5. let resolver: Resolver
  6. @StateObject var state = StateModel()
  7. @State private var isPromtPresented = false
  8. @State private var isRemoveAlertPresented = false
  9. @State private var removeAlert: Alert?
  10. @State private var isEditing = false
  11. private var formatter: NumberFormatter {
  12. let formatter = NumberFormatter()
  13. formatter.numberStyle = .decimal
  14. formatter.maximumFractionDigits = 1
  15. return formatter
  16. }
  17. var body: some View {
  18. Form {
  19. if !state.presets.isEmpty {
  20. Section(header: Text("Presets")) {
  21. ForEach(state.presets) { preset in
  22. presetView(for: preset)
  23. }
  24. }
  25. }
  26. Section(
  27. header: Text("Basal Insulin and Sensitivity ratio"),
  28. footer: Text(
  29. "A lower 'Half Basal Target' setting will reduce the basal and raise the ISF earlier, at a lower target glucose." +
  30. " Your setting: \(state.halfBasal) mg/dl. Autosens.max limits the max endpoint (\(state.maxValue * 100) %)"
  31. )
  32. ) {
  33. VStack {
  34. Slider(
  35. value: $state.percentage,
  36. in: 15 ...
  37. Double(state.maxValue * 100),
  38. step: 1,
  39. onEditingChanged: { editing in
  40. isEditing = editing
  41. }
  42. )
  43. Text("\(state.percentage.formatted(.number)) %")
  44. .foregroundColor(isEditing ? .orange : .blue)
  45. .font(.largeTitle)
  46. Divider()
  47. Text(
  48. "Target" +
  49. (
  50. state
  51. .units == .mmolL ? ": \(computeTarget().asMmolL.formatted(.number)) mmol/L" :
  52. ": \(computeTarget().formatted(.number)) mg/dl"
  53. )
  54. ).foregroundColor(.secondary).italic()
  55. }
  56. }
  57. Section {
  58. HStack {
  59. Text("Duration")
  60. Spacer()
  61. DecimalTextField("0", value: $state.duration, formatter: formatter, cleanInput: true)
  62. Text("minutes").foregroundColor(.secondary)
  63. }
  64. DatePicker("Date", selection: $state.date)
  65. Button { isPromtPresented = true }
  66. label: { Text("Save as preset") }
  67. }
  68. Section {
  69. Button { state.enact() }
  70. label: { Text("Enact") }
  71. Button { state.cancel() }
  72. label: { Text("Cancel Temp Target") }
  73. }
  74. }
  75. .popover(isPresented: $isPromtPresented) {
  76. Form {
  77. Section(header: Text("Enter preset name")) {
  78. TextField("Name", text: $state.newPresetName)
  79. Button {
  80. state.save()
  81. isPromtPresented = false
  82. }
  83. label: { Text("Save") }
  84. Button { isPromtPresented = false }
  85. label: { Text("Cancel") }
  86. }
  87. }
  88. }
  89. .onAppear(perform: configureView)
  90. .navigationTitle("Enact Temp Target")
  91. .navigationBarTitleDisplayMode(.automatic)
  92. .navigationBarItems(leading: Button("Close", action: state.hideModal))
  93. }
  94. func computeTarget() -> Decimal {
  95. let ratio = min(Decimal(state.percentage / 100), state.maxValue)
  96. let diff = Double(state.halfBasal - 100)
  97. let multiplier = state.percentage - (diff * (state.percentage / 100))
  98. var target = Decimal(diff + multiplier) / ratio
  99. if (state.halfBasal + (state.halfBasal + target - 100)) <= 0 {
  100. target = (state.halfBasal - 100 + (state.halfBasal - 100) * state.maxValue) / state.maxValue
  101. }
  102. return target
  103. }
  104. private func presetView(for preset: TempTarget) -> some View {
  105. var low = preset.targetBottom
  106. var high = preset.targetTop
  107. if state.units == .mmolL {
  108. low = low?.asMmolL
  109. high = high?.asMmolL
  110. }
  111. return HStack {
  112. VStack {
  113. HStack {
  114. Text(preset.displayName)
  115. Spacer()
  116. }
  117. HStack(spacing: 2) {
  118. Text(
  119. "\(formatter.string(from: (low ?? 0) as NSNumber)!) - \(formatter.string(from: (high ?? 0) as NSNumber)!)"
  120. )
  121. .foregroundColor(.secondary)
  122. .font(.caption)
  123. Text(state.units.rawValue)
  124. .foregroundColor(.secondary)
  125. .font(.caption)
  126. Text("for")
  127. .foregroundColor(.secondary)
  128. .font(.caption)
  129. Text("\(formatter.string(from: preset.duration as NSNumber)!)")
  130. .foregroundColor(.secondary)
  131. .font(.caption)
  132. Text("min")
  133. .foregroundColor(.secondary)
  134. .font(.caption)
  135. Spacer()
  136. }.padding(.top, 2)
  137. }
  138. .contentShape(Rectangle())
  139. .onTapGesture {
  140. state.enactPreset(id: preset.id)
  141. }
  142. Image(systemName: "xmark.circle").foregroundColor(.secondary)
  143. .contentShape(Rectangle())
  144. .padding(.vertical)
  145. .onTapGesture {
  146. removeAlert = Alert(
  147. title: Text("Are you sure?"),
  148. message: Text("Delete preset \"\(preset.displayName)\""),
  149. primaryButton: .destructive(Text("Delete"), action: { state.removePreset(id: preset.id) }),
  150. secondaryButton: .cancel()
  151. )
  152. isRemoveAlertPresented = true
  153. }
  154. .alert(isPresented: $isRemoveAlertPresented) {
  155. removeAlert!
  156. }
  157. }
  158. }
  159. }
  160. }