SettingsMenuView.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // LoopFollow
  2. // SettingsMenuView.swift
  3. import SwiftUI
  4. import UIKit
  5. struct SettingsMenuView: View {
  6. @ObservedObject private var nightscoutURL = Storage.shared.url
  7. var body: some View {
  8. List {
  9. dataSection
  10. Section("Display Settings") {
  11. NavigationRow(title: "General",
  12. icon: "gearshape",
  13. value: SettingsRoute.general)
  14. NavigationRow(title: "Graph",
  15. icon: "chart.xyaxis.line",
  16. value: SettingsRoute.graph)
  17. if !nightscoutURL.value.isEmpty {
  18. NavigationRow(title: "Information Display",
  19. icon: "info.circle",
  20. value: SettingsRoute.infoDisplay)
  21. }
  22. NavigationRow(title: "Units and Metrics",
  23. icon: "scalemass",
  24. value: SettingsRoute.units)
  25. NavigationRow(title: "Tabs",
  26. icon: "rectangle.3.group",
  27. value: SettingsRoute.tabSettings)
  28. }
  29. Section("App Settings") {
  30. NavigationRow(title: "Background Refresh",
  31. icon: "arrow.clockwise",
  32. value: SettingsRoute.backgroundRefresh)
  33. NavigationRow(title: "Import/Export",
  34. icon: "square.and.arrow.down",
  35. value: SettingsRoute.importExport)
  36. NavigationRow(title: "APN",
  37. icon: "bell.and.waves.left.and.right",
  38. value: SettingsRoute.apn)
  39. #if !targetEnvironment(macCatalyst)
  40. NavigationRow(title: "Live Activity",
  41. icon: "dot.radiowaves.left.and.right",
  42. value: SettingsRoute.liveActivity)
  43. #endif
  44. if !nightscoutURL.value.isEmpty {
  45. NavigationRow(title: "Remote",
  46. icon: "antenna.radiowaves.left.and.right",
  47. value: SettingsRoute.remote)
  48. }
  49. }
  50. Section("Alarms") {
  51. NavigationRow(title: "Alarms",
  52. icon: "bell.badge",
  53. value: SettingsRoute.alarmSettings)
  54. }
  55. Section("Integrations") {
  56. NavigationRow(title: "Calendar",
  57. icon: "calendar",
  58. value: SettingsRoute.calendar)
  59. NavigationRow(title: "Contact",
  60. icon: "person.circle",
  61. value: SettingsRoute.contact)
  62. }
  63. Section("Advanced Settings") {
  64. NavigationRow(title: "Advanced",
  65. icon: "exclamationmark.shield",
  66. value: SettingsRoute.advanced)
  67. }
  68. }
  69. .navigationTitle("Settings")
  70. .navigationBarTitleDisplayMode(.large)
  71. }
  72. // MARK: – Section builders
  73. @ViewBuilder
  74. private var dataSection: some View {
  75. Section("Data Settings") {
  76. NavigationRow(title: "Nightscout",
  77. icon: "network",
  78. value: SettingsRoute.nightscout)
  79. NavigationRow(title: "Dexcom",
  80. icon: "sensor.tag.radiowaves.forward",
  81. value: SettingsRoute.dexcom)
  82. }
  83. }
  84. }
  85. // MARK: – Sheet routing
  86. enum SettingsRoute: Hashable, Identifiable {
  87. case settings
  88. case units
  89. case nightscout, dexcom
  90. case backgroundRefresh
  91. case general, graph
  92. case tabSettings
  93. case infoDisplay
  94. case alarmSettings
  95. case apn
  96. #if !targetEnvironment(macCatalyst)
  97. case liveActivity
  98. #endif
  99. case remote
  100. case importExport
  101. case calendar, contact
  102. case advanced
  103. case aggregatedStats
  104. var id: Self { self }
  105. @ViewBuilder
  106. var destination: some View {
  107. switch self {
  108. case .settings: SettingsMenuView()
  109. case .units: UnitsSettingsView()
  110. case .nightscout: NightscoutSettingsView(viewModel: .init())
  111. case .dexcom: DexcomSettingsView(viewModel: .init())
  112. case .backgroundRefresh: BackgroundRefreshSettingsView(viewModel: .init())
  113. case .general: GeneralSettingsView()
  114. case .graph: GraphSettingsView()
  115. case .tabSettings: TabCustomizationModal()
  116. case .infoDisplay: InfoDisplaySettingsView(viewModel: .init())
  117. case .alarmSettings: AlarmSettingsView()
  118. case .apn: APNSettingsView()
  119. #if !targetEnvironment(macCatalyst)
  120. case .liveActivity: LiveActivitySettingsView()
  121. #endif
  122. case .remote: RemoteSettingsView(viewModel: .init())
  123. case .importExport: ImportExportSettingsView()
  124. case .calendar: CalendarSettingsView()
  125. case .contact: ContactSettingsView(viewModel: .init())
  126. case .advanced: AdvancedSettingsView(viewModel: .init())
  127. case .aggregatedStats:
  128. AggregatedStatsViewWrapper()
  129. }
  130. }
  131. }
  132. // Helper view to access MainViewController
  133. struct AggregatedStatsViewWrapper: View {
  134. @State private var mainViewController: MainViewController?
  135. var body: some View {
  136. Group {
  137. if let mainVC = mainViewController {
  138. AggregatedStatsContentView(mainViewController: mainVC)
  139. } else {
  140. Text("Loading stats...")
  141. .onAppear {
  142. mainViewController = getMainViewController()
  143. }
  144. }
  145. }
  146. }
  147. private func getMainViewController() -> MainViewController? {
  148. MainViewController.shared
  149. }
  150. }
  151. // MARK: – UIKit helpers (unchanged)
  152. import UIKit
  153. extension UIApplication {
  154. var topMost: UIViewController? {
  155. // `keyWindow` is deprecated and returns nil on Mac Catalyst / multi-window iPad.
  156. // Walk connected scenes instead and prefer the foreground-active one.
  157. let windowScenes = connectedScenes.compactMap { $0 as? UIWindowScene }
  158. let activeScene = windowScenes.first { $0.activationState == .foregroundActive }
  159. ?? windowScenes.first
  160. let rootVC = activeScene?.windows.first(where: \.isKeyWindow)?.rootViewController
  161. ?? activeScene?.windows.first?.rootViewController
  162. guard var top = rootVC else { return nil }
  163. while let presented = top.presentedViewController {
  164. top = presented
  165. }
  166. return top
  167. }
  168. }
  169. extension UIViewController {
  170. func presentSimpleAlert(title: String, message: String) {
  171. let a = UIAlertController(title: title,
  172. message: message,
  173. preferredStyle: .alert)
  174. a.addAction(UIAlertAction(title: "OK", style: .default))
  175. present(a, animated: true)
  176. }
  177. }