AlarmEditorFields.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //
  2. // AlarmEditorFields.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2025-04-21.
  6. // Copyright © 2025 Jon Fawcett. All rights reserved.
  7. //
  8. import SwiftUI
  9. struct AlarmGeneralSection: View {
  10. @Binding var alarm: Alarm
  11. var body: some View {
  12. Section(header: Text("General")) {
  13. HStack {
  14. Text("Name")
  15. TextField("Alarm Name", text: $alarm.name)
  16. .multilineTextAlignment(.trailing)
  17. .textFieldStyle(.plain)
  18. }
  19. Toggle("Enabled", isOn: $alarm.isEnabled)
  20. }
  21. }
  22. }
  23. struct AlarmStepperSection: View {
  24. let title: String
  25. let range: ClosedRange<Double>
  26. let step: Double
  27. let unitLabel: String
  28. @Binding var value: Double
  29. var body: some View {
  30. Section(
  31. header: Text(title),
  32. footer: Text("\(title) in \(Int(range.lowerBound))–\(Int(range.upperBound)) \(unitLabel)")
  33. ) {
  34. Stepper(
  35. "\(title): \(Int(value)) \(unitLabel)",
  36. value: $value,
  37. in: range,
  38. step: step
  39. )
  40. }
  41. }
  42. }
  43. struct AlarmSnoozeSection: View {
  44. let title: String
  45. let range: ClosedRange<Double>
  46. let step: Double
  47. let unitLabel: String
  48. @Binding var value: Double
  49. var body: some View {
  50. Section(
  51. header: Text(title),
  52. footer: Text("How long to snooze after firing \(Int(range.lowerBound))–\(Int(range.upperBound)) \(unitLabel)")
  53. ) {
  54. Stepper(
  55. "\(title): \(Int(value)) \(unitLabel)",
  56. value: $value,
  57. in: range,
  58. step: step
  59. )
  60. }
  61. }
  62. }
  63. import SwiftUI
  64. struct AlarmAudioSection: View {
  65. @Binding var alarm: Alarm
  66. @State private var showingTonePicker = false
  67. var body: some View {
  68. Section(header: Text("Alert Sound")) {
  69. // ——— Tone Row ———
  70. Button {
  71. showingTonePicker = true
  72. } label: {
  73. HStack {
  74. Text("Tone")
  75. Spacer()
  76. Text(alarm.soundFile.displayName)
  77. .foregroundColor(.secondary)
  78. Image(systemName: "chevron.right")
  79. .foregroundColor(.secondary)
  80. }
  81. }
  82. .sheet(isPresented: $showingTonePicker) {
  83. NavigationView {
  84. List {
  85. ForEach(SoundFile.allCases) { tone in
  86. Button {
  87. alarm.soundFile = tone
  88. // play test tone
  89. AlarmSound.setSoundFile(str: tone.rawValue)
  90. AlarmSound.stop()
  91. AlarmSound.playTest()
  92. } label: {
  93. HStack {
  94. Text(tone.displayName)
  95. if alarm.soundFile == tone {
  96. Spacer()
  97. Image(systemName: "checkmark")
  98. .foregroundColor(.accentColor)
  99. }
  100. }
  101. }
  102. }
  103. }
  104. .navigationTitle("Choose Tone")
  105. .toolbar {
  106. ToolbarItem(placement: .confirmationAction) {
  107. Button("Done") {
  108. AlarmSound.stop()
  109. showingTonePicker = false
  110. }
  111. }
  112. }
  113. }
  114. }
  115. // ——— Play / Repeat Toggles ———
  116. VStack(alignment: .leading, spacing: 8) {
  117. Text("Play")
  118. .font(.subheadline)
  119. .foregroundColor(.secondary)
  120. Picker("", selection: $alarm.playSoundOption) {
  121. ForEach(PlaySoundOption.allCases, id: \.self) { opt in
  122. Text(opt.rawValue.capitalized).tag(opt)
  123. }
  124. }
  125. .pickerStyle(.segmented)
  126. }
  127. VStack(alignment: .leading, spacing: 8) {
  128. Text("Repeat")
  129. .font(.subheadline)
  130. .foregroundColor(.secondary)
  131. Picker("", selection: $alarm.repeatSoundOption) {
  132. ForEach(RepeatSoundOption.allCases, id: \.self) { opt in
  133. Text(opt.rawValue.capitalized).tag(opt)
  134. }
  135. }
  136. .pickerStyle(.segmented)
  137. }
  138. }
  139. }
  140. }
  141. struct AlarmActiveSection: View {
  142. @Binding var alarm: Alarm
  143. var body: some View {
  144. Section(header: Text("Active During")) {
  145. Picker("Active", selection: $alarm.activeOption) {
  146. Text("Always").tag(ActiveOption.always)
  147. Text("Day").tag(ActiveOption.day)
  148. Text("Night").tag(ActiveOption.night)
  149. }
  150. .pickerStyle(.segmented)
  151. }
  152. }
  153. }
  154. struct AlarmSnoozedUntilSection: View {
  155. @Binding var alarm: Alarm
  156. private var isSnoozed: Binding<Bool> {
  157. Binding(
  158. get: {
  159. if let until = alarm.snoozedUntil, until > Date() {
  160. return true
  161. }
  162. return false
  163. },
  164. set: { on in
  165. if on {
  166. // keep existing future snooze or set default ahead
  167. if let until = alarm.snoozedUntil, until > Date() {
  168. alarm.snoozedUntil = until
  169. } else {
  170. let secs = alarm.type.timeUnit.seconds
  171. alarm.snoozedUntil = Date().addingTimeInterval(Double(alarm.snoozeDuration) * secs)
  172. }
  173. } else {
  174. alarm.snoozedUntil = nil
  175. }
  176. }
  177. )
  178. }
  179. var body: some View {
  180. Section(header: Text("Snoozed Until")) {
  181. Toggle("Snoozed", isOn: isSnoozed)
  182. if isSnoozed.wrappedValue, let until = alarm.snoozedUntil {
  183. DatePicker("Until", selection: Binding(
  184. get: { until },
  185. set: { alarm.snoozedUntil = $0 }
  186. ), displayedComponents: [.date, .hourAndMinute])
  187. }
  188. }
  189. }
  190. }