Adds confirmation sounds

main
Laurent 3 years ago
parent 79f817bf27
commit ea459d7a2a
  1. 14
      LeCountdown.xcodeproj/project.pbxproj
  2. 7
      LeCountdown/AppDelegate.swift
  3. 18
      LeCountdown/Conductor.swift
  4. 5
      LeCountdown/CountdownScheduler.swift
  5. 3
      LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift
  6. 2
      LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion
  7. 52
      LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.3.xcdatamodel/contents
  8. 19
      LeCountdown/Model/Model+Extensions.swift
  9. 39
      LeCountdown/Sound/Sound.swift
  10. 0
      LeCountdown/Sound_Assets/Shorts/ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav
  11. 0
      LeCountdown/Sound_Assets/Shorts/ESM_MVG_fx_ui_one_shot_cancel_clicky_reverb_digital_long.wav
  12. 0
      LeCountdown/Sound_Assets/Shorts/ESM_One_Shot_FX_Interface_Glitch_Spaceship_Console_18_Interface_Button_Alert_System_Cm.wav
  13. 0
      LeCountdown/Sound_Assets/Shorts/MRKRSTPHR_synth_one_shot_bleep_G.wav
  14. 0
      LeCountdown/Sound_Assets/Shorts/PVP_Stab_Oneshot_Bleep_Em.wav
  15. 11
      LeCountdown/TimerRouter.swift
  16. 3
      LeCountdown/Views/Countdown/CountdownFormView.swift
  17. 8
      LeCountdown/Views/Countdown/NewCountdownView.swift
  18. 100
      LeCountdown/Views/Reusable/SoundFormView.swift
  19. 81
      LeCountdown/Views/Reusable/SoundSelectionView.swift
  20. 26
      LeCountdown/Views/Reusable/TimerModel.swift
  21. 6
      LeCountdown/Views/Stopwatch/StopwatchFormView.swift
  22. 1
      LeCountdown/fr.lproj/Localizable.strings

@ -397,6 +397,7 @@
C4A16DA029D0A7FE00143D5E /* ESM_One_Shot_FX_Interface_Glitch_Spaceship_Console_18_Interface_Button_Alert_System_Cm.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ESM_One_Shot_FX_Interface_Glitch_Spaceship_Console_18_Interface_Button_Alert_System_Cm.wav; sourceTree = "<group>"; };
C4A16DA629D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav; sourceTree = "<group>"; };
C4A16DAC29D0AB1F00143D5E /* ESM_MVG_fx_ui_one_shot_cancel_clicky_reverb_digital_long.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = ESM_MVG_fx_ui_one_shot_cancel_clicky_reverb_digital_long.wav; sourceTree = "<group>"; };
C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.3.xcdatamodel; sourceTree = "<group>"; };
C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundSelectionView.swift; sourceTree = "<group>"; };
C4BA2AD72993F7D200CB4FBA /* LeCountdown.0.5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.5.xcdatamodel; sourceTree = "<group>"; };
C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerModel.swift; sourceTree = "<group>"; };
@ -754,16 +755,24 @@
C445FA962987D0CF0054D761 /* Sound_Assets */ = {
isa = PBXGroup;
children = (
C4A16DBC29D1A69200143D5E /* Shorts */,
C415D3EB29C376530037B215 /* Nature */,
C415D3CC29C0B13A0037B215 /* Relax */,
C4BA2ADC2995AB7600CB4FBA /* Stephan_Bodzin */,
);
path = Sound_Assets;
sourceTree = "<group>";
};
C4A16DBC29D1A69200143D5E /* Shorts */ = {
isa = PBXGroup;
children = (
C4E5D68129B93583008E7465 /* PVP_Stab_Oneshot_Bleep_Em.wav */,
C4A16DA029D0A7FE00143D5E /* ESM_One_Shot_FX_Interface_Glitch_Spaceship_Console_18_Interface_Button_Alert_System_Cm.wav */,
C4A16DA629D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav */,
C4A16DAC29D0AB1F00143D5E /* ESM_MVG_fx_ui_one_shot_cancel_clicky_reverb_digital_long.wav */,
C4A16D9A29D0A7D300143D5E /* MRKRSTPHR_synth_one_shot_bleep_G.wav */,
);
path = Sound_Assets;
path = Shorts;
sourceTree = "<group>";
};
C4BA2ADC2995AB7600CB4FBA /* Stephan_Bodzin */ = {
@ -1894,6 +1903,7 @@
C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */,
C4BA2B6B29A4C47100CB4FBA /* LeCountdown.0.6.2.xcdatamodel */,
C4BA2B46299FCD8B00CB4FBA /* LeCountdown.0.6.1.xcdatamodel */,
C4BA2B07299BDAE000CB4FBA /* LeCountdown.0.6.xcdatamodel */,
@ -1905,7 +1915,7 @@
C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */,
C4060DCB297AE73D003FAB80 /* LeCountdown.xcdatamodel */,
);
currentVersion = C4BA2B6B29A4C47100CB4FBA /* LeCountdown.0.6.2.xcdatamodel */;
currentVersion = C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */;
path = LeCountdown.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

@ -81,8 +81,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
print("didReceive notification")
let timerId = self._timerId(notificationId: response.notification.request.identifier)
Conductor.maestro.cancelCountdown(id: timerId)
// Conductor.maestro.cancelCountdown(id: timerId)
Conductor.maestro.cancelSoundPlayer(id: timerId)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
@ -93,14 +93,13 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
Conductor.maestro.notifyUser(countdownId: timerId)
}
fileprivate func _timerId(notificationId: String) -> String {
fileprivate func _timerId(notificationId: String) -> TimerID {
let components = notificationId.components(separatedBy: CountdownScheduler.notificationIdSeparator)
if components.count == 2 {
return components[0]
} else {
fatalError("bad notification format : \(notificationId)")
}
}
}

@ -174,7 +174,8 @@ class Conductor: ObservableObject {
self.removeLiveTimer(id: countdown.stringId)
let soundFile = try SoundFile(fullName: countdown.soundName)
let sound = countdown.someSound
let soundFile = try SoundFile(fullName: sound.fileName)
let soundPlayer = try DelaySoundPlayer(timerID: countdown.stringId, soundFile: soundFile)
self._delayedSoundPlayers[countdown.stringId] = soundPlayer
try soundPlayer.start(in: countdown.duration,
@ -184,7 +185,7 @@ class Conductor: ObservableObject {
self.currentCountdowns[countdown.stringId] = dateInterval
if Preferences.playConfirmationSound {
self._playConfirmationSound()
self._playConfirmationSound(timer: countdown)
}
handler(.success(date))
@ -234,7 +235,8 @@ class Conductor: ObservableObject {
if let countdown = context.object(stringId: countdownId) as? Countdown {
do {
let soundFile = try SoundFile(fullName: countdown.soundName)
let sound: Sound = countdown.someSound
let soundFile: SoundFile = try SoundFile(fullName: sound.fileName)
let soundPlayer = try DelaySoundPlayer(timerID: countdownId, soundFile: soundFile)
self._delayedSoundPlayers[countdown.stringId] = soundPlayer
try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount))
@ -288,8 +290,14 @@ class Conductor: ObservableObject {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}
fileprivate func _playConfirmationSound() {
self._playSound(Const.confirmationSound.rawValue)
fileprivate func _playConfirmationSound(timer: AbstractSoundTimer) {
let fileName: String
if let confirmationSound = timer.confirmationSounds.randomElement() {
fileName = confirmationSound.fileName
} else {
fileName = Const.confirmationSound.rawValue
}
self._playSound(fileName)
}
fileprivate func _playCancellationSound() {

@ -36,9 +36,7 @@ class CountdownScheduler {
content.body = body
let sound = countdown.soundName
self._createNotification(countdown: countdown, content: content, handler: handler)
print("Selected sound = \(sound)")
// content.sound = UNNotificationSound.criticalSoundNamed(UNNotificationSoundName(rawValue: sound), withAudioVolume: 1.0)
content.interruptionLevel = .critical
@ -92,13 +90,10 @@ class CountdownScheduler {
}
func cancelCurrentNotifications(countdownId: String) {
UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
let ids = requests.map { $0.identifier }.filter { $0.hasPrefix(countdownId) }
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
}
// Conductor.maestro.cancelCountdown(id: countdownId)
}
}

@ -2,7 +2,7 @@
// AbstractSoundTimer+CoreDataProperties.swift
// LeCountdown
//
// Created by Laurent Morvillier on 10/02/2023.
// Created by Laurent Morvillier on 27/03/2023.
//
//
@ -18,5 +18,6 @@ extension AbstractSoundTimer {
@NSManaged public var repeatCount: Int16
@NSManaged public var soundList: String?
@NSManaged public var confirmationSoundList: String?
}

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>LeCountdown.0.6.2.xcdatamodel</string>
<string>LeCountdown.0.6.3.xcdatamodel</string>
</dict>
</plist>

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22A400" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="AbstractSoundTimer" representedClassName="AbstractSoundTimer" isAbstract="YES" parentEntity="AbstractTimer" syncable="YES">
<attribute name="confirmationSoundList" optional="YES" attributeType="String"/>
<attribute name="repeatCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="soundList" optional="YES" attributeType="String"/>
</entity>
<entity name="AbstractTimer" representedClassName="AbstractTimer" isAbstract="YES" syncable="YES">
<attribute name="image" optional="YES" attributeType="String"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="timers" inverseEntity="Activity"/>
</entity>
<entity name="Activity" representedClassName="Activity" syncable="YES">
<attribute name="name" attributeType="String" defaultValueString=""/>
<relationship name="records" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Record" inverseName="activity" inverseEntity="Record"/>
<relationship name="timers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="AbstractTimer" inverseName="activity" inverseEntity="AbstractTimer"/>
</entity>
<entity name="Alarm" representedClassName="Alarm" parentEntity="AbstractSoundTimer" syncable="YES">
<attribute name="fireDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<entity name="Countdown" representedClassName="Countdown" parentEntity="AbstractSoundTimer" syncable="YES">
<attribute name="duration" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="group" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="IntervalGroup" inverseName="countdown" inverseEntity="IntervalGroup"/>
</entity>
<entity name="CustomSound" representedClassName="CustomSound" syncable="YES">
<attribute name="file" optional="YES" attributeType="String"/>
<attribute name="text" optional="YES" attributeType="String"/>
</entity>
<entity name="Interval" representedClassName="Interval" syncable="YES">
<attribute name="duration" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="soundList" optional="YES" attributeType="String"/>
<relationship name="group" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="IntervalGroup" inverseName="intervals" inverseEntity="IntervalGroup"/>
</entity>
<entity name="IntervalGroup" representedClassName="IntervalGroup" syncable="YES">
<attribute name="repeatCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="countdown" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Countdown" inverseName="group" inverseEntity="Countdown"/>
<relationship name="intervals" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Interval" inverseName="group" inverseEntity="Interval"/>
</entity>
<entity name="Record" representedClassName="Record" syncable="YES">
<attribute name="duration" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="end" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/>
<attribute name="month" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<attribute name="start" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/>
<attribute name="year" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="records" inverseEntity="Activity"/>
</entity>
<entity name="Stopwatch" representedClassName="Stopwatch" parentEntity="AbstractSoundTimer" syncable="YES">
<attribute name="end" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sound" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<attribute name="start" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
</model>

@ -17,11 +17,22 @@ extension AbstractSoundTimer {
}
return []
}
func setSounds(_ sounds: Set<Sound>) {
self.soundList = sounds.stringRepresentation
}
var confirmationSounds: Set<Sound> {
if let confirmationSoundList {
return Set(confirmationSoundList.enumItems())
}
return []
}
func setConfirmationSounds(_ sounds: Set<Sound>) {
self.confirmationSoundList = sounds.stringRepresentation
}
var someSound: Sound {
var sounds = self.sounds
@ -39,11 +50,7 @@ extension AbstractSoundTimer {
return Sound.default
}
var soundName: String {
return self.someSound.fileName
}
}
extension Stopwatch {

@ -23,37 +23,50 @@ class SoundCatalog {
}
func sounds(for playlist: Playlist) -> [Sound] {
return self._soundsByPlaylist[playlist] ?? []
switch playlist {
case .shorts:
return [.FF_SH_bowl_drone_tap_hold_E, .FF_SH_bowl_drone_tapping_C, .EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab, .ESM_Ambient_Game_Menu_Soft_Wood]
default:
return self._soundsByPlaylist[playlist] ?? []
}
}
}
enum Catalog {
case ring
case confirmation
var playlists: [Playlist] {
switch self {
case .ring: return [.nature, .stephanBodzin, .relax]
case .confirmation: return [.shorts]
}
}
}
enum Playlist: Int, CaseIterable, Identifiable, Localized {
var id: Int { return self.rawValue }
case custom
case nature
// case fun
case stephanBodzin
case relax
static var selectable: [Playlist] {
return Playlist.allCases.filter { $0 != .custom }
}
case shorts
var localizedString: String {
switch self {
case .nature:
return NSLocalizedString("Nature", comment: "")
// case .fun:
// return NSLocalizedString("Fun", comment: "")
case .stephanBodzin:
return "Stephan Bodzin"
case .custom:
return NSLocalizedString("Custom", comment: "")
case .relax:
return NSLocalizedString("Relax", comment: "")
case .shorts:
return NSLocalizedString("Confirmation", comment: "")
}
}
@ -75,12 +88,12 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
// Relax
case FF_SH_bowl_drone_tapping_C
case FF_SH_bowl_drone_tap_hold_E
case EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab
case EX_ATSM_Koshi_Chimes_Aria_Tuning_Texture_Longer_Dm
case EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am
case EX_ATSM_Bell_Binaural_Flam_Eb
case EX_ATSM_160_Metal_Tonal_Percussion_Sansula_Loop_Call_Am
case EX_ATSM_125_Metal_Percussion_Wing_Loop_Chimey_Dm
case EX_ATSM_140_Koshi_Chimes_Aria_Tuning_Loop_Wondering_Am
case EX_ATSM_20_Inch_Highwall_Bowl_Hit_Ring_Ab
// Nature
case rain_soft
case stream1
@ -91,6 +104,8 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
case deciduousForestMorning
case wetland
case riparianZone
// Shorts
case ESM_Ambient_Game_Menu_Soft_Wood
static var `default`: Sound { .sbSEM_Synths_Loop4_Nothing_Like_You }
@ -120,6 +135,7 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
case .deciduousForestMorning: return "Forest morning 2"
case .wetland: return "Wetland"
case .riparianZone: return "Riparian Zone"
case .ESM_Ambient_Game_Menu_Soft_Wood: return "Wood percussion"
}
}
@ -149,6 +165,7 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
case .deciduousForestMorning: return "QP01 0050 Deciduous forest morning songbirds robin.wav"
case .wetland: return "QP01 0096 Wetland lake early morning.wav"
case .riparianZone: return "QP01 0096 Wetland lake early morning.wav"
case .ESM_Ambient_Game_Menu_Soft_Wood: return "ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav"
}
}
@ -160,6 +177,8 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
return .relax
case .rain_soft, .stream1, .stream2, .surf1, .crickets, .tropicalForestMorning, .deciduousForestMorning, .wetland, .riparianZone:
return .nature
case .ESM_Ambient_Game_Menu_Soft_Wood:
return .shorts
}
}

@ -45,17 +45,6 @@ class TimerRouter {
}
static func stopTimer(timer: AbstractTimer) {
switch timer {
case let countdown as Countdown:
Conductor.maestro.cancelCountdown(id: countdown.stringId)
case let stopwatch as Stopwatch:
self._stopStopwatch(stopwatch)
default:
print("missing launcher for \(self)")
}
}
fileprivate static func _launchCountdown(_ countdown: Countdown, handler: @escaping (Result<Void, Error>) -> Void) {
UNUserNotificationCenter.current().getNotificationSettings { settings in

@ -19,6 +19,7 @@ struct CountdownFormView : View {
@FocusState private var focusedField: CountdownField?
@EnvironmentObject var model: TimerModel
@EnvironmentObject var confirmationModel: TimerModel
var secondsBinding: Binding<String>
var minutesBinding: Binding<String>
@ -55,9 +56,9 @@ struct CountdownFormView : View {
}
SoundFormView(
model: self.model,
imageBinding: imageBinding,
repeatCountBinding: repeatCountBinding)
.environmentObject(self.model)
}.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {

@ -193,7 +193,7 @@ struct CountdownEditView : View {
}
self.model.group = preset.intervalGroup
self.model.sounds = preset.sound
self.model.soundModel.sounds = preset.sound
}
@ -214,7 +214,7 @@ struct CountdownEditView : View {
self.nameString = name
}
self.model.sounds = countdown.sounds
self.model.soundModel.sounds = countdown.sounds
// if let sound = Sound(rawValue: Int(countdown.sound)) {
// self.sound = sound
@ -262,9 +262,9 @@ struct CountdownEditView : View {
}
cd.image = self.image.rawValue
cd.setSounds(self.model.sounds)
cd.setSounds(self.model.soundModel.sounds)
cd.setConfirmationSounds(self.model.confirmationSoundModel.sounds)
// cd.setPlaylists(self.playlists)
cd.repeatCount = self.soundRepeatCount
if !self.nameString.isEmpty {

@ -9,9 +9,9 @@ import SwiftUI
struct SoundFormView : View {
var model: TimerModel
var imageBinding: Binding<CoolPic>
@EnvironmentObject var model: TimerModel
var repeatCountBinding: Binding<Int16>? = nil
var optionalSound: Binding<Bool>? = nil
@ -25,26 +25,13 @@ struct SoundFormView : View {
Section(header: Text("Properties")) {
if self.optionalSound != nil {
Toggle("Play sound on end", isOn: optionalSound!)
if self.optionalSound?.wrappedValue == true {
NavigationLink {
PlaylistsView().environmentObject(self.model)
// SoundSelectionView(playlistBinding: playlistBinding)
PlaylistsView(model: self.model.soundModel, catalog: .ring)
} label: {
Text("Sound")
}
// Picker(selection: soundBinding) {
// ForEach(Sound.allCases) { sound in
// Text(sound.localizedString).tag(sound)
// }
// } label: {
// Text("Sound")
// }
}
} else {
@ -57,17 +44,21 @@ struct SoundFormView : View {
// }
}
NavigationLink {
NavigationStack {
PlaylistsView().environmentObject(self.model)
}
} label: {
HStack {
Text("Sound")
Spacer()
Text(self.model.soundSelection)
}
}
SoundLinkView(soundModel: self.model.soundModel,
catalog: .ring,
title: "Sound")
// NavigationLink {
// NavigationStack {
// PlaylistsView(model: self.model.soundModel, catalog: .ring)
// }
// } label: {
// HStack {
// Text("Sound")
// Spacer()
// Text(self.model.soundModel.soundSelection)
// }
// }
if self.repeatCountBinding != nil {
Picker("Repeat Count", selection: self.repeatCountBinding!) {
@ -79,28 +70,23 @@ struct SoundFormView : View {
}
}
}
// Section(header: Text("Background")) {
//
// Button {
// self.imageSelectionSheetShown = true
SoundLinkView(soundModel: self.model.confirmationSoundModel,
catalog: .confirmation,
title: "Confirmation Sound")
// NavigationLink {
// NavigationStack {
// PlaylistsView(model: self.model.confirmationSoundModel, catalog: .confirmation)
// }
// } label: {
// Group {
// if let image = self.imageBinding.wrappedValue {
// Image(image.rawValue).resizable()
// } else {
// Image(imageBinding.wrappedValue.rawValue).resizable()
// }
// HStack {
// Text("Confirmation Sound")
// Spacer()
// Text(self.confirmationModel.soundSelection)
// }
// .font(Font.system(size: 90.0))
// .aspectRatio(1, contentMode: .fit)
// .frame(width: 100.0, height: 100.0)
// .cornerRadius(20.0)
//
// }
//
// }
}
}.sheet(isPresented: self.$imageSelectionSheetShown) {
ImageSelectionView(showBinding: self.$imageSelectionSheetShown, imageBinding: self.imageBinding)
}
@ -108,14 +94,32 @@ struct SoundFormView : View {
}
struct SoundLinkView: View {
@StateObject var soundModel: SoundModel
var catalog: Catalog
var title: String
var body: some View {
NavigationLink {
NavigationStack {
PlaylistsView(model: self.soundModel,
catalog: self.catalog)
}
} label: {
LabeledContent(self.title, value: self.soundModel.soundSelection)
}
}
}
struct SoundImageFormView_Previews: PreviewProvider {
static var previews: some View {
Form {
SoundFormView(
SoundFormView(model: TimerModel(),
imageBinding: .constant(.pic1),
repeatCountBinding: .constant(2))
.environmentObject(TimerModel())
}
}
}

@ -9,14 +9,15 @@ import SwiftUI
struct PlaylistsView: View {
@EnvironmentObject var model: TimerModel
@StateObject var model: SoundModel
var catalog: Catalog
var body: some View {
Form {
ForEach(Playlist.selectable) { playlist in
PlaylistSectionView(playlist: playlist)
.environmentObject(self.model)
ForEach(catalog.playlists) { playlist in
PlaylistSectionView(model: self.model, playlist: playlist)
}
}
.navigationTitle("Sounds")
@ -26,7 +27,7 @@ struct PlaylistsView: View {
struct PlaylistSectionView: View {
@EnvironmentObject var model: TimerModel
@StateObject var model: SoundModel
var playlist: Playlist
@ -101,15 +102,6 @@ struct RightAlignToggleRow<T : Localized>: View {
}
}.onChange(of: self.selected, perform: handleSelection)
// if let keyPath {
//
//
// Toggle(item[keyPath: keyPath], isOn: $selected)
// .onChange(of: self.selected, perform: handleSelection)
// } else {
// Toggle(item.localizedString, isOn: $selected)
// .onChange(of: self.selected, perform: handleSelection)
// }
}
}
@ -129,66 +121,10 @@ struct ImageToggleRow<T : Localized>: View {
}
//struct PlaylistRow: View {
// var playlist: Playlist
// @State var selected: Bool
// var handleSelection: (Bool) -> ()
//
// var body: some View {
// Toggle(playlist.localizedString, isOn: $selected)
// .onChange(of: selected, perform: handleSelection)
// }
//}
//
//struct SoundRow: View {
// var sound: Sound
// @State var selected: Bool
// var handleSelection: (Bool) -> ()
//
// var body: some View {
// Toggle(sound.localizedString, isOn: $selected)
// .onChange(of: selected, perform: handleSelection)
// }
//}
struct SoundSelectionView: View {
var body: some View {
Form {
// PlaylistsView()
// ForEach($playlistBinding, id: \.id) { $ps in
//
//
// Section {
// ForEach(ps.playlist.sounds) { sound in
//
// SoundRow(sound: sound, selected: ps.sounds.contains(sound)) { selected in
// if selected {
// ps.sounds.append(sound)
// } else {
// ps.sounds.removeAll(where: { $0 == sound })
// }
// }
// }
// } header: {
// Toggle(ps.playlist.localizedString, isOn: $ps.selected)
// }
// }.navigationTitle("Sounds")
}
}
}
struct PlaylistsView_Previews: PreviewProvider {
static var previews: some View {
PlaylistsView()
.environmentObject(TimerModel())
PlaylistsView(model: SoundModel(), catalog: .ring)
}
}
@ -196,8 +132,7 @@ struct PlaylistSectionView_Previews: PreviewProvider {
static var previews: some View {
Form {
PlaylistSectionView(playlist: .stephanBodzin)
.environmentObject(TimerModel())
PlaylistSectionView(model: SoundModel(), playlist: .stephanBodzin)
}
}
}

@ -13,7 +13,17 @@ protocol SoundHolder {
func selectPlaylist(_ playlist: Playlist, selected: Bool)
}
class TimerModel : ObservableObject, SoundHolder {
class TimerModel: ObservableObject {
@Published var soundModel: SoundModel = SoundModel()
@Published var confirmationSoundModel: SoundModel = SoundModel()
@Published var group: CountdownIntervalGroup =
CountdownIntervalGroup(repeatCount: 0, intervals: [])
}
class SoundModel: ObservableObject, SoundHolder {
@Published var playlists: Set<Playlist> = []
@Published var sounds: Set<Sound> = [] {
@ -21,9 +31,7 @@ class TimerModel : ObservableObject, SoundHolder {
self._selectPlaylists()
}
}
@Published var group: CountdownIntervalGroup =
CountdownIntervalGroup(repeatCount: 0, intervals: [])
var soundSelection: String {
if !sounds.isEmpty {
if sounds.count == 1 {
@ -56,13 +64,11 @@ class TimerModel : ObservableObject, SoundHolder {
// MARK: - SoundHolder
func selectSound(_ sound: Sound, selected: Bool) {
if selected {
self.sounds.insert(sound)
} else {
self.sounds.remove(sound)
}
self._togglePlaylist(sound.playlist)
}
@ -99,12 +105,4 @@ class TimerModel : ObservableObject, SoundHolder {
}
// func isSelected(sound: Sound) -> Bool {
// self.sounds.contains(sound)
// }
//
// func isSelected(playlist: Playlist) -> Bool {
// self.playlists.contains(playlist)
// }
}

@ -34,9 +34,9 @@ struct StopwatchFormView: View {
}
}
SoundFormView(imageBinding: imageBinding,
optionalSound: playSoundBinding)
.environmentObject(self.model)
SoundFormView(model: self.model,
imageBinding: imageBinding,
optionalSound: playSoundBinding)
}.toolbar {
ToolbarItemGroup(placement: .keyboard) {

@ -254,3 +254,4 @@
"Play confirmation sound" = "Jouer son de confirmation";
"Play cancellation sound" = "Jouer son d'annulation";
"Contact us" = "Contactez-nous";
"Confirmation" = "Confirmation";

Loading…
Cancel
Save