WatchSettingsView.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // LoopFollow
  2. // WatchSettingsView.swift
  3. // Created by Jonas Björkert on 2025-05-26.
  4. import EventKit
  5. import SwiftUI
  6. struct WatchSettingsView: View {
  7. // MARK: Storage bindings
  8. @ObservedObject private var writeCalendarEvent = Storage.shared.writeCalendarEvent
  9. @ObservedObject private var calendarIdentifier = Storage.shared.calendarIdentifier
  10. @ObservedObject private var watchLine1 = Storage.shared.watchLine1
  11. @ObservedObject private var watchLine2 = Storage.shared.watchLine2
  12. // MARK: Local state
  13. @Environment(\.presentationMode) private var presentationMode
  14. @State private var calendars: [EKCalendar] = []
  15. @State private var accessDenied = false
  16. // MARK: Body
  17. var body: some View {
  18. NavigationStack {
  19. Form {
  20. // ------------- Calendar write -------------
  21. Section {
  22. Toggle("Save BG to Calendar",
  23. isOn: $writeCalendarEvent.value)
  24. .disabled(accessDenied) // prevent use when no access
  25. } footer: {
  26. Text("""
  27. Add the Apple-Calendar complication to your watch or CarPlay \
  28. to see BG readings. Create a separate calendar (e.g. “Follow”) \
  29. — this view will **delete** events on the same calendar each time \
  30. it writes new readings.
  31. """)
  32. }
  33. // ------------- Access / calendar picker -------------
  34. if accessDenied {
  35. Text("Calendar access denied")
  36. .foregroundColor(.red)
  37. } else {
  38. if !calendars.isEmpty {
  39. Picker("Calendar",
  40. selection: $calendarIdentifier.value)
  41. {
  42. ForEach(calendars, id: \.calendarIdentifier) { cal in
  43. Text(cal.title).tag(cal.calendarIdentifier)
  44. }
  45. }
  46. }
  47. }
  48. // ------------- Template lines -------------
  49. Section("Calendar Text") {
  50. TextField("Line 1", text: $watchLine1.value)
  51. .textInputAutocapitalization(.never)
  52. .disableAutocorrection(true)
  53. TextField("Line 2", text: $watchLine2.value)
  54. .textInputAutocapitalization(.never)
  55. .disableAutocorrection(true)
  56. }
  57. // ------------- Variable cheat-sheet -------------
  58. Section("Available Variables") {
  59. ForEach(variableDescriptions, id: \.self) { desc in
  60. Text(desc)
  61. }
  62. }
  63. }
  64. .navigationTitle("Calendar")
  65. .toolbar {
  66. ToolbarItem(placement: .confirmationAction) {
  67. Button("Done") { presentationMode.wrappedValue.dismiss() }
  68. }
  69. }
  70. .task { // runs once on appear
  71. await requestCalendarAccessAndLoad()
  72. }
  73. }
  74. .preferredColorScheme(Storage.shared.forceDarkMode.value ? .dark : nil)
  75. }
  76. // MARK: - Helpers
  77. /// Returns array of “%TOKEN% : Explanation” strings used in the cheat-sheet.
  78. private var variableDescriptions: [String] {
  79. [
  80. "%BG% : Blood-glucose reading",
  81. "%DIRECTION% : Dexcom trend arrow",
  82. "%DELTA% : Difference from last reading",
  83. "%IOB% : Insulin-on-Board",
  84. "%COB% : Carbs-on-Board",
  85. "%BASAL% : Current basal U/h",
  86. "%LOOP% : Loop status symbol",
  87. "%OVERRIDE% : Active override %",
  88. "%MINAGO% : Minutes since last reading",
  89. ]
  90. }
  91. /// Ask for calendar permission, then pull the user’s calendars.
  92. private func requestCalendarAccessAndLoad() async {
  93. let store = EKEventStore()
  94. do {
  95. try await store.requestAccess(to: .event)
  96. accessDenied = false
  97. calendars = store.calendars(for: .event)
  98. .sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending }
  99. } catch {
  100. accessDenied = true
  101. }
  102. // If the previously-saved calendar no longer exists, blank it out
  103. if !calendarIdentifier.value.isEmpty &&
  104. !calendars.contains(where: { $0.calendarIdentifier == calendarIdentifier.value })
  105. {
  106. calendarIdentifier.value = ""
  107. }
  108. }
  109. }