SettingsMenuView.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // LoopFollow
  2. // SettingsMenuView.swift
  3. import SwiftUI
  4. import UIKit
  5. struct SettingsMenuView: View {
  6. // MARK: - Observed Objects
  7. @ObservedObject private var nightscoutURL = Storage.shared.url
  8. @ObservedObject private var settingsPath = Observable.shared.settingsPath
  9. // MARK: – Local state
  10. var onBack: (() -> Void)?
  11. // MARK: – Observed objects
  12. @ObservedObject private var url = Storage.shared.url
  13. // MARK: – Body
  14. var body: some View {
  15. NavigationStack(path: $settingsPath.value) {
  16. List {
  17. dataSection
  18. Section("Display Settings") {
  19. NavigationRow(title: "General",
  20. icon: "gearshape")
  21. {
  22. settingsPath.value.append(Sheet.general)
  23. }
  24. NavigationRow(title: "Graph",
  25. icon: "chart.xyaxis.line")
  26. {
  27. settingsPath.value.append(Sheet.graph)
  28. }
  29. if !nightscoutURL.value.isEmpty {
  30. NavigationRow(title: "Information Display",
  31. icon: "info.circle")
  32. {
  33. settingsPath.value.append(Sheet.infoDisplay)
  34. }
  35. }
  36. NavigationRow(title: "Tabs",
  37. icon: "rectangle.3.group")
  38. {
  39. settingsPath.value.append(Sheet.tabSettings)
  40. }
  41. }
  42. Section("App Settings") {
  43. NavigationRow(title: "Background Refresh",
  44. icon: "arrow.clockwise")
  45. {
  46. settingsPath.value.append(Sheet.backgroundRefresh)
  47. }
  48. NavigationRow(title: "Import/Export",
  49. icon: "square.and.arrow.down")
  50. {
  51. settingsPath.value.append(Sheet.importExport)
  52. }
  53. if !nightscoutURL.value.isEmpty {
  54. NavigationRow(title: "Remote",
  55. icon: "antenna.radiowaves.left.and.right")
  56. {
  57. settingsPath.value.append(Sheet.remote)
  58. }
  59. }
  60. }
  61. Section("Alarms") {
  62. NavigationRow(title: "Alarms",
  63. icon: "bell.badge")
  64. {
  65. settingsPath.value.append(Sheet.alarmSettings)
  66. }
  67. }
  68. Section("Integrations") {
  69. NavigationRow(title: "Calendar",
  70. icon: "calendar")
  71. {
  72. settingsPath.value.append(Sheet.calendar)
  73. }
  74. NavigationRow(title: "Contact",
  75. icon: "person.circle")
  76. {
  77. settingsPath.value.append(Sheet.contact)
  78. }
  79. }
  80. Section("Advanced Settings") {
  81. NavigationRow(title: "Advanced",
  82. icon: "exclamationmark.shield")
  83. {
  84. settingsPath.value.append(Sheet.advanced)
  85. }
  86. }
  87. }
  88. .navigationTitle("Settings")
  89. .navigationBarTitleDisplayMode(.large)
  90. .navigationDestination(for: Sheet.self) { $0.destination }
  91. .toolbar {
  92. if let onBack {
  93. ToolbarItem(placement: .navigationBarLeading) {
  94. Button(action: onBack) {
  95. Image(systemName: "chevron.left")
  96. }
  97. }
  98. }
  99. }
  100. }
  101. }
  102. // MARK: – Section builders
  103. @ViewBuilder
  104. private var dataSection: some View {
  105. Section("Data Settings") {
  106. Picker("Units",
  107. selection: Binding(
  108. get: { Storage.shared.units.value },
  109. set: { Storage.shared.units.value = $0 }
  110. )) {
  111. Text("mg/dL").tag("mg/dL")
  112. Text("mmol/L").tag("mmol/L")
  113. }
  114. .pickerStyle(.segmented)
  115. NavigationRow(title: "Nightscout",
  116. icon: "network")
  117. {
  118. settingsPath.value.append(Sheet.nightscout)
  119. }
  120. NavigationRow(title: "Dexcom",
  121. icon: "sensor.tag.radiowaves.forward")
  122. {
  123. settingsPath.value.append(Sheet.dexcom)
  124. }
  125. }
  126. }
  127. }
  128. // MARK: – Sheet routing
  129. private enum Sheet: Hashable, Identifiable {
  130. case nightscout, dexcom
  131. case backgroundRefresh
  132. case general, graph
  133. case tabSettings
  134. case infoDisplay
  135. case alarmSettings
  136. case remote
  137. case importExport
  138. case calendar, contact
  139. case advanced
  140. case aggregatedStats
  141. var id: Self { self }
  142. @ViewBuilder
  143. var destination: some View {
  144. switch self {
  145. case .nightscout: NightscoutSettingsView(viewModel: .init())
  146. case .dexcom: DexcomSettingsView(viewModel: .init())
  147. case .backgroundRefresh: BackgroundRefreshSettingsView(viewModel: .init())
  148. case .general: GeneralSettingsView()
  149. case .graph: GraphSettingsView()
  150. case .tabSettings: TabCustomizationModal()
  151. case .infoDisplay: InfoDisplaySettingsView(viewModel: .init())
  152. case .alarmSettings: AlarmSettingsView()
  153. case .remote: RemoteSettingsView(viewModel: .init())
  154. case .importExport: ImportExportSettingsView()
  155. case .calendar: CalendarSettingsView()
  156. case .contact: ContactSettingsView(viewModel: .init())
  157. case .advanced: AdvancedSettingsView(viewModel: .init())
  158. case .aggregatedStats:
  159. AggregatedStatsViewWrapper()
  160. }
  161. }
  162. }
  163. // Helper view to access MainViewController
  164. struct AggregatedStatsViewWrapper: View {
  165. @State private var mainViewController: MainViewController?
  166. var body: some View {
  167. Group {
  168. if let mainVC = mainViewController {
  169. AggregatedStatsView(viewModel: AggregatedStatsViewModel(mainViewController: mainVC))
  170. } else {
  171. Text("Loading stats...")
  172. .onAppear {
  173. mainViewController = getMainViewController()
  174. }
  175. }
  176. }
  177. }
  178. private func getMainViewController() -> MainViewController? {
  179. guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
  180. let window = windowScene.windows.first,
  181. let rootVC = window.rootViewController
  182. else {
  183. return nil
  184. }
  185. if let mainVC = rootVC as? MainViewController {
  186. return mainVC
  187. }
  188. if let navVC = rootVC as? UINavigationController,
  189. let mainVC = navVC.viewControllers.first as? MainViewController
  190. {
  191. return mainVC
  192. }
  193. if let tabVC = rootVC as? UITabBarController {
  194. for vc in tabVC.viewControllers ?? [] {
  195. if let mainVC = vc as? MainViewController {
  196. return mainVC
  197. }
  198. if let navVC = vc as? UINavigationController,
  199. let mainVC = navVC.viewControllers.first as? MainViewController
  200. {
  201. return mainVC
  202. }
  203. }
  204. }
  205. return nil
  206. }
  207. }
  208. // MARK: – UIKit helpers (unchanged)
  209. import UIKit
  210. extension UIApplication {
  211. var topMost: UIViewController? {
  212. guard var top = keyWindow?.rootViewController else { return nil }
  213. while let presented = top.presentedViewController {
  214. top = presented
  215. }
  216. return top
  217. }
  218. }
  219. extension UIViewController {
  220. func presentSimpleAlert(title: String, message: String) {
  221. let a = UIAlertController(title: title,
  222. message: message,
  223. preferredStyle: .alert)
  224. a.addAction(UIAlertAction(title: "OK", style: .default))
  225. present(a, animated: true)
  226. }
  227. }