AlarmSettingsView.swift 7.9 KB

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