AlarmAudioSection.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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: "Play",
  28. selection: $alarm.playSoundOption,
  29. allowed: PlaySoundOption.allowed(for: alarm.activeOption)
  30. )
  31. if !hideRepeat {
  32. AlarmEnumMenuPicker(
  33. title: "Repeat",
  34. selection: $alarm.repeatSoundOption,
  35. allowed: RepeatSoundOption.allowed(for: alarm.activeOption)
  36. )
  37. }
  38. }.onChange(of: alarm.activeOption) { newActive in
  39. let playAllowed = PlaySoundOption.allowed(for: newActive)
  40. if !playAllowed.contains(alarm.playSoundOption) {
  41. alarm.playSoundOption = playAllowed.last!
  42. }
  43. let repeatAllowed = RepeatSoundOption.allowed(for: newActive)
  44. if !repeatAllowed.contains(alarm.repeatSoundOption) {
  45. alarm.repeatSoundOption = repeatAllowed.last!
  46. }
  47. }
  48. }
  49. }
  50. struct AlarmEnumMenuPicker<E: CaseIterable & Hashable & DayNightDisplayable>: View {
  51. let title: String
  52. @Binding var selection: E
  53. var allowed: [E]
  54. var body: some View {
  55. HStack {
  56. Text(title)
  57. Spacer()
  58. Picker("", selection: $selection) {
  59. ForEach(allowed, id: \.self) { opt in
  60. Text(opt.displayName).tag(opt)
  61. }
  62. }
  63. // if the current selection became invalid, snap to the first allowed
  64. .onAppear { validate() }
  65. .onChange(of: allowed) { _ in validate() }
  66. }
  67. }
  68. private func validate() {
  69. if !allowed.contains(selection), let first = allowed.first {
  70. selection = first
  71. }
  72. }
  73. }
  74. extension AlarmEnumMenuPicker where E: CaseIterable {
  75. init(title: String, selection: Binding<E>) {
  76. self.title = title
  77. _selection = selection
  78. allowed = Array(E.allCases)
  79. }
  80. }
  81. private struct TonePickerSheet: View {
  82. @Binding var selected: SoundFile
  83. @Environment(\.dismiss) private var dismiss
  84. var body: some View {
  85. NavigationView {
  86. ScrollViewReader { proxy in
  87. List {
  88. ForEach(SoundFile.allCases) { tone in
  89. Button {
  90. selected = tone
  91. AlarmSound.setSoundFile(str: tone.rawValue)
  92. AlarmSound.stop()
  93. AlarmSound.playTest()
  94. } label: {
  95. HStack {
  96. Text(tone.displayName)
  97. if tone == selected {
  98. Spacer()
  99. Image(systemName: "checkmark")
  100. .foregroundColor(.accentColor)
  101. }
  102. }
  103. }
  104. .id(tone)
  105. }
  106. }
  107. .navigationTitle("Choose Tone")
  108. .toolbar {
  109. ToolbarItem(placement: .confirmationAction) {
  110. Button("Done") {
  111. AlarmSound.stop()
  112. dismiss()
  113. }
  114. }
  115. }
  116. .onAppear {
  117. proxy.scrollTo(selected, anchor: .center)
  118. }
  119. }
  120. }
  121. }
  122. }