GlucoseNotificationSettingsRootView.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. @Environment(AppState.self) var appState
  35. var body: some View {
  36. List {
  37. SettingInputSection(
  38. decimalValue: $decimalPlaceholder,
  39. booleanValue: $state.useAlarmSound,
  40. shouldDisplayHint: $shouldDisplayHint,
  41. selectedVerboseHint: Binding(
  42. get: { selectedVerboseHint },
  43. set: {
  44. selectedVerboseHint = $0.map { AnyView($0) }
  45. hintLabel = "Play Alarm Sound"
  46. }
  47. ),
  48. units: state.units,
  49. type: .boolean,
  50. label: "Play Alarm Sound",
  51. miniHint: "Alarm with every Trio notification.",
  52. verboseHint: VStack(alignment: .leading, spacing: 10) {
  53. Text("Default: OFF").bold()
  54. Text(
  55. "This will cause a sound to be triggered by Trio notifications for Carbs Required, and Glucose Low/High Alarms."
  56. )
  57. }
  58. )
  59. SettingInputSection(
  60. decimalValue: $decimalPlaceholder,
  61. booleanValue: $state.notificationsPump,
  62. shouldDisplayHint: $shouldDisplayHint,
  63. selectedVerboseHint: Binding(
  64. get: { selectedVerboseHint },
  65. set: {
  66. selectedVerboseHint = $0.map { AnyView($0) }
  67. hintLabel = "Always Notify Pump"
  68. }
  69. ),
  70. units: state.units,
  71. type: .boolean,
  72. label: "Always Notify Pump",
  73. miniHint: "Always Notify Pump Warnings.",
  74. verboseHint:
  75. VStack(alignment: .leading, spacing: 10) {
  76. Text("Default: ON").bold()
  77. Text(
  78. "With iOS Trio Notifications enabled, you can let Trio display most Pump Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
  79. )
  80. Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
  81. Text("An example of a Pump Warning is 'Pod Expiration Reminder'")
  82. },
  83. headerText: "Trio Information Notifications"
  84. )
  85. SettingInputSection(
  86. decimalValue: $decimalPlaceholder,
  87. booleanValue: $state.notificationsCgm,
  88. shouldDisplayHint: $shouldDisplayHint,
  89. selectedVerboseHint: Binding(
  90. get: { selectedVerboseHint },
  91. set: {
  92. selectedVerboseHint = $0.map { AnyView($0) }
  93. hintLabel = "Always Notify CGM"
  94. }
  95. ),
  96. units: state.units,
  97. type: .boolean,
  98. label: "Always Notify CGM",
  99. miniHint: "Always Notify CGM Warnings.",
  100. verboseHint:
  101. VStack(alignment: .leading, spacing: 10) {
  102. Text("Default: ON").bold()
  103. Text(
  104. "With iOS Trio Notifications enabled, you can let Trio display most CGM Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
  105. )
  106. Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
  107. Text("An example of a CGM Warning is 'Unable to open the app'")
  108. }
  109. )
  110. SettingInputSection(
  111. decimalValue: $decimalPlaceholder,
  112. booleanValue: $state.notificationsCarb,
  113. shouldDisplayHint: $shouldDisplayHint,
  114. selectedVerboseHint: Binding(
  115. get: { selectedVerboseHint },
  116. set: {
  117. selectedVerboseHint = $0.map { AnyView($0) }
  118. hintLabel = "Always Notify Carb"
  119. }
  120. ),
  121. units: state.units,
  122. type: .boolean,
  123. label: "Always Notify Carb",
  124. miniHint: "Always Notify Carb Warnings.",
  125. verboseHint:
  126. VStack(alignment: .leading, spacing: 10) {
  127. Text("Default: ON").bold()
  128. Text(
  129. "With iOS Trio Notifications enabled, you can let Trio display most Carb Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
  130. )
  131. Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
  132. Text("An example of a Carb Warning is 'Carbs required: 30 g'")
  133. }
  134. )
  135. SettingInputSection(
  136. decimalValue: $decimalPlaceholder,
  137. booleanValue: $state.notificationsAlgorithm,
  138. shouldDisplayHint: $shouldDisplayHint,
  139. selectedVerboseHint: Binding(
  140. get: { selectedVerboseHint },
  141. set: {
  142. selectedVerboseHint = $0.map { AnyView($0) }
  143. hintLabel = "Always Notify Algorithm"
  144. }
  145. ),
  146. units: state.units,
  147. type: .boolean,
  148. label: "Always Notify Algorithm",
  149. miniHint: "Always Notify Algorithm Warnings.",
  150. verboseHint:
  151. VStack(alignment: .leading, spacing: 10) {
  152. Text("Default: ON").bold()
  153. Text(
  154. "With iOS Trio Notifications enabled, you can let Trio display most Algorithm Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
  155. )
  156. Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
  157. Text(
  158. "An example of an Algorithm Warning is 'Error: Invalid glucose: Not enough glucose data'"
  159. )
  160. }
  161. )
  162. SettingInputSection(
  163. decimalValue: $decimalPlaceholder,
  164. booleanValue: $state.glucoseBadge,
  165. shouldDisplayHint: $shouldDisplayHint,
  166. selectedVerboseHint: Binding(
  167. get: { selectedVerboseHint },
  168. set: {
  169. selectedVerboseHint = $0.map { AnyView($0) }
  170. hintLabel = "Show Glucose App Badge"
  171. }
  172. ),
  173. units: state.units,
  174. type: .boolean,
  175. label: "Show Glucose App Badge",
  176. miniHint: "Show your current glucose on Trio app icon.",
  177. verboseHint: VStack(alignment: .leading, spacing: 10) {
  178. Text("Default: OFF").bold()
  179. Text(
  180. "This will add your current glucose on the top right of your Trio icon as a red notification badge. Changing setting takes effect on next Glucose reading."
  181. )
  182. },
  183. headerText: "Various Glucose Notifications"
  184. )
  185. Section {
  186. VStack {
  187. Picker(
  188. selection: $state.glucoseNotificationsOption,
  189. label: Text("Glucose Notifications")
  190. ) {
  191. ForEach(GlucoseNotificationsOption.allCases) { selection in
  192. Text(selection.displayName).tag(selection)
  193. }
  194. }.padding(.top)
  195. HStack(alignment: .center) {
  196. Text(
  197. "Choose glucose notifications option. See hint for more details."
  198. )
  199. .font(.footnote)
  200. .foregroundColor(.secondary)
  201. .lineLimit(nil)
  202. Spacer()
  203. Button(
  204. action: {
  205. hintLabel = "Glucose Notifications"
  206. selectedVerboseHint =
  207. AnyView(
  208. VStack(alignment: .leading, spacing: 10) {
  209. Text(
  210. "Set the Glucose Notifications Option. Descriptions for each option found below."
  211. )
  212. VStack(alignment: .leading, spacing: 5) {
  213. Text("Disabled:").bold()
  214. Text("No Glucose Notificatitons will be triggered.")
  215. }
  216. VStack(alignment: .leading, spacing: 5) {
  217. Text("Always:").bold()
  218. Text(
  219. "A notification will be triggered every time your glucose is updated in Trio."
  220. )
  221. }
  222. VStack(alignment: .leading, spacing: 5) {
  223. Text("Only Alarm Limits:").bold()
  224. Text(
  225. "A notification will be triggered only when glucose levels are below the LOW limit or above the HIGH limit, as specified in Glucose Alarm Limits below."
  226. )
  227. }
  228. }
  229. )
  230. shouldDisplayHint.toggle()
  231. },
  232. label: {
  233. HStack {
  234. Image(systemName: "questionmark.circle")
  235. }
  236. }
  237. ).buttonStyle(BorderlessButtonStyle())
  238. }.padding(.top)
  239. }.padding(.bottom)
  240. }.listRowBackground(Color.chart)
  241. if state.glucoseNotificationsOption != GlucoseNotificationsOption.disabled {
  242. self.lowAndHighGlucoseAlertSection
  243. SettingInputSection(
  244. decimalValue: $decimalPlaceholder,
  245. booleanValue: $state.addSourceInfoToGlucoseNotifications,
  246. shouldDisplayHint: $shouldDisplayHint,
  247. selectedVerboseHint: Binding(
  248. get: { selectedVerboseHint },
  249. set: {
  250. selectedVerboseHint = $0.map { AnyView($0) }
  251. hintLabel = "Add Glucose Source to Alarm"
  252. }
  253. ),
  254. units: state.units,
  255. type: .boolean,
  256. label: "Add Glucose Source to Alarm",
  257. miniHint: "Source of the glucose reading will be added to the notification.",
  258. verboseHint: VStack(alignment: .leading, spacing: 10) {
  259. Text("Default: OFF").bold()
  260. Text("The source of the glucose reading will be added to the notification.")
  261. }
  262. )
  263. }
  264. }
  265. .listSectionSpacing(sectionSpacing)
  266. .sheet(isPresented: $shouldDisplayHint) {
  267. SettingInputHintView(
  268. hintDetent: $hintDetent,
  269. shouldDisplayHint: $shouldDisplayHint,
  270. hintLabel: hintLabel ?? "",
  271. hintText: selectedVerboseHint ?? AnyView(EmptyView()),
  272. sheetTitle: "Help"
  273. )
  274. }
  275. .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
  276. .onAppear(perform: configureView)
  277. .navigationBarTitle("Trio Notifications")
  278. .navigationBarTitleDisplayMode(.automatic)
  279. }
  280. var lowAndHighGlucoseAlertSection: some View {
  281. Section {
  282. VStack {
  283. VStack {
  284. HStack {
  285. Text("Low Glucose Alarm Limit")
  286. Spacer()
  287. Group {
  288. Text(
  289. state.units == .mgdL ? state.lowGlucose.description : state.lowGlucose.formattedAsMmolL
  290. )
  291. .foregroundColor(!displayPickerLowGlucose ? .primary : .accentColor)
  292. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  293. }
  294. }
  295. .onTapGesture {
  296. displayPickerLowGlucose.toggle()
  297. }
  298. }
  299. .padding(.top)
  300. if displayPickerLowGlucose {
  301. let setting = PickerSettingsProvider.shared.settings.lowGlucose
  302. Picker(selection: $state.lowGlucose, label: Text("")) {
  303. ForEach(
  304. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  305. id: \.self
  306. ) { value in
  307. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  308. Text(displayValue).tag(value)
  309. }
  310. }
  311. .pickerStyle(WheelPickerStyle())
  312. .frame(maxWidth: .infinity)
  313. }
  314. VStack {
  315. HStack {
  316. Text("High Glucose Alarm Limit")
  317. Spacer()
  318. Group {
  319. Text(
  320. state.units == .mgdL ? state.highGlucose.description : state.highGlucose.formattedAsMmolL
  321. )
  322. .foregroundColor(!displayPickerHighGlucose ? .primary : .accentColor)
  323. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  324. }
  325. }
  326. .onTapGesture {
  327. displayPickerHighGlucose.toggle()
  328. }
  329. }
  330. .padding(.top)
  331. if displayPickerHighGlucose {
  332. let setting = PickerSettingsProvider.shared.settings.highGlucose
  333. Picker(selection: $state.highGlucose, label: Text("")) {
  334. ForEach(
  335. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  336. id: \.self
  337. ) { value in
  338. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  339. Text(displayValue).tag(value)
  340. }
  341. }
  342. .pickerStyle(WheelPickerStyle())
  343. .frame(maxWidth: .infinity)
  344. }
  345. HStack(alignment: .center) {
  346. Text(
  347. "Sets the lower and upper limit for glucose alarms."
  348. )
  349. .lineLimit(nil)
  350. .font(.footnote)
  351. .foregroundColor(.secondary)
  352. Spacer()
  353. Button(
  354. action: {
  355. hintLabel = "Low and High Glucose Alarm Limits"
  356. selectedVerboseHint =
  357. AnyView(VStack(alignment: .leading, spacing: 10) {
  358. let low: Decimal = 70
  359. let high: Decimal = 180
  360. let labelLow = (state.units == .mgdL ? low.description : low.formattedAsMmolL) + " " +
  361. state.units.rawValue
  362. let labelHigh = (state.units == .mgdL ? high.description : high.formattedAsMmolL) + " " +
  363. state.units.rawValue
  364. Text("Low Default: " + labelLow).bold()
  365. Text("High Default: " + labelHigh).bold()
  366. VStack(alignment: .leading, spacing: 10) {
  367. Text(
  368. "These two settings determine the range outside of which you will be notified via push notifications."
  369. )
  370. Text(
  371. "If your CGM readings are below the Low value or above the High value, you will receive a glucose alarm."
  372. )
  373. }
  374. })
  375. shouldDisplayHint.toggle()
  376. },
  377. label: {
  378. HStack {
  379. Image(systemName: "questionmark.circle")
  380. }
  381. }
  382. ).buttonStyle(BorderlessButtonStyle())
  383. }.padding(.top)
  384. }.padding(.bottom)
  385. }.listRowBackground(Color.chart)
  386. }
  387. }
  388. }