GlucoseNotificationSettingsRootView.swift 20 KB

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