| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- // LoopFollow
- // SettingsMenuView.swift
- // Created by Jonas Björkert.
- import SwiftUI
- import UIKit
- struct SettingsMenuView: View {
- // MARK: - Observed Objects
- @ObservedObject private var nightscoutURL = Storage.shared.url
- @ObservedObject private var settingsPath = Observable.shared.settingsPath
- // MARK: – Local state
- @State private var latestVersion: String?
- @State private var versionTint: Color = .secondary
- // MARK: – Body
- var body: some View {
- NavigationStack(path: $settingsPath.value) {
- List {
- // ───────── Data settings ─────────
- dataSection
- // ───────── App settings ─────────
- Section("App Settings") {
- NavigationRow(title: "Background Refresh Settings",
- icon: "arrow.clockwise")
- {
- settingsPath.value.append(Sheet.backgroundRefresh)
- }
- NavigationRow(title: "General Settings",
- icon: "gearshape")
- {
- settingsPath.value.append(Sheet.general)
- }
- NavigationRow(title: "Graph Settings",
- icon: "chart.xyaxis.line")
- {
- settingsPath.value.append(Sheet.graph)
- }
- if !nightscoutURL.value.isEmpty {
- NavigationRow(title: "Information Display Settings",
- icon: "info.circle")
- {
- settingsPath.value.append(Sheet.infoDisplay)
- }
- NavigationRow(title: "Remote Settings",
- icon: "antenna.radiowaves.left.and.right")
- {
- settingsPath.value.append(Sheet.remote)
- }
- }
- }
- // ───────── Alarms ─────────
- Section {
- NavigationRow(title: "Alarms",
- icon: "bell")
- {
- settingsPath.value.append(Sheet.alarmsList)
- }
- NavigationRow(title: "Alarm Settings",
- icon: "bell.badge")
- {
- settingsPath.value.append(Sheet.alarmSettings)
- }
- }
- // ───────── Integrations ─────────
- Section("Integrations") {
- NavigationRow(title: "Calendar",
- icon: "calendar")
- {
- settingsPath.value.append(Sheet.calendar)
- }
- NavigationRow(title: "Contact",
- icon: "person.circle")
- {
- settingsPath.value.append(Sheet.contact)
- }
- }
- // ───────── Advanced / Logs ─────────
- Section("Advanced Settings") {
- NavigationRow(title: "Advanced Settings",
- icon: "exclamationmark.shield")
- {
- settingsPath.value.append(Sheet.advanced)
- }
- }
- Section("Logging") {
- NavigationRow(title: "View Log",
- icon: "doc.text.magnifyingglass")
- {
- settingsPath.value.append(Sheet.viewLog)
- }
- ActionRow(title: "Share Logs",
- icon: "square.and.arrow.up",
- action: shareLogs)
- }
- // ───────── Community ─────────
- Section("Community") {
- LinkRow(title: "LoopFollow Facebook Group",
- icon: "person.2.fill",
- url: URL(string: "https://www.facebook.com/groups/loopfollowlnl")!)
- }
- // ───────── Build info ─────────
- buildInfoSection
- }
- .navigationTitle("Settings")
- .navigationDestination(for: Sheet.self) { $0.destination }
- }
- .task { await refreshVersionInfo() }
- }
- // MARK: – Section builders
- @ViewBuilder
- private var dataSection: some View {
- Section("Data Settings") {
- Picker("Units",
- selection: Binding(
- get: { Storage.shared.units.value },
- set: { Storage.shared.units.value = $0 }
- )) {
- Text("mg/dL").tag("mg/dL")
- Text("mmol/L").tag("mmol/L")
- }
- .pickerStyle(.segmented)
- NavigationRow(title: "Nightscout Settings",
- icon: "network")
- {
- settingsPath.value.append(Sheet.nightscout)
- }
- NavigationRow(title: "Dexcom Settings",
- icon: "sensor.tag.radiowaves.forward")
- {
- settingsPath.value.append(Sheet.dexcom)
- }
- }
- }
- @ViewBuilder
- private var buildInfoSection: some View {
- let build = BuildDetails.default
- let ver = AppVersionManager().version()
- Section("Build Information") {
- keyValue("Version", ver, tint: versionTint)
- keyValue("Latest version", latestVersion ?? "Fetching…")
- if !(build.isMacApp() || build.isSimulatorBuild()) {
- keyValue(build.expirationHeaderString,
- dateTimeUtils.formattedDate(from: build.calculateExpirationDate()))
- }
- keyValue("Built",
- dateTimeUtils.formattedDate(from: build.buildDate()))
- keyValue("Branch", build.branchAndSha)
- }
- }
- // MARK: – Helpers
- private func keyValue(_ key: String,
- _ value: String,
- tint: Color = .secondary) -> some View
- {
- HStack {
- Text(key)
- Spacer()
- Text(value).foregroundColor(tint)
- }
- }
- private func refreshVersionInfo() async {
- let mgr = AppVersionManager()
- let (latest, newer, blacklisted) = await mgr.checkForNewVersionAsync()
- latestVersion = latest ?? "Unknown"
- let current = mgr.version()
- versionTint = blacklisted ? .red
- : newer ? .orange
- : latest == current ? .green
- : .secondary
- }
- private func shareLogs() {
- let files = LogManager.shared.logFilesForTodayAndYesterday()
- guard !files.isEmpty else {
- UIApplication.shared.topMost?.presentSimpleAlert(
- title: "No Logs Available",
- message: "There are no logs to share."
- )
- return
- }
- let avc = UIActivityViewController(activityItems: files,
- applicationActivities: nil)
- UIApplication.shared.topMost?.present(avc, animated: true)
- }
- }
- // MARK: – Sheet routing
- private enum Sheet: Hashable, Identifiable {
- case nightscout, dexcom
- case backgroundRefresh
- case general, graph
- case infoDisplay
- case alarmsList, alarmSettings
- case remote
- case calendar, contact
- case advanced
- case viewLog
- var id: Self { self }
- @ViewBuilder
- var destination: some View {
- switch self {
- case .nightscout: NightscoutSettingsView(viewModel: .init())
- case .dexcom: DexcomSettingsView(viewModel: .init())
- case .backgroundRefresh: BackgroundRefreshSettingsView(viewModel: .init())
- case .general: GeneralSettingsView()
- case .graph: GraphSettingsView()
- case .infoDisplay: InfoDisplaySettingsView(viewModel: .init())
- case .alarmsList: AlarmListView()
- case .alarmSettings: AlarmSettingsView()
- case .remote: RemoteSettingsView(viewModel: .init())
- case .calendar: CalendarSettingsView()
- case .contact: ContactSettingsView(viewModel: .init())
- case .advanced: AdvancedSettingsView(viewModel: .init())
- case .viewLog: LogView(viewModel: .init())
- }
- }
- }
- // MARK: – UIKit helpers (unchanged)
- import UIKit
- extension UIApplication {
- var topMost: UIViewController? {
- guard var top = keyWindow?.rootViewController else { return nil }
- while let presented = top.presentedViewController {
- top = presented
- }
- return top
- }
- }
- extension UIViewController {
- func presentSimpleAlert(title: String, message: String) {
- let a = UIAlertController(title: title,
- message: message,
- preferredStyle: .alert)
- a.addAction(UIAlertAction(title: "OK", style: .default))
- present(a, animated: true)
- }
- }
|