AlarmEditorFields.swift 6.0 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. VStack(alignment: .leading, spacing: 8) {
  97. Text("Play")
  98. .font(.subheadline)
  99. .foregroundColor(.secondary)
  100. Picker("", selection: $alarm.playSoundOption) {
  101. ForEach(PlaySoundOption.allCases, id: \.self) { opt in
  102. Text(opt.rawValue.capitalized).tag(opt)
  103. }
  104. }
  105. .pickerStyle(.segmented)
  106. }
  107. VStack(alignment: .leading, spacing: 8) {
  108. Text("Repeat")
  109. .font(.subheadline)
  110. .foregroundColor(.secondary)
  111. Picker("", selection: $alarm.repeatSoundOption) {
  112. ForEach(RepeatSoundOption.allCases, id: \.self) { opt in
  113. Text(opt.rawValue.capitalized).tag(opt)
  114. }
  115. }
  116. .pickerStyle(.segmented)
  117. }
  118. }
  119. }
  120. }
  121. struct AlarmActiveSection: View {
  122. @Binding var alarm: Alarm
  123. var body: some View {
  124. Section(header: Text("Active During")) {
  125. Picker("Active", selection: $alarm.activeOption) {
  126. Text("Always").tag(ActiveOption.always)
  127. Text("Day").tag(ActiveOption.day)
  128. Text("Night").tag(ActiveOption.night)
  129. }
  130. .pickerStyle(.segmented)
  131. }
  132. }
  133. }
  134. struct AlarmSnoozedUntilSection: View {
  135. @Binding var alarm: Alarm
  136. private var isSnoozed: Binding<Bool> {
  137. Binding(
  138. get: {
  139. if let until = alarm.snoozedUntil, until > Date() {
  140. return true
  141. }
  142. return false
  143. },
  144. set: { on in
  145. if on {
  146. // keep existing future snooze or set default ahead
  147. if let until = alarm.snoozedUntil, until > Date() {
  148. alarm.snoozedUntil = until
  149. } else {
  150. let secs = alarm.type.timeUnit.seconds
  151. alarm.snoozedUntil = Date().addingTimeInterval(Double(alarm.snoozeDuration) * secs)
  152. }
  153. } else {
  154. alarm.snoozedUntil = nil
  155. }
  156. }
  157. )
  158. }
  159. var body: some View {
  160. Section(header: Text("Snoozed Until")) {
  161. Toggle("Snoozed", isOn: isSnoozed)
  162. if isSnoozed.wrappedValue, let until = alarm.snoozedUntil {
  163. DatePicker("Until", selection: Binding(
  164. get: { until },
  165. set: { alarm.snoozedUntil = $0 }
  166. ), displayedComponents: [.date, .hourAndMinute])
  167. }
  168. }
  169. }
  170. }