SettingsMenuView.swift 8.8 KB

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