GlucoseNotificationSettingsRootView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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. Form {
  52. SettingInputSection(
  53. decimalValue: $decimalPlaceholder,
  54. booleanValue: $state.glucoseBadge,
  55. shouldDisplayHint: $shouldDisplayHint,
  56. selectedVerboseHint: Binding(
  57. get: { selectedVerboseHint },
  58. set: {
  59. selectedVerboseHint = $0.map { AnyView($0) }
  60. hintLabel = "Show Glucose App Badge"
  61. }
  62. ),
  63. units: state.units,
  64. type: .boolean,
  65. label: "Show Glucose App Badge",
  66. miniHint: """
  67. Show your current glucose reading at the top of the Trio app icon
  68. Default: OFF
  69. """,
  70. verboseHint: VStack {
  71. Text("Default: OFF").bold()
  72. Text("""
  73. This will add your current glucose on the top right of your Trio icon as a red notification badge.
  74. """)
  75. },
  76. headerText: "Various Glucose Notifications"
  77. )
  78. SettingInputSection(
  79. decimalValue: $decimalPlaceholder,
  80. booleanValue: $state.glucoseNotificationsAlways,
  81. shouldDisplayHint: $shouldDisplayHint,
  82. selectedVerboseHint: Binding(
  83. get: { selectedVerboseHint },
  84. set: {
  85. selectedVerboseHint = $0.map { AnyView($0) }
  86. hintLabel = "Always Notify Glucose"
  87. }
  88. ),
  89. units: state.units,
  90. type: .boolean,
  91. label: "Always Notify Glucose",
  92. miniHint: """
  93. A notification will be triggered every time your glucose is updated in Trio
  94. Default: OFF
  95. """,
  96. verboseHint: VStack {
  97. Text("Default: OFF").bold()
  98. Text("""
  99. A notification will be triggered every time your glucose is updated in Trio.
  100. """)
  101. }
  102. )
  103. SettingInputSection(
  104. decimalValue: $decimalPlaceholder,
  105. booleanValue: $state.useAlarmSound,
  106. shouldDisplayHint: $shouldDisplayHint,
  107. selectedVerboseHint: Binding(
  108. get: { selectedVerboseHint },
  109. set: {
  110. selectedVerboseHint = $0.map { AnyView($0) }
  111. hintLabel = "Play Alarm Sound"
  112. }
  113. ),
  114. units: state.units,
  115. type: .boolean,
  116. label: "Play Alarm Sound",
  117. miniHint: """
  118. This will cause a sound to be triggered by every Trio notification
  119. Default: OFF
  120. """,
  121. verboseHint: VStack {
  122. Text("Default: OFF").bold()
  123. Text("""
  124. This will cause a sound to be triggered by every Trio notification.
  125. """)
  126. }
  127. )
  128. SettingInputSection(
  129. decimalValue: $decimalPlaceholder,
  130. booleanValue: $state.addSourceInfoToGlucoseNotifications,
  131. shouldDisplayHint: $shouldDisplayHint,
  132. selectedVerboseHint: Binding(
  133. get: { selectedVerboseHint },
  134. set: {
  135. selectedVerboseHint = $0.map { AnyView($0) }
  136. hintLabel = "Add Glucose Source to Alarm"
  137. }
  138. ),
  139. units: state.units,
  140. type: .boolean,
  141. label: "Add Glucose Source to Alarm",
  142. miniHint: """
  143. The source of the glucose reading will be added to the notification
  144. Default: OFF
  145. """,
  146. verboseHint: VStack {
  147. Text("Default: OFF").bold()
  148. Text("""
  149. The source of the glucose reading will be added to the notification.
  150. """)
  151. }
  152. )
  153. self.lowAndHighGlucoseAlertSection
  154. }
  155. .sheet(isPresented: $shouldDisplayHint) {
  156. SettingInputHintView(
  157. hintDetent: $hintDetent,
  158. shouldDisplayHint: $shouldDisplayHint,
  159. hintLabel: hintLabel ?? "",
  160. hintText: selectedVerboseHint ?? AnyView(EmptyView()),
  161. sheetTitle: "Help"
  162. )
  163. }
  164. .scrollContentBackground(.hidden).background(color)
  165. .onAppear(perform: configureView)
  166. .navigationBarTitle("Glucose Notifications")
  167. .navigationBarTitleDisplayMode(.automatic)
  168. }
  169. var lowAndHighGlucoseAlertSection: some View {
  170. Section {
  171. VStack {
  172. VStack {
  173. HStack {
  174. Text("Low Glucose Alarm Limit")
  175. Spacer()
  176. Group {
  177. Text(
  178. state.units == .mgdL ? state.lowGlucose.description : state.lowGlucose.formattedAsMmolL
  179. )
  180. .foregroundColor(!displayPickerLowGlucose ? .primary : .accentColor)
  181. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  182. }
  183. }
  184. .onTapGesture {
  185. displayPickerLowGlucose.toggle()
  186. }
  187. }
  188. .padding(.top)
  189. if displayPickerLowGlucose {
  190. let setting = PickerSettingsProvider.shared.settings.lowGlucose
  191. Picker(selection: $state.lowGlucose, label: Text("")) {
  192. ForEach(
  193. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  194. id: \.self
  195. ) { value in
  196. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  197. Text(displayValue).tag(value)
  198. }
  199. }
  200. .pickerStyle(WheelPickerStyle())
  201. .frame(maxWidth: .infinity)
  202. }
  203. VStack {
  204. HStack {
  205. Text("High Glucose Alarm Limit")
  206. Spacer()
  207. Group {
  208. Text(
  209. state.units == .mgdL ? state.highGlucose.description : state.highGlucose.formattedAsMmolL
  210. )
  211. .foregroundColor(!displayPickerHighGlucose ? .primary : .accentColor)
  212. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  213. }
  214. }
  215. .onTapGesture {
  216. displayPickerHighGlucose.toggle()
  217. }
  218. }
  219. .padding(.top)
  220. if displayPickerHighGlucose {
  221. let setting = PickerSettingsProvider.shared.settings.highGlucose
  222. Picker(selection: $state.highGlucose, label: Text("")) {
  223. ForEach(
  224. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  225. id: \.self
  226. ) { value in
  227. let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
  228. Text(displayValue).tag(value)
  229. }
  230. }
  231. .pickerStyle(WheelPickerStyle())
  232. .frame(maxWidth: .infinity)
  233. }
  234. HStack(alignment: .top) {
  235. Text(
  236. "Set the lower and upper limit for glucose alarms. See hint for more details."
  237. )
  238. .lineLimit(nil)
  239. .font(.footnote)
  240. .foregroundColor(.secondary)
  241. Spacer()
  242. Button(
  243. action: {
  244. hintLabel = "Low and High Glucose Alarm Limits"
  245. selectedVerboseHint =
  246. AnyView(VStack {
  247. Text("Low Default: 70 mg/dL").bold()
  248. Text("High Default: 180 mg/dL").bold()
  249. Text("""
  250. These two settings determine the range outside of which you will be notified via push notifications.
  251. If your CGM readings are below the Low value or above the High value, you will receive a glucose alarm.
  252. """)
  253. })
  254. shouldDisplayHint.toggle()
  255. },
  256. label: {
  257. HStack {
  258. Image(systemName: "questionmark.circle")
  259. }
  260. }
  261. ).buttonStyle(BorderlessButtonStyle())
  262. }.padding(.top)
  263. }.padding(.bottom)
  264. }.listRowBackground(Color.chart)
  265. }
  266. }
  267. }