GlucoseNotificationSettingsRootView.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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 = "Always Notify Pump"
  46. }
  47. ),
  48. units: state.units,
  49. type: .boolean,
  50. label: "Always Notify Pump",
  51. miniHint: "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: "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 = "Always Notify CGM"
  72. }
  73. ),
  74. units: state.units,
  75. type: .boolean,
  76. label: "Always Notify CGM",
  77. miniHint: "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 = "Always Notify Carb"
  97. }
  98. ),
  99. units: state.units,
  100. type: .boolean,
  101. label: "Always Notify Carb",
  102. miniHint: "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 = "Always Notify Algorithm"
  122. }
  123. ),
  124. units: state.units,
  125. type: .boolean,
  126. label: "Always Notify Algorithm",
  127. miniHint: "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 = "Show Glucose App Badge"
  149. }
  150. ),
  151. units: state.units,
  152. type: .boolean,
  153. label: "Show Glucose App Badge",
  154. miniHint: "Show your current glucose on Trio app icon.",
  155. verboseHint: VStack(alignment: .leading, spacing: 10) {
  156. Text("Default: OFF").bold()
  157. Text("This will add your current glucose on the top right of your Trio icon as a red notification badge.")
  158. },
  159. headerText: "Various Glucose Notifications"
  160. )
  161. SettingInputSection(
  162. decimalValue: $decimalPlaceholder,
  163. booleanValue: $state.glucoseNotificationsAlways,
  164. shouldDisplayHint: $shouldDisplayHint,
  165. selectedVerboseHint: Binding(
  166. get: { selectedVerboseHint },
  167. set: {
  168. selectedVerboseHint = $0.map { AnyView($0) }
  169. hintLabel = "Always Notify Glucose"
  170. }
  171. ),
  172. units: state.units,
  173. type: .boolean,
  174. label: "Always Notify Glucose",
  175. miniHint: "Trigger a notification every time your glucose is updated.",
  176. verboseHint: VStack(alignment: .leading, spacing: 10) {
  177. Text("Default: OFF").bold()
  178. Text("A notification will be triggered every time your glucose is updated in Trio.")
  179. }
  180. )
  181. SettingInputSection(
  182. decimalValue: $decimalPlaceholder,
  183. booleanValue: $state.useAlarmSound,
  184. shouldDisplayHint: $shouldDisplayHint,
  185. selectedVerboseHint: Binding(
  186. get: { selectedVerboseHint },
  187. set: {
  188. selectedVerboseHint = $0.map { AnyView($0) }
  189. hintLabel = "Play Alarm Sound"
  190. }
  191. ),
  192. units: state.units,
  193. type: .boolean,
  194. label: "Play Alarm Sound",
  195. miniHint: "Alarm with every Trio notification.",
  196. verboseHint: VStack(alignment: .leading, spacing: 10) {
  197. Text("Default: OFF").bold()
  198. Text("This will cause a sound to be triggered by every Trio notification.")
  199. }
  200. )
  201. SettingInputSection(
  202. decimalValue: $decimalPlaceholder,
  203. booleanValue: $state.addSourceInfoToGlucoseNotifications,
  204. shouldDisplayHint: $shouldDisplayHint,
  205. selectedVerboseHint: Binding(
  206. get: { selectedVerboseHint },
  207. set: {
  208. selectedVerboseHint = $0.map { AnyView($0) }
  209. hintLabel = "Add Glucose Source to Alarm"
  210. }
  211. ),
  212. units: state.units,
  213. type: .boolean,
  214. label: "Add Glucose Source to Alarm",
  215. miniHint: "Source of the glucose reading will be added to the notification.",
  216. verboseHint: VStack(alignment: .leading, spacing: 10) {
  217. Text("Default: OFF").bold()
  218. Text("The source of the glucose reading will be added to the notification.")
  219. }
  220. )
  221. self.lowAndHighGlucoseAlertSection
  222. }
  223. .listSectionSpacing(sectionSpacing)
  224. .sheet(isPresented: $shouldDisplayHint) {
  225. SettingInputHintView(
  226. hintDetent: $hintDetent,
  227. shouldDisplayHint: $shouldDisplayHint,
  228. hintLabel: hintLabel ?? "",
  229. hintText: selectedVerboseHint ?? AnyView(EmptyView()),
  230. sheetTitle: "Help"
  231. )
  232. }
  233. .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
  234. .onAppear(perform: configureView)
  235. .navigationBarTitle("Trio Notifications")
  236. .navigationBarTitleDisplayMode(.automatic)
  237. }
  238. var lowAndHighGlucoseAlertSection: some View {
  239. Section {
  240. VStack {
  241. VStack {
  242. HStack {
  243. Text("Low Glucose Alarm Limit")
  244. Spacer()
  245. Group {
  246. Text(
  247. state.units == .mgdL ? state.lowGlucose.description : state.lowGlucose.formattedAsMmolL
  248. )
  249. .foregroundColor(!displayPickerLowGlucose ? .primary : .accentColor)
  250. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  251. }
  252. }
  253. .onTapGesture {
  254. displayPickerLowGlucose.toggle()
  255. }
  256. }
  257. .padding(.top)
  258. if displayPickerLowGlucose {
  259. let setting = PickerSettingsProvider.shared.settings.lowGlucose
  260. Picker(selection: $state.lowGlucose, label: Text("")) {
  261. ForEach(
  262. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  263. id: \.self
  264. ) { value in
  265. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  266. Text(displayValue).tag(value)
  267. }
  268. }
  269. .pickerStyle(WheelPickerStyle())
  270. .frame(maxWidth: .infinity)
  271. }
  272. VStack {
  273. HStack {
  274. Text("High Glucose Alarm Limit")
  275. Spacer()
  276. Group {
  277. Text(
  278. state.units == .mgdL ? state.highGlucose.description : state.highGlucose.formattedAsMmolL
  279. )
  280. .foregroundColor(!displayPickerHighGlucose ? .primary : .accentColor)
  281. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  282. }
  283. }
  284. .onTapGesture {
  285. displayPickerHighGlucose.toggle()
  286. }
  287. }
  288. .padding(.top)
  289. if displayPickerHighGlucose {
  290. let setting = PickerSettingsProvider.shared.settings.highGlucose
  291. Picker(selection: $state.highGlucose, label: Text("")) {
  292. ForEach(
  293. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  294. id: \.self
  295. ) { value in
  296. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  297. Text(displayValue).tag(value)
  298. }
  299. }
  300. .pickerStyle(WheelPickerStyle())
  301. .frame(maxWidth: .infinity)
  302. }
  303. HStack(alignment: .center) {
  304. Text(
  305. "Sets the lower and upper limit for glucose alarms."
  306. )
  307. .lineLimit(nil)
  308. .font(.footnote)
  309. .foregroundColor(.secondary)
  310. Spacer()
  311. Button(
  312. action: {
  313. hintLabel = "Low and High Glucose Alarm Limits"
  314. selectedVerboseHint =
  315. AnyView(VStack(alignment: .leading, spacing: 10) {
  316. Text("Low Default: 70 mg/dL").bold()
  317. Text("High Default: 180 mg/dL").bold()
  318. VStack(alignment: .leading, spacing: 10) {
  319. Text(
  320. "These two settings determine the range outside of which you will be notified via push notifications."
  321. )
  322. Text(
  323. "If your CGM readings are below the Low value or above the High value, you will receive a glucose alarm."
  324. )
  325. }
  326. })
  327. shouldDisplayHint.toggle()
  328. },
  329. label: {
  330. HStack {
  331. Image(systemName: "questionmark.circle")
  332. }
  333. }
  334. ).buttonStyle(BorderlessButtonStyle())
  335. }.padding(.top)
  336. }.padding(.bottom)
  337. }.listRowBackground(Color.chart)
  338. }
  339. }
  340. }