AlarmAudioSection.swift 4.3 KB

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