AlarmEditorFields.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 AlarmSnoozeSection: 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("How long to snooze after firing \(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. import SwiftUI
  44. struct AlarmAudioSection: View {
  45. @Binding var alarm: Alarm
  46. @State private var showingTonePicker = false
  47. var body: some View {
  48. Section(header: Text("Alert Sound")) {
  49. // ——— Tone Row ———
  50. Button {
  51. showingTonePicker = true
  52. } label: {
  53. HStack {
  54. Text("Tone")
  55. Spacer()
  56. Text(alarm.soundFile.displayName)
  57. .foregroundColor(.secondary)
  58. Image(systemName: "chevron.right")
  59. .foregroundColor(.secondary)
  60. }
  61. }
  62. .sheet(isPresented: $showingTonePicker) {
  63. NavigationView {
  64. List {
  65. ForEach(SoundFile.allCases) { tone in
  66. Button {
  67. alarm.soundFile = tone
  68. // play test tone
  69. AlarmSound.setSoundFile(str: tone.rawValue)
  70. AlarmSound.stop()
  71. AlarmSound.playTest()
  72. } label: {
  73. HStack {
  74. Text(tone.displayName)
  75. if alarm.soundFile == tone {
  76. Spacer()
  77. Image(systemName: "checkmark")
  78. .foregroundColor(.accentColor)
  79. }
  80. }
  81. }
  82. }
  83. }
  84. .navigationTitle("Choose Tone")
  85. .toolbar {
  86. ToolbarItem(placement: .confirmationAction) {
  87. Button("Done") {
  88. AlarmSound.stop()
  89. showingTonePicker = false
  90. }
  91. }
  92. }
  93. }
  94. }
  95. // ——— Play / Repeat Toggles ———
  96. HStack() {
  97. Text("Play")
  98. .font(.subheadline)
  99. .foregroundColor(.secondary)
  100. Spacer()
  101. Picker("", selection: $alarm.playSoundOption) {
  102. ForEach(PlaySoundOption.allCases, id: \.self) { opt in
  103. Text(opt.rawValue.capitalized).tag(opt)
  104. }
  105. }
  106. .pickerStyle(.menu)
  107. }
  108. VStack(alignment: .leading, spacing: 8) {
  109. Text("Repeat")
  110. .font(.subheadline)
  111. .foregroundColor(.secondary)
  112. Picker("", selection: $alarm.repeatSoundOption) {
  113. ForEach(RepeatSoundOption.allCases, id: \.self) { opt in
  114. Text(opt.rawValue.capitalized).tag(opt)
  115. }
  116. }
  117. .pickerStyle(.menu)
  118. }
  119. }
  120. }
  121. }
  122. struct AlarmActiveSection: View {
  123. @Binding var alarm: Alarm
  124. var body: some View {
  125. Section(header: Text("Active During")) {
  126. Picker("Active", selection: $alarm.activeOption) {
  127. Text("Always").tag(ActiveOption.always)
  128. Text("Day").tag(ActiveOption.day)
  129. Text("Night").tag(ActiveOption.night)
  130. }
  131. .pickerStyle(.segmented)
  132. }
  133. }
  134. }
  135. struct AlarmSnoozedUntilSection: View {
  136. @Binding var alarm: Alarm
  137. private var isSnoozed: Binding<Bool> {
  138. Binding(
  139. get: {
  140. if let until = alarm.snoozedUntil, until > Date() {
  141. return true
  142. }
  143. return false
  144. },
  145. set: { on in
  146. if on {
  147. // keep existing future snooze or set default ahead
  148. if let until = alarm.snoozedUntil, until > Date() {
  149. alarm.snoozedUntil = until
  150. } else {
  151. let secs = alarm.type.timeUnit.seconds
  152. alarm.snoozedUntil = Date().addingTimeInterval(Double(alarm.snoozeDuration) * secs)
  153. }
  154. } else {
  155. alarm.snoozedUntil = nil
  156. }
  157. }
  158. )
  159. }
  160. var body: some View {
  161. Section(header: Text("Snoozed Until")) {
  162. Toggle("Snoozed", isOn: isSnoozed)
  163. if isSnoozed.wrappedValue, let until = alarm.snoozedUntil {
  164. DatePicker("Until", selection: Binding(
  165. get: { until },
  166. set: { alarm.snoozedUntil = $0 }
  167. ), displayedComponents: [.date, .hourAndMinute])
  168. }
  169. }
  170. }
  171. }