GlucoseNotificationSettingsRootView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import ActivityKit
  2. import Combine
  3. import SwiftUI
  4. import Swinject
  5. extension GlucoseNotificationSettings {
  6. struct RootView: BaseView {
  7. let resolver: Resolver
  8. @StateObject var state = StateModel()
  9. @State private var shouldDisplayHint: Bool = false
  10. @State var hintDetent = PresentationDetent.large
  11. @State var selectedVerboseHint: AnyView?
  12. @State var hintLabel: String?
  13. @State private var decimalPlaceholder: Decimal = 0.0
  14. @State private var booleanPlaceholder: Bool = false
  15. @State private var displayPickerLowGlucose: Bool = false
  16. @State private var displayPickerHighGlucose: Bool = false
  17. private var glucoseFormatter: NumberFormatter {
  18. let formatter = NumberFormatter()
  19. formatter.numberStyle = .decimal
  20. formatter.maximumFractionDigits = 0
  21. if state.units == .mmolL {
  22. formatter.maximumFractionDigits = 1
  23. }
  24. formatter.roundingMode = .halfUp
  25. return formatter
  26. }
  27. private var carbsFormatter: NumberFormatter {
  28. let formatter = NumberFormatter()
  29. formatter.numberStyle = .decimal
  30. formatter.maximumFractionDigits = 0
  31. return formatter
  32. }
  33. @Environment(\.colorScheme) var colorScheme
  34. var color: LinearGradient {
  35. colorScheme == .dark ? LinearGradient(
  36. gradient: Gradient(colors: [
  37. Color.bgDarkBlue,
  38. Color.bgDarkerDarkBlue
  39. ]),
  40. startPoint: .top,
  41. endPoint: .bottom
  42. )
  43. :
  44. LinearGradient(
  45. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  46. startPoint: .top,
  47. endPoint: .bottom
  48. )
  49. }
  50. var body: some View {
  51. List {
  52. SettingInputSection(
  53. decimalValue: $decimalPlaceholder,
  54. booleanValue: $state.glucoseBadge,
  55. shouldDisplayHint: $shouldDisplayHint,
  56. selectedVerboseHint: Binding(
  57. get: { selectedVerboseHint },
  58. set: {
  59. selectedVerboseHint = $0.map { AnyView($0) }
  60. hintLabel = "Show Glucose App Badge"
  61. }
  62. ),
  63. units: state.units,
  64. type: .boolean,
  65. label: "Show Glucose App Badge",
  66. miniHint: "Show your current glucose on Trio app icon.",
  67. verboseHint: VStack(alignment: .leading, spacing: 10) {
  68. Text("Default: OFF").bold()
  69. Text("This will add your current glucose on the top right of your Trio icon as a red notification badge.")
  70. },
  71. headerText: "Various Glucose Notifications"
  72. )
  73. SettingInputSection(
  74. decimalValue: $decimalPlaceholder,
  75. booleanValue: $state.glucoseNotificationsAlways,
  76. shouldDisplayHint: $shouldDisplayHint,
  77. selectedVerboseHint: Binding(
  78. get: { selectedVerboseHint },
  79. set: {
  80. selectedVerboseHint = $0.map { AnyView($0) }
  81. hintLabel = "Always Notify Glucose"
  82. }
  83. ),
  84. units: state.units,
  85. type: .boolean,
  86. label: "Always Notify Glucose",
  87. miniHint: "Trigger a notification every time your glucose is updated.",
  88. verboseHint: VStack(alignment: .leading, spacing: 10) {
  89. Text("Default: OFF").bold()
  90. Text("A notification will be triggered every time your glucose is updated in Trio.")
  91. }
  92. )
  93. SettingInputSection(
  94. decimalValue: $decimalPlaceholder,
  95. booleanValue: $state.useAlarmSound,
  96. shouldDisplayHint: $shouldDisplayHint,
  97. selectedVerboseHint: Binding(
  98. get: { selectedVerboseHint },
  99. set: {
  100. selectedVerboseHint = $0.map { AnyView($0) }
  101. hintLabel = "Play Alarm Sound"
  102. }
  103. ),
  104. units: state.units,
  105. type: .boolean,
  106. label: "Play Alarm Sound",
  107. miniHint: "Alarm with every Trio notification.",
  108. verboseHint: VStack(alignment: .leading, spacing: 10) {
  109. Text("Default: OFF").bold()
  110. Text("This will cause a sound to be triggered by every Trio notification.")
  111. }
  112. )
  113. SettingInputSection(
  114. decimalValue: $decimalPlaceholder,
  115. booleanValue: $state.addSourceInfoToGlucoseNotifications,
  116. shouldDisplayHint: $shouldDisplayHint,
  117. selectedVerboseHint: Binding(
  118. get: { selectedVerboseHint },
  119. set: {
  120. selectedVerboseHint = $0.map { AnyView($0) }
  121. hintLabel = "Add Glucose Source to Alarm"
  122. }
  123. ),
  124. units: state.units,
  125. type: .boolean,
  126. label: "Add Glucose Source to Alarm",
  127. miniHint: "Source of the glucose reading will be added to the notification.",
  128. verboseHint: VStack(alignment: .leading, spacing: 10) {
  129. Text("Default: OFF").bold()
  130. Text("The source of the glucose reading will be added to the notification.")
  131. }
  132. )
  133. self.lowAndHighGlucoseAlertSection
  134. }
  135. .listSectionSpacing(sectionSpacing)
  136. .sheet(isPresented: $shouldDisplayHint) {
  137. SettingInputHintView(
  138. hintDetent: $hintDetent,
  139. shouldDisplayHint: $shouldDisplayHint,
  140. hintLabel: hintLabel ?? "",
  141. hintText: selectedVerboseHint ?? AnyView(EmptyView()),
  142. sheetTitle: "Help"
  143. )
  144. }
  145. .scrollContentBackground(.hidden).background(color)
  146. .onAppear(perform: configureView)
  147. .navigationBarTitle("Glucose Notifications")
  148. .navigationBarTitleDisplayMode(.automatic)
  149. }
  150. var lowAndHighGlucoseAlertSection: some View {
  151. Section {
  152. VStack {
  153. VStack {
  154. HStack {
  155. Text("Low Glucose Alarm Limit")
  156. Spacer()
  157. Group {
  158. Text(
  159. state.units == .mgdL ? state.lowGlucose.description : state.lowGlucose.formattedAsMmolL
  160. )
  161. .foregroundColor(!displayPickerLowGlucose ? .primary : .accentColor)
  162. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  163. }
  164. }
  165. .onTapGesture {
  166. displayPickerLowGlucose.toggle()
  167. }
  168. }
  169. .padding(.top)
  170. if displayPickerLowGlucose {
  171. let setting = PickerSettingsProvider.shared.settings.lowGlucose
  172. Picker(selection: $state.lowGlucose, label: Text("")) {
  173. ForEach(
  174. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  175. id: \.self
  176. ) { value in
  177. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  178. Text(displayValue).tag(value)
  179. }
  180. }
  181. .pickerStyle(WheelPickerStyle())
  182. .frame(maxWidth: .infinity)
  183. }
  184. VStack {
  185. HStack {
  186. Text("High Glucose Alarm Limit")
  187. Spacer()
  188. Group {
  189. Text(
  190. state.units == .mgdL ? state.highGlucose.description : state.highGlucose.formattedAsMmolL
  191. )
  192. .foregroundColor(!displayPickerHighGlucose ? .primary : .accentColor)
  193. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  194. }
  195. }
  196. .onTapGesture {
  197. displayPickerHighGlucose.toggle()
  198. }
  199. }
  200. .padding(.top)
  201. if displayPickerHighGlucose {
  202. let setting = PickerSettingsProvider.shared.settings.highGlucose
  203. Picker(selection: $state.highGlucose, label: Text("")) {
  204. ForEach(
  205. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  206. id: \.self
  207. ) { value in
  208. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  209. Text(displayValue).tag(value)
  210. }
  211. }
  212. .pickerStyle(WheelPickerStyle())
  213. .frame(maxWidth: .infinity)
  214. }
  215. HStack(alignment: .center) {
  216. Text(
  217. "Sets the lower and upper limit for glucose alarms."
  218. )
  219. .lineLimit(nil)
  220. .font(.footnote)
  221. .foregroundColor(.secondary)
  222. Spacer()
  223. Button(
  224. action: {
  225. hintLabel = "Low and High Glucose Alarm Limits"
  226. selectedVerboseHint =
  227. AnyView(VStack(spacing: 10) {
  228. Text("Low Default: 70 mg/dL").bold()
  229. Text("High Default: 180 mg/dL").bold()
  230. VStack(alignment: .leading, spacing: 10) {
  231. Text(
  232. "These two settings determine the range outside of which you will be notified via push notifications."
  233. )
  234. Text(
  235. "If your CGM readings are below the Low value or above the High value, you will receive a glucose alarm."
  236. )
  237. }
  238. })
  239. shouldDisplayHint.toggle()
  240. },
  241. label: {
  242. HStack {
  243. Image(systemName: "questionmark.circle")
  244. }
  245. }
  246. ).buttonStyle(BorderlessButtonStyle())
  247. }.padding(.top)
  248. }.padding(.bottom)
  249. }.listRowBackground(Color.chart)
  250. }
  251. }
  252. }