diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 4d873f5..b07c8fb 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -132,6 +132,11 @@ C4A16DA929D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4A16DA629D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav */; }; C4A16DAA29D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4A16DA629D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav */; }; C4A16DAB29D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4A16DA629D0AAA800143D5E /* ESM_Ambient_Game_Menu_Soft_Wood_Confirm_1_Notification_Button_Settings_UI.wav */; }; + C4A16DC529D311C800143D5E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16DC429D311C800143D5E /* Extensions.swift */; }; + C4A16DC629D311C800143D5E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16DC429D311C800143D5E /* Extensions.swift */; }; + C4A16DC729D311C800143D5E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16DC429D311C800143D5E /* Extensions.swift */; }; + C4A16DC829D311C800143D5E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16DC429D311C800143D5E /* Extensions.swift */; }; + C4A16DC929D311C800143D5E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16DC429D311C800143D5E /* Extensions.swift */; }; C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */; }; C4BA2ADB299549BC00CB4FBA /* TimerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */; }; C4BA2ADE2995ABA800CB4FBA /* MatriarchFxs_Loop2_Collider.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4BA2ADD2995ABA800CB4FBA /* MatriarchFxs_Loop2_Collider.wav */; }; @@ -391,6 +396,8 @@ 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 = ""; }; 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 = ""; }; C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.3.xcdatamodel; sourceTree = ""; }; + C4A16DC429D311C800143D5E /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.4.xcdatamodel; sourceTree = ""; }; C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundSelectionView.swift; sourceTree = ""; }; C4BA2AD72993F7D200CB4FBA /* LeCountdown.0.5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.5.xcdatamodel; sourceTree = ""; }; C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerModel.swift; sourceTree = ""; }; @@ -700,6 +707,7 @@ C4BA2AFC299A3A3700CB4FBA /* AppleMusicPlayer.swift */, C4742B5629840F6400D5D950 /* CoolPic.swift */, C4BA2B6929A4BE1800CB4FBA /* Date+Extensions.swift */, + C4A16DC429D311C800143D5E /* Extensions.swift */, C4BA2B5A299FFAB000CB4FBA /* Logger.swift */, C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */, C438C81029829EAF00BF3EF9 /* PropertyWrappers.swift */, @@ -1244,6 +1252,7 @@ C473C31829A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */, C4742B5729840F6400D5D950 /* CoolPic.swift in Sources */, + C4A16DC529D311C800143D5E /* Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1252,6 +1261,7 @@ buildActionMask = 2147483647; files = ( C4A16D9629C4B06400143D5E /* StatePlayer.swift in Sources */, + C4A16DC629D311C800143D5E /* Extensions.swift in Sources */, C4060DD7297AE73D003FAB80 /* LeCountdownTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1263,6 +1273,7 @@ C4060DE1297AE73D003FAB80 /* LeCountdownUITests.swift in Sources */, C4A16D9729C4B06400143D5E /* StatePlayer.swift in Sources */, C4060DE3297AE73D003FAB80 /* LeCountdownUITestsLaunchTests.swift in Sources */, + C4A16DC729D311C800143D5E /* Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1304,6 +1315,7 @@ C4F8B192298AC288005C86A5 /* Activity+CoreDataProperties.swift in Sources */, C4BA2B18299BE6A000CB4FBA /* Interval+CoreDataClass.swift in Sources */, C4F8B18E298AC288005C86A5 /* AbstractTimer+CoreDataProperties.swift in Sources */, + C4A16DC829D311C800143D5E /* Extensions.swift in Sources */, C4F8B1AE298AC451005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, C4BA2B32299F75DE00CB4FBA /* DefaultView.swift in Sources */, C4F8B18D298AC288005C86A5 /* Stopwatch+CoreDataClass.swift in Sources */, @@ -1358,6 +1370,7 @@ C4BA2B4E299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, C4E5D67829B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */, C4BA2B23299BE82E00CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */, + C4A16DC929D311C800143D5E /* Extensions.swift in Sources */, C473C2F529A8DAF30056B38A /* PropertyWrappers.swift in Sources */, C473C2F029A8CFFC0056B38A /* TimerRouter.swift in Sources */, C438C800298130E900BF3EF9 /* IntentDataProvider.swift in Sources */, @@ -1887,6 +1900,7 @@ C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */, C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */, C4BA2B6B29A4C47100CB4FBA /* LeCountdown.0.6.2.xcdatamodel */, C4BA2B46299FCD8B00CB4FBA /* LeCountdown.0.6.1.xcdatamodel */, @@ -1899,7 +1913,7 @@ C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */, C4060DCB297AE73D003FAB80 /* LeCountdown.xcdatamodel */, ); - currentVersion = C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */; + currentVersion = C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */; path = LeCountdown.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift b/LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift index 5a64584..c4759da 100644 --- a/LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift +++ b/LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift @@ -2,7 +2,7 @@ // AbstractSoundTimer+CoreDataProperties.swift // LeCountdown // -// Created by Laurent Morvillier on 27/03/2023. +// Created by Laurent Morvillier on 28/03/2023. // // @@ -16,8 +16,8 @@ extension AbstractSoundTimer { return NSFetchRequest(entityName: "AbstractSoundTimer") } - @NSManaged public var repeatCount: Int16 - @NSManaged public var soundList: String? @NSManaged public var confirmationSoundList: String? + @NSManaged public var repeatCount: Int16 + @NSManaged public var playableIds: String? } diff --git a/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion b/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion index d18b0c0..f257262 100644 --- a/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - LeCountdown.0.6.3.xcdatamodel + LeCountdown.0.6.4.xcdatamodel diff --git a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.4.xcdatamodel/contents b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.4.xcdatamodel/contents new file mode 100644 index 0000000..07a48ef --- /dev/null +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.4.xcdatamodel/contents @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index 03a5295..99d31c5 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -11,17 +11,31 @@ import CoreData extension AbstractSoundTimer { - var sounds: Set { + var playables: [any Playable] { if let soundList { - return Set(soundList.enumItems()) + var playables: [any Playable] = [] + var playableIds = soundList.components(separatedBy: idSeparator) + for id in playableIds { + if let intId = numberFormatter.number(from: id)?.intValue, + let sound = Sound(rawValue: intId) { + playables.append(sound) + } else if let playlist = Playlist(rawValue: id) { + playables.append(playlist) + } + } + return playables } return [] } - func setSounds(_ sounds: Set) { - self.soundList = sounds.stringRepresentation + var allSounds: Set { + return self.playables.reduce(Set()) { $0.union($1.soundList) } } +// func setSounds(_ sounds: Set) { +// self.soundList = sounds.stringRepresentation +// } + var confirmationSounds: Set { if let confirmationSoundList { return Set(confirmationSoundList.enumItems()) @@ -34,7 +48,7 @@ extension AbstractSoundTimer { } var someSound: Sound { - var sounds = self.sounds + var sounds = self.allSounds // remove last played sound if the playlist has at least 3 sounds if sounds.count > 2, @@ -130,27 +144,3 @@ extension CustomSound : Localized { } - -// MARK: - Storage convenience - -fileprivate let separator = "|" -fileprivate let formatter: NumberFormatter = NumberFormatter() - -extension String { - - func enumItems>() -> [T] { - let ids: [String] = self.components(separatedBy: separator) - let intIds: [Int] = ids.compactMap { formatter.number(from: $0)?.intValue } - return intIds.compactMap { T(rawValue: $0) } - } - -} - -extension Sequence where Element : RawRepresentable { - - var stringRepresentation: String { - let ids = self.compactMap { formatter.string(from: NSNumber(value: $0.rawValue)) } - return ids.joined(separator: separator) - } - -} diff --git a/LeCountdown/Sound/Sound.swift b/LeCountdown/Sound/Sound.swift index 5841a69..e3fe4aa 100644 --- a/LeCountdown/Sound/Sound.swift +++ b/LeCountdown/Sound/Sound.swift @@ -8,6 +8,23 @@ import Foundation import AVFoundation +protocol Playable: StringRepresentable, Equatable, Hashable { + var soundList: Set { get } +} + +extension Playlist : Playable { + var stringValue: String { self.rawValue } + var soundList: Set { + return Set(SoundCatalog.main.sounds(for: self)) + } +} +extension Sound: Playable { + var stringValue: String { self.rawValue.formatted() } + var soundList: Set { + return [self] + } +} + protocol Localized { var localizedString: String { get } } @@ -45,9 +62,9 @@ enum Catalog { } } -enum Playlist: Int, CaseIterable, Identifiable, Localized { +enum Playlist: String, CaseIterable, Identifiable, Localized { - var id: Int { return self.rawValue } + var id: String { return self.rawValue } case custom case nature diff --git a/LeCountdown/Utils/Extensions.swift b/LeCountdown/Utils/Extensions.swift new file mode 100644 index 0000000..50359a2 --- /dev/null +++ b/LeCountdown/Utils/Extensions.swift @@ -0,0 +1,45 @@ +// +// Extensions.swift +// LeCountdown +// +// Created by Laurent Morvillier on 28/03/2023. +// + +import Foundation + +// MARK: - Storage convenience + +let idSeparator = "|" +let numberFormatter: NumberFormatter = NumberFormatter() + +extension String { + + func enumItems>() -> [T] { + let ids: [String] = self.components(separatedBy: idSeparator) + let intIds: [Int] = ids.compactMap { numberFormatter.number(from: $0)?.intValue } + return intIds.compactMap { T(rawValue: $0) } + } + +} + +//extension Sequence where Element : RawRepresentable { +// var stringRepresentation: String { +// let ids = self.compactMap { formatter.string(from: NSNumber(value: $0.rawValue)) } +// return ids.joined(separator: separator) +// } +//} + +extension Sequence where Element : StringRepresentable { + var stringRepresentation: String { + let ids = self.compactMap { $0.stringValue } + return ids.joined(separator: idSeparator) + } +} + +protocol StringRepresentable { + var stringValue: String { get } +} + +extension String: StringRepresentable { + var stringValue: String { self } +} diff --git a/LeCountdown/Views/Countdown/NewCountdownView.swift b/LeCountdown/Views/Countdown/NewCountdownView.swift index 4958089..2f43ab4 100644 --- a/LeCountdown/Views/Countdown/NewCountdownView.swift +++ b/LeCountdown/Views/Countdown/NewCountdownView.swift @@ -214,14 +214,12 @@ struct CountdownEditView : View { self.nameString = name } - self.model.soundModel.sounds = countdown.sounds + self.model.soundModel.setPlayables(countdown.playables) - // if let sound = Sound(rawValue: Int(countdown.sound)) { - // self.sound = sound - // } self.soundRepeatCount = countdown.repeatCount - if let image = countdown.image, let coolpic = CoolPic(rawValue: image) { + if let image = countdown.image, + let coolpic = CoolPic(rawValue: image) { self.image = coolpic } } @@ -262,7 +260,8 @@ struct CountdownEditView : View { } cd.image = self.image.rawValue - cd.setSounds(self.model.soundModel.sounds) + cd.playableIds = self.model.soundModel.playableIds + cd.setConfirmationSounds(self.model.confirmationSoundModel.sounds) cd.repeatCount = self.soundRepeatCount diff --git a/LeCountdown/Views/Reusable/TimerModel.swift b/LeCountdown/Views/Reusable/TimerModel.swift index c3b4463..de974e4 100644 --- a/LeCountdown/Views/Reusable/TimerModel.swift +++ b/LeCountdown/Views/Reusable/TimerModel.swift @@ -32,6 +32,19 @@ class SoundModel: ObservableObject, SoundHolder { } } + func setPlayables(_ playables: [any Playable]) { + for playable in playables { + switch playable { + case let playlist as Playlist: + self.playlists.insert(playlist) + case let sound as Sound: + self.sounds.insert(sound) + default: + Logger.w("Unmanaged playable: \(playable)") + } + } + } + var soundSelection: String { if !sounds.isEmpty { if sounds.count == 1 { @@ -102,7 +115,13 @@ class SoundModel: ObservableObject, SoundHolder { self.sounds.formSymmetricDifference(sounds) } } - + } + + var playableIds: String { + var ids: Set = [] + ids.formUnion(self.sounds.map { $0.stringValue} ) + ids.formUnion(self.playlists.map { $0.stringValue} ) + return ids.joined(separator: idSeparator) } }