GeneralSettingsView.swift 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // LoopFollow
  2. // GeneralSettingsView.swift
  3. import SwiftUI
  4. struct GeneralSettingsView: View {
  5. @ObservedObject var colorBGText = Storage.shared.colorBGText
  6. @ObservedObject var appBadge = Storage.shared.appBadge
  7. @ObservedObject var appearanceMode = Storage.shared.appearanceMode
  8. @ObservedObject var showStats = Storage.shared.showStats
  9. @ObservedObject var useIFCC = Storage.shared.useIFCC
  10. @ObservedObject var showSmallGraph = Storage.shared.showSmallGraph
  11. @ObservedObject var screenlockSwitchState = Storage.shared.screenlockSwitchState
  12. @ObservedObject var showDisplayName = Storage.shared.showDisplayName
  13. @ObservedObject var snoozerEmoji = Storage.shared.snoozerEmoji
  14. @ObservedObject var forcePortraitMode = Storage.shared.forcePortraitMode
  15. @ObservedObject var persistentNotification = Storage.shared.persistentNotification
  16. @ObservedObject var graphTimeZoneEnabled = Storage.shared.graphTimeZoneEnabled
  17. @ObservedObject var graphTimeZoneIdentifier = Storage.shared.graphTimeZoneIdentifier
  18. // Speak-BG settings
  19. @ObservedObject var speakBG = Storage.shared.speakBG
  20. @ObservedObject var speakBGAlways = Storage.shared.speakBGAlways
  21. @ObservedObject var speakLanguage = Storage.shared.speakLanguage
  22. @ObservedObject var speakLowBG = Storage.shared.speakLowBG
  23. @ObservedObject var speakProactiveLowBG = Storage.shared.speakProactiveLowBG
  24. @ObservedObject var speakLowBGLimit = Storage.shared.speakLowBGLimit
  25. @ObservedObject var speakFastDropDelta = Storage.shared.speakFastDropDelta
  26. @ObservedObject var speakHighBG = Storage.shared.speakHighBG
  27. @ObservedObject var speakHighBGLimit = Storage.shared.speakHighBGLimit
  28. // Telemetry — see LoopFollow/Helpers/Telemetry.swift
  29. @ObservedObject var telemetryEnabled = Storage.shared.telemetryEnabled
  30. var body: some View {
  31. NavigationView {
  32. Form {
  33. Section("App Settings") {
  34. Toggle("Display App Badge", isOn: $appBadge.value)
  35. Toggle("Persistent Notification", isOn: $persistentNotification.value)
  36. }
  37. Section("Display") {
  38. Picker("Appearance", selection: $appearanceMode.value) {
  39. ForEach(AppearanceMode.allCases, id: \.self) { mode in
  40. Text(mode.displayName).tag(mode)
  41. }
  42. }
  43. Toggle("Display Stats", isOn: $showStats.value)
  44. Toggle("Use IFCC A1C", isOn: $useIFCC.value)
  45. Toggle("Display Small Graph", isOn: $showSmallGraph.value)
  46. Toggle("Color BG Text", isOn: $colorBGText.value)
  47. Toggle("Keep Screen Active", isOn: $screenlockSwitchState.value)
  48. Toggle("Show Display Name", isOn: $showDisplayName.value)
  49. Toggle("Snoozer emoji", isOn: $snoozerEmoji.value)
  50. Toggle("Force portrait mode", isOn: $forcePortraitMode.value)
  51. .onChange(of: forcePortraitMode.value) { _ in
  52. let window = UIApplication.shared.connectedScenes
  53. .compactMap { $0 as? UIWindowScene }
  54. .flatMap { $0.windows }
  55. .first
  56. window?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
  57. }
  58. }
  59. Section("Time Zone") {
  60. Toggle("Time Zone Override", isOn: $graphTimeZoneEnabled.value)
  61. .onChange(of: graphTimeZoneEnabled.value) { _ in markChartSettingsDirty() }
  62. if graphTimeZoneEnabled.value {
  63. Picker("Time Zone", selection: $graphTimeZoneIdentifier.value) {
  64. ForEach(Self.sortedTimeZones, id: \.identifier) { tz in
  65. Text(Self.timeZoneLabel(tz)).tag(tz.identifier)
  66. }
  67. }
  68. .onChange(of: graphTimeZoneIdentifier.value) { _ in markChartSettingsDirty() }
  69. }
  70. }
  71. Section("Speak BG") {
  72. Toggle("Speak BG", isOn: $speakBG.value.animation())
  73. if speakBG.value {
  74. Picker("Language", selection: $speakLanguage.value) {
  75. Text("English").tag("en")
  76. Text("French").tag("fr")
  77. Text("Italian").tag("it")
  78. Text("Slovak").tag("sk")
  79. Text("Swedish").tag("sv")
  80. }
  81. Toggle("Always", isOn: $speakBGAlways.value.animation())
  82. if !speakBGAlways.value {
  83. Toggle("Low", isOn: $speakLowBG.value.animation())
  84. .onChange(of: speakLowBG.value) { newValue in
  85. if newValue {
  86. speakProactiveLowBG.value = false
  87. }
  88. }
  89. Toggle("Proactive Low", isOn: $speakProactiveLowBG.value.animation())
  90. .onChange(of: speakProactiveLowBG.value) { newValue in
  91. if newValue {
  92. speakLowBG.value = false
  93. }
  94. }
  95. if speakLowBG.value || speakProactiveLowBG.value {
  96. BGPicker(
  97. title: "Low BG Limit",
  98. range: 40 ... 108,
  99. value: $speakLowBGLimit.value
  100. )
  101. }
  102. if speakProactiveLowBG.value {
  103. BGPicker(
  104. title: "Fast Drop Delta",
  105. range: 3 ... 20,
  106. value: $speakFastDropDelta.value
  107. )
  108. }
  109. Toggle("High", isOn: $speakHighBG.value.animation())
  110. if speakHighBG.value {
  111. BGPicker(
  112. title: "High BG Limit",
  113. range: 140 ... 300,
  114. value: $speakHighBGLimit.value
  115. )
  116. }
  117. }
  118. }
  119. }
  120. Section("Diagnostics") {
  121. Toggle("Send anonymous usage stats", isOn: $telemetryEnabled.value)
  122. .onChange(of: telemetryEnabled.value) { newValue in
  123. if newValue {
  124. TelemetryClient.shared.scheduleRecurring()
  125. }
  126. }
  127. NavigationLink("What's sent") { TelemetryPreviewView() }
  128. NavigationLink("Privacy") { TelemetryPrivacyView() }
  129. }
  130. }
  131. }
  132. .preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme)
  133. .navigationBarTitle("General Settings", displayMode: .inline)
  134. }
  135. private func markChartSettingsDirty() {
  136. Observable.shared.chartSettingsChanged.value = true
  137. }
  138. private static let sortedTimeZones: [TimeZone] = TimeZone.knownTimeZoneIdentifiers
  139. .compactMap { TimeZone(identifier: $0) }
  140. .sorted { $0.secondsFromGMT() < $1.secondsFromGMT() }
  141. private static func timeZoneLabel(_ tz: TimeZone) -> String {
  142. let offsetMinutes = tz.secondsFromGMT() / 60
  143. let sign = offsetMinutes >= 0 ? "+" : "-"
  144. let offsetString = String(format: "UTC%@%02d:%02d", sign, abs(offsetMinutes) / 60, abs(offsetMinutes) % 60)
  145. return "(\(offsetString)) \(tz.identifier)"
  146. }
  147. }