SettingsMenuView.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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: "Units and Metrics",
  37. icon: "scalemass")
  38. {
  39. settingsPath.value.append(Sheet.units)
  40. }
  41. NavigationRow(title: "Tabs",
  42. icon: "rectangle.3.group")
  43. {
  44. settingsPath.value.append(Sheet.tabSettings)
  45. }
  46. }
  47. Section("App Settings") {
  48. NavigationRow(title: "Background Refresh",
  49. icon: "arrow.clockwise")
  50. {
  51. settingsPath.value.append(Sheet.backgroundRefresh)
  52. }
  53. NavigationRow(title: "Import/Export",
  54. icon: "square.and.arrow.down")
  55. {
  56. settingsPath.value.append(Sheet.importExport)
  57. }
  58. NavigationRow(title: "APN",
  59. icon: "bell.and.waves.left.and.right")
  60. {
  61. settingsPath.value.append(Sheet.apn)
  62. }
  63. #if !targetEnvironment(macCatalyst)
  64. NavigationRow(title: "Live Activity",
  65. icon: "dot.radiowaves.left.and.right")
  66. {
  67. settingsPath.value.append(Sheet.liveActivity)
  68. }
  69. #endif
  70. if !nightscoutURL.value.isEmpty {
  71. NavigationRow(title: "Remote",
  72. icon: "antenna.radiowaves.left.and.right")
  73. {
  74. settingsPath.value.append(Sheet.remote)
  75. }
  76. }
  77. }
  78. Section("Alarms") {
  79. NavigationRow(title: "Alarms",
  80. icon: "bell.badge")
  81. {
  82. settingsPath.value.append(Sheet.alarmSettings)
  83. }
  84. }
  85. Section("Integrations") {
  86. NavigationRow(title: "Calendar",
  87. icon: "calendar")
  88. {
  89. settingsPath.value.append(Sheet.calendar)
  90. }
  91. NavigationRow(title: "Contact",
  92. icon: "person.circle")
  93. {
  94. settingsPath.value.append(Sheet.contact)
  95. }
  96. }
  97. Section("Advanced Settings") {
  98. NavigationRow(title: "Advanced",
  99. icon: "exclamationmark.shield")
  100. {
  101. settingsPath.value.append(Sheet.advanced)
  102. }
  103. }
  104. }
  105. .navigationTitle("Settings")
  106. .navigationBarTitleDisplayMode(.large)
  107. .navigationDestination(for: Sheet.self) { $0.destination }
  108. .toolbar {
  109. if let onBack {
  110. ToolbarItem(placement: .navigationBarLeading) {
  111. Button(action: onBack) {
  112. Image(systemName: "chevron.left")
  113. }
  114. }
  115. }
  116. }
  117. }
  118. }
  119. // MARK: – Section builders
  120. @ViewBuilder
  121. private var dataSection: some View {
  122. Section("Data Settings") {
  123. NavigationRow(title: "Nightscout",
  124. icon: "network")
  125. {
  126. settingsPath.value.append(Sheet.nightscout)
  127. }
  128. NavigationRow(title: "Dexcom",
  129. icon: "sensor.tag.radiowaves.forward")
  130. {
  131. settingsPath.value.append(Sheet.dexcom)
  132. }
  133. }
  134. }
  135. }
  136. // MARK: – Sheet routing
  137. private enum Sheet: Hashable, Identifiable {
  138. case units
  139. case nightscout, dexcom
  140. case backgroundRefresh
  141. case general, graph
  142. case tabSettings
  143. case infoDisplay
  144. case alarmSettings
  145. case apn
  146. #if !targetEnvironment(macCatalyst)
  147. case liveActivity
  148. #endif
  149. case remote
  150. case importExport
  151. case calendar, contact
  152. case advanced
  153. case aggregatedStats
  154. var id: Self { self }
  155. @ViewBuilder
  156. var destination: some View {
  157. switch self {
  158. case .units: UnitsSettingsView()
  159. case .nightscout: NightscoutSettingsView(viewModel: .init())
  160. case .dexcom: DexcomSettingsView(viewModel: .init())
  161. case .backgroundRefresh: BackgroundRefreshSettingsView(viewModel: .init())
  162. case .general: GeneralSettingsView()
  163. case .graph: GraphSettingsView()
  164. case .tabSettings: TabCustomizationModal()
  165. case .infoDisplay: InfoDisplaySettingsView(viewModel: .init())
  166. case .alarmSettings: AlarmSettingsView()
  167. case .apn: APNSettingsView()
  168. #if !targetEnvironment(macCatalyst)
  169. case .liveActivity: LiveActivitySettingsView()
  170. #endif
  171. case .remote: RemoteSettingsView(viewModel: .init())
  172. case .importExport: ImportExportSettingsView()
  173. case .calendar: CalendarSettingsView()
  174. case .contact: ContactSettingsView(viewModel: .init())
  175. case .advanced: AdvancedSettingsView(viewModel: .init())
  176. case .aggregatedStats:
  177. AggregatedStatsViewWrapper()
  178. }
  179. }
  180. }
  181. // Helper view to access MainViewController
  182. struct AggregatedStatsViewWrapper: View {
  183. @State private var mainViewController: MainViewController?
  184. var body: some View {
  185. Group {
  186. if let mainVC = mainViewController {
  187. AggregatedStatsContentView(mainViewController: mainVC)
  188. } else {
  189. Text("Loading stats...")
  190. .onAppear {
  191. mainViewController = getMainViewController()
  192. }
  193. }
  194. }
  195. }
  196. private func getMainViewController() -> MainViewController? {
  197. guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
  198. let window = windowScene.windows.first,
  199. let rootVC = window.rootViewController
  200. else {
  201. return nil
  202. }
  203. if let mainVC = rootVC as? MainViewController {
  204. return mainVC
  205. }
  206. if let navVC = rootVC as? UINavigationController,
  207. let mainVC = navVC.viewControllers.first as? MainViewController
  208. {
  209. return mainVC
  210. }
  211. if let tabVC = rootVC as? UITabBarController {
  212. for vc in tabVC.viewControllers ?? [] {
  213. if let mainVC = vc as? MainViewController {
  214. return mainVC
  215. }
  216. if let navVC = vc as? UINavigationController,
  217. let mainVC = navVC.viewControllers.first as? MainViewController
  218. {
  219. return mainVC
  220. }
  221. }
  222. }
  223. return nil
  224. }
  225. }
  226. // MARK: – UIKit helpers (unchanged)
  227. import UIKit
  228. extension UIApplication {
  229. var topMost: UIViewController? {
  230. guard var top = keyWindow?.rootViewController else { return nil }
  231. while let presented = top.presentedViewController {
  232. top = presented
  233. }
  234. return top
  235. }
  236. }
  237. extension UIViewController {
  238. func presentSimpleAlert(title: String, message: String) {
  239. let a = UIAlertController(title: title,
  240. message: message,
  241. preferredStyle: .alert)
  242. a.addAction(UIAlertAction(title: "OK", style: .default))
  243. present(a, animated: true)
  244. }
  245. }