AlarmSettingsView.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //
  2. // AlarmSettingsView.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2025‑04‑20.
  6. // Copyright © 2025 Jon Fawcett. All rights reserved.
  7. //
  8. import SwiftUI
  9. struct AlarmSettingsView: View {
  10. @ObservedObject private var cfgStore = Storage.shared.alarmConfiguration
  11. @Environment(\.presentationMode) var presentationMode
  12. /// Helper to bind an optional Date? into a non‑optional Date for DatePicker
  13. private func optDateBinding(_ b: Binding<Date?>) -> Binding<Date> {
  14. Binding(
  15. get: { b.wrappedValue ?? Date() },
  16. set: { b.wrappedValue = $0 }
  17. )
  18. }
  19. private var dayBinding: Binding<Date> {
  20. Binding(
  21. get: {
  22. var c = Calendar.current.dateComponents([.year, .month, .day], from: Date())
  23. c.hour = cfgStore.value.dayStart.hour
  24. c.minute = cfgStore.value.dayStart.minute
  25. return Calendar.current.date(from: c)!
  26. },
  27. set: { d in
  28. let hc = Calendar.current.dateComponents([.hour, .minute], from: d)
  29. cfgStore.value.dayStart = TimeOfDay(hour: hc.hour!, minute: hc.minute!)
  30. }
  31. )
  32. }
  33. private var nightBinding: Binding<Date> {
  34. Binding(
  35. get: {
  36. var c = Calendar.current.dateComponents([.year, .month, .day], from: Date())
  37. c.hour = cfgStore.value.nightStart.hour
  38. c.minute = cfgStore.value.nightStart.minute
  39. return Calendar.current.date(from: c)!
  40. },
  41. set: { d in
  42. let hc = Calendar.current.dateComponents([.hour, .minute], from: d)
  43. cfgStore.value.nightStart = TimeOfDay(hour: hc.hour!, minute: hc.minute!)
  44. }
  45. )
  46. }
  47. var body: some View {
  48. NavigationView {
  49. Form {
  50. Section(
  51. header: Text("Snooze & Mute Options"),
  52. footer: Text("""
  53. Snooze All turns everything off, \
  54. Mute All turns off phone sounds but leaves vibration \
  55. and iOS notifications on
  56. """)
  57. ) {
  58. // Snooze All Until
  59. DatePicker(
  60. "Snooze All Until",
  61. selection: optDateBinding(
  62. Binding(
  63. get: { cfgStore.value.snoozeUntil },
  64. set: { cfgStore.value.snoozeUntil = $0 }
  65. )
  66. ),
  67. displayedComponents: [.date, .hourAndMinute]
  68. )
  69. Toggle(
  70. "All Alerts Snoozed",
  71. isOn: Binding(
  72. get: {
  73. if let until = cfgStore.value.snoozeUntil {
  74. return until > Date()
  75. }
  76. return false
  77. },
  78. set: { newOn in
  79. if newOn {
  80. // if turning on, set a default 1h snooze if none or expired
  81. if cfgStore.value.snoozeUntil == nil || cfgStore.value.snoozeUntil! <= Date() {
  82. cfgStore.value.snoozeUntil = Date().addingTimeInterval(3600)
  83. }
  84. } else {
  85. cfgStore.value.snoozeUntil = nil
  86. }
  87. }
  88. )
  89. )
  90. // Mute All Until
  91. DatePicker(
  92. "Mute All Until",
  93. selection: optDateBinding(
  94. Binding(
  95. get: { cfgStore.value.muteUntil },
  96. set: { cfgStore.value.muteUntil = $0 }
  97. )
  98. ),
  99. displayedComponents: [.date, .hourAndMinute]
  100. )
  101. Toggle(
  102. "All Sounds Muted",
  103. isOn: Binding(
  104. get: {
  105. if let until = cfgStore.value.muteUntil {
  106. return until > Date()
  107. }
  108. return false
  109. },
  110. set: { newOn in
  111. if newOn {
  112. if cfgStore.value.muteUntil == nil || cfgStore.value.muteUntil! <= Date() {
  113. cfgStore.value.muteUntil = Date().addingTimeInterval(3600)
  114. }
  115. } else {
  116. cfgStore.value.muteUntil = nil
  117. }
  118. }
  119. )
  120. )
  121. }
  122. Section(header: Text("Alarm Settings")) {
  123. Toggle(
  124. "Override System Volume",
  125. isOn: Binding(
  126. get: { cfgStore.value.overrideSystemOutputVolume },
  127. set: { cfgStore.value.overrideSystemOutputVolume = $0 }
  128. )
  129. )
  130. if cfgStore.value.overrideSystemOutputVolume {
  131. Stepper(
  132. "Volume Level: \(Int(cfgStore.value.forcedOutputVolume * 100))%",
  133. value: Binding(
  134. get: { Double(cfgStore.value.forcedOutputVolume) },
  135. set: { cfgStore.value.forcedOutputVolume = Float($0) }
  136. ),
  137. in: 0...1,
  138. step: 0.05
  139. )
  140. }
  141. Toggle(
  142. "Audio During Calls",
  143. isOn: Binding(
  144. get: { cfgStore.value.audioDuringCalls },
  145. set: { cfgStore.value.audioDuringCalls = $0 }
  146. )
  147. )
  148. Toggle(
  149. "Ignore Zero BG",
  150. isOn: Binding(
  151. get: { cfgStore.value.ignoreZeroBG },
  152. set: { cfgStore.value.ignoreZeroBG = $0 }
  153. )
  154. )
  155. Toggle(
  156. "Auto‑Snooze CGM Start",
  157. isOn: Binding(
  158. get: { cfgStore.value.autoSnoozeCGMStart },
  159. set: { cfgStore.value.autoSnoozeCGMStart = $0 }
  160. )
  161. )
  162. }
  163. }
  164. .navigationTitle("Alarm Settings")
  165. .toolbar {
  166. ToolbarItem(placement: .navigationBarTrailing) {
  167. Button("Done") {
  168. presentationMode.wrappedValue.dismiss()
  169. }
  170. }
  171. }
  172. }
  173. }
  174. }