AlarmAudioSection.swift 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // LoopFollow
  2. // AlarmAudioSection.swift
  3. import SwiftUI
  4. struct AlarmAudioSection: View {
  5. @Binding var alarm: Alarm
  6. var hideRepeat: Bool = false
  7. @State private var showingTonePicker = false
  8. var body: some View {
  9. Section(header: Text("Alert Sound")) {
  10. Button {
  11. showingTonePicker = true
  12. } label: {
  13. HStack {
  14. Text("Tone")
  15. Spacer()
  16. Text(alarm.soundFile.displayName)
  17. .foregroundColor(.secondary)
  18. Image(systemName: "chevron.right")
  19. .foregroundColor(.secondary)
  20. }
  21. }
  22. .buttonStyle(.plain)
  23. .sheet(isPresented: $showingTonePicker) {
  24. TonePickerSheet(selected: $alarm.soundFile)
  25. }
  26. AlarmEnumMenuPicker(
  27. title: String(localized: "Play"),
  28. selection: $alarm.playSoundOption,
  29. allowed: PlaySoundOption.allowed(for: alarm.activeOption)
  30. )
  31. if !hideRepeat {
  32. AlarmEnumMenuPicker(
  33. title: String(localized: "Repeat"),
  34. selection: $alarm.repeatSoundOption,
  35. allowed: RepeatSoundOption.allowed(for: alarm.activeOption)
  36. )
  37. }
  38. Stepper(
  39. value: $alarm.soundDelay,
  40. in: 0 ... 60,
  41. step: 5
  42. ) {
  43. HStack {
  44. Text("Delay Between Sounds")
  45. Spacer()
  46. if alarm.soundDelay == 0 {
  47. Text("Off")
  48. .foregroundColor(.secondary)
  49. } else {
  50. Text("\(alarm.soundDelay) sec")
  51. .foregroundColor(.secondary)
  52. }
  53. }
  54. }
  55. }.onChange(of: alarm.activeOption) { newActive in
  56. let playAllowed = PlaySoundOption.allowed(for: newActive)
  57. if !playAllowed.contains(alarm.playSoundOption) {
  58. alarm.playSoundOption = playAllowed.last!
  59. }
  60. let repeatAllowed = RepeatSoundOption.allowed(for: newActive)
  61. if !repeatAllowed.contains(alarm.repeatSoundOption) {
  62. alarm.repeatSoundOption = repeatAllowed.last!
  63. }
  64. }
  65. }
  66. }
  67. struct AlarmEnumMenuPicker<E: CaseIterable & Hashable & DayNightDisplayable>: View {
  68. let title: String
  69. @Binding var selection: E
  70. var allowed: [E]
  71. var body: some View {
  72. HStack {
  73. Text(title)
  74. Spacer()
  75. Picker("", selection: $selection) {
  76. ForEach(allowed, id: \.self) { opt in
  77. Text(opt.displayName).tag(opt)
  78. }
  79. }
  80. // if the current selection became invalid, snap to the first allowed
  81. .onAppear { validate() }
  82. .onChange(of: allowed) { _ in validate() }
  83. }
  84. }
  85. private func validate() {
  86. if !allowed.contains(selection), let first = allowed.first {
  87. selection = first
  88. }
  89. }
  90. }
  91. extension AlarmEnumMenuPicker where E: CaseIterable {
  92. init(title: String, selection: Binding<E>) {
  93. self.title = title
  94. _selection = selection
  95. allowed = Array(E.allCases)
  96. }
  97. }
  98. private struct TonePickerSheet: View {
  99. @Binding var selected: SoundFile
  100. @Environment(\.dismiss) private var dismiss
  101. var body: some View {
  102. NavigationView {
  103. ScrollViewReader { proxy in
  104. List {
  105. ForEach(SoundFile.allCases) { tone in
  106. Button {
  107. selected = tone
  108. AlarmSound.setSoundFile(str: tone.rawValue)
  109. AlarmSound.stop()
  110. AlarmSound.playTest()
  111. } label: {
  112. HStack {
  113. Text(tone.displayName)
  114. if tone == selected {
  115. Spacer()
  116. Image(systemName: "checkmark")
  117. .foregroundColor(.accentColor)
  118. }
  119. }
  120. }
  121. .id(tone)
  122. }
  123. }
  124. .navigationTitle("Choose Tone")
  125. .toolbar {
  126. ToolbarItem(placement: .confirmationAction) {
  127. Button("Done") {
  128. AlarmSound.stop()
  129. dismiss()
  130. }
  131. }
  132. }
  133. .onAppear {
  134. proxy.scrollTo(selected, anchor: .center)
  135. }
  136. }
  137. }
  138. }
  139. }