AlarmSettingsView.swift 7.7 KB

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