GlucoseNotificationSettingsRootView.swift 21 KB

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