SettingsMenuView.swift 8.9 KB

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