GlucoseNotificationSettingsRootView.swift 18 KB

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