| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- // LoopFollow
- // SettingsMenuView.swift
- // Created by Jonas Björkert on 2025-05-26.
- import SwiftUI
- import UIKit
- struct SettingsMenuView: View {
- // MARK: – Call-backs
- let onNightscoutVisibilityChange: (_ enabled: Bool) -> Void
- // MARK: – Local state
- @State private var sheet: Sheet?
- @State private var latestVersion: String?
- @State private var versionTint: Color = .secondary
- // MARK: – Body
- var body: some View {
- NavigationStack {
- List {
- // ───────── Data settings ─────────
- dataSection
- // ───────── App settings ─────────
- Section("App Settings") {
- NavigationRow(title: "Background Refresh Settings",
- icon: "arrow.clockwise")
- {
- sheet = .backgroundRefresh
- }
- NavigationRow(title: "General Settings",
- icon: "gearshape")
- {
- sheet = .general
- }
- NavigationRow(title: "Graph Settings",
- icon: "chart.xyaxis.line")
- {
- sheet = .graph
- }
- if IsNightscoutEnabled() {
- NavigationRow(title: "Information Display Settings",
- icon: "info.circle")
- {
- sheet = .infoDisplay
- }
- }
- }
- // ───────── Alarms ─────────
- Section {
- NavigationRow(title: "Alarms",
- icon: "bell")
- {
- sheet = .alarmsList
- }
- NavigationRow(title: "Alarm Settings",
- icon: "bell.badge")
- {
- sheet = .alarmSettings
- }
- }
- // ───────── Integrations ─────────
- Section("Integrations") {
- NavigationRow(title: "Calendar",
- icon: "calendar")
- {
- sheet = .calendar
- }
- NavigationRow(title: "Contact",
- icon: "person.circle")
- {
- sheet = .contact
- }
- }
- // ───────── Advanced / Logs ─────────
- Section("Advanced Settings") {
- NavigationRow(title: "Advanced Settings",
- icon: "exclamationmark.shield")
- {
- sheet = .advanced
- }
- }
- Section("Logging") {
- NavigationRow(title: "View Log",
- icon: "doc.text.magnifyingglass")
- {
- sheet = .viewLog
- }
- ActionRow(title: "Share Logs",
- icon: "square.and.arrow.up")
- {
- 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")
- }
- .task { await refreshVersionInfo() }
- .sheet(item: $sheet) { $0.destination }
- }
- // MARK: – Section builders
- @ViewBuilder
- private var dataSection: some View {
- Section("Data Settings") {
- Picker("Units",
- selection: Binding(
- get: { UserDefaultsRepository.units.value },
- set: { UserDefaultsRepository.units.value = $0 }
- )) {
- Text("mg/dL").tag("mg/dL")
- Text("mmol/L").tag("mmol/L")
- }
- .pickerStyle(.segmented)
- NavigationRow(title: "Nightscout Settings",
- icon: "network")
- {
- sheet = .nightscout
- }
- NavigationRow(title: "Dexcom Settings",
- icon: "sensor.tag.radiowaves.forward")
- {
- sheet = .dexcom
- }
- }
- .onAppear {
- onNightscoutVisibilityChange(IsNightscoutEnabled())
- }
- }
- @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 (unchanged)
- private enum Sheet: 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: Int { hashValue }
- @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: WatchSettingsView()
- case .contact: ContactSettingsView(viewModel: .init())
- case .advanced: AdvancedSettingsView(viewModel: .init())
- case .viewLog: LogView(viewModel: .init())
- }
- }
- }
- 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)
- }
- }
|