Adds duration next to sound

release
Laurent 3 years ago
parent 3e69ef40f1
commit ad0f2d4790
  1. 2
      LaunchIntents/IntentHandler.swift
  2. 24
      LeCountdown.xcodeproj/project.pbxproj
  3. 9
      LeCountdown/Conductor.swift
  4. 2
      LeCountdown/LeCountdownApp.swift
  5. 35
      LeCountdown/Model/Fakes.swift
  6. 57
      LeCountdown/Model/Model+Extensions.swift
  7. 39
      LeCountdown/Model/Model+SharedExtensions.swift
  8. 27
      LeCountdown/Sound/Sound.swift
  9. 6
      LeCountdown/Sound/SoundPlayer.swift
  10. 6
      LeCountdown/Utils/Preferences.swift
  11. 113
      LeCountdown/Views/Components/SoundSelectionView.swift

@ -37,7 +37,7 @@ class IntentHandler: INExtension, SelectTimerIntentHandling {
}
break
case let stopwatch as Stopwatch:
displayName = stopwatch.name ?? "no name"
displayName = stopwatch.activity?.name ?? "no name"
default:
displayName = "no name"
}

@ -48,7 +48,6 @@
C438C81129829EAF00BF3EF9 /* PropertyWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C81029829EAF00BF3EF9 /* PropertyWrappers.swift */; };
C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7FE2981300500BF3EF9 /* IntentDataProvider.swift */; };
C438C8162982BE1E00BF3EF9 /* LeCountdown.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */; };
C438C8172982BE9C00BF3EF9 /* Model+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C806298195E600BF3EF9 /* Model+Extensions.swift */; };
C438C8182982BFC100BF3EF9 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4060DC8297AE73D003FAB80 /* Persistence.swift */; };
C438C8192982BFDB00BF3EF9 /* NSManagedContext+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7C4298024E900BF3EF9 /* NSManagedContext+Extensions.swift */; };
C438C81A2982BFF100BF3EF9 /* NSManagedContext+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7C4298024E900BF3EF9 /* NSManagedContext+Extensions.swift */; };
@ -57,7 +56,6 @@
C445FA882984487F0054D761 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C4060DC3297AE73D003FAB80 /* Assets.xcassets */; };
C445FA8F2987B83B0054D761 /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA8E2987B83B0054D761 /* SoundPlayer.swift */; };
C445FA922987CC8A0054D761 /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA912987CC8A0054D761 /* Sound.swift */; };
C445FA932987CF280054D761 /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA912987CC8A0054D761 /* Sound.swift */; };
C445FA952987D01C0054D761 /* train_horn.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C445FA942987D01C0054D761 /* train_horn.mp3 */; };
C4742B5729840F6400D5D950 /* CoolPic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5629840F6400D5D950 /* CoolPic.swift */; };
C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B58298411E800D5D950 /* CountdownFormView.swift */; };
@ -111,6 +109,12 @@
C4BA2B2D299E2DEE00CB4FBA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */; };
C4BA2B2F299E69A000CB4FBA /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */; };
C4BA2B32299F75DE00CB4FBA /* DefaultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B30299F759700CB4FBA /* DefaultView.swift */; };
C4BA2B36299F82FB00CB4FBA /* Fakes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B35299F82FB00CB4FBA /* Fakes.swift */; };
C4BA2B37299F82FF00CB4FBA /* Fakes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B35299F82FB00CB4FBA /* Fakes.swift */; };
C4BA2B38299F82FF00CB4FBA /* Fakes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B35299F82FB00CB4FBA /* Fakes.swift */; };
C4BA2B3A299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */; };
C4BA2B3B299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */; };
C4BA2B3C299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */; };
C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; };
C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; };
C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; };
@ -157,8 +161,6 @@
C4F8B1B8298AC81D005C86A5 /* CountdownDialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1B7298AC81D005C86A5 /* CountdownDialView.swift */; };
C4F8B1BD298AC8DE005C86A5 /* AlarmDialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1BC298AC8DE005C86A5 /* AlarmDialView.swift */; };
C4F8B1BF298ACA0B005C86A5 /* StopwatchDialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1BE298ACA0B005C86A5 /* StopwatchDialView.swift */; };
C4F8B1C0298ACA61005C86A5 /* Model+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C806298195E600BF3EF9 /* Model+Extensions.swift */; };
C4F8B1C3298ACBDB005C86A5 /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA912987CC8A0054D761 /* Sound.swift */; };
C4F8B1C6298ACC1F005C86A5 /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C445FA8E2987B83B0054D761 /* SoundPlayer.swift */; };
C4F8B1D0298BF2E2005C86A5 /* DialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1CF298BF2E2005C86A5 /* DialView.swift */; };
C4F8B1D2298BF646005C86A5 /* PermissionAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B1D1298BF646005C86A5 /* PermissionAlertView.swift */; };
@ -295,6 +297,8 @@
C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = "<group>"; };
C4BA2B30299F759700CB4FBA /* DefaultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultView.swift; sourceTree = "<group>"; };
C4BA2B35299F82FB00CB4FBA /* Fakes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fakes.swift; sourceTree = "<group>"; };
C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Model+SharedExtensions.swift"; sourceTree = "<group>"; };
C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = "<group>"; };
C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = "<group>"; };
C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
@ -489,8 +493,10 @@
C438C80C2982847300BF3EF9 /* CoreDataRequests.swift */,
C498E5A0298D543900E90DE0 /* LiveTimer.swift */,
C438C806298195E600BF3EF9 /* Model+Extensions.swift */,
C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */,
C438C7C4298024E900BF3EF9 /* NSManagedContext+Extensions.swift */,
C4060DC8297AE73D003FAB80 /* Persistence.swift */,
C4BA2B35299F82FB00CB4FBA /* Fakes.swift */,
);
path = Model;
sourceTree = "<group>";
@ -835,6 +841,7 @@
files = (
C4F8B1D2298BF646005C86A5 /* PermissionAlertView.swift in Sources */,
C4060DC9297AE73D003FAB80 /* Persistence.swift in Sources */,
C4BA2B36299F82FB00CB4FBA /* Fakes.swift in Sources */,
C498E5A1298D543900E90DE0 /* LiveTimer.swift in Sources */,
C438C80F29828B8600BF3EF9 /* RecordsView.swift in Sources */,
C4F8B187298AC234005C86A5 /* Activity+CoreDataProperties.swift in Sources */,
@ -858,6 +865,7 @@
C4BA2B11299BE61E00CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */,
C4BA2AFD299A3A3700CB4FBA /* AppleMusicPlayer.swift in Sources */,
C4BA2B12299BE61E00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */,
C4BA2B3A299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */,
C4BA2AF32996A11900CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */,
C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */,
C4BA2B25299D35C100CB4FBA /* HomeView.swift in Sources */,
@ -926,8 +934,8 @@
buildActionMask = 2147483647;
files = (
C4BA2B15299BE6A000CB4FBA /* Interval+CoreDataProperties.swift in Sources */,
C445FA932987CF280054D761 /* Sound.swift in Sources */,
C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */,
C4BA2B3B299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */,
C438C7EB2981266F00BF3EF9 /* SingleTimerView.swift in Sources */,
C438C7D62981216200BF3EF9 /* LaunchWidgetBundle.swift in Sources */,
C4BA2B16299BE6A000CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */,
@ -939,13 +947,13 @@
C4BA2AF72996A4EF00CB4FBA /* CustomSound+CoreDataClass.swift in Sources */,
C4F8B1AD298AC451005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */,
C445FA87298448730054D761 /* CoolPic.swift in Sources */,
C438C8172982BE9C00BF3EF9 /* Model+Extensions.swift in Sources */,
C438C8162982BE1E00BF3EF9 /* LeCountdown.xcdatamodeld in Sources */,
C4F8B194298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */,
C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */,
C438C7DF2981216300BF3EF9 /* LaunchWidget.intentdefinition in Sources */,
C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */,
C438C7E82981255D00BF3EF9 /* TimeInterval+Extensions.swift in Sources */,
C4BA2B37299F82FF00CB4FBA /* Fakes.swift in Sources */,
C4F8B18F298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */,
C4BA2B19299BE6A000CB4FBA /* Countdown+CoreDataProperties.swift in Sources */,
C438C7D82981216200BF3EF9 /* LaunchWidgetLiveActivity.swift in Sources */,
@ -973,13 +981,12 @@
C4F8B1B2298AC451005C86A5 /* Alarm+CoreDataProperties.swift in Sources */,
C4BA2AF92996A4F000CB4FBA /* CustomSound+CoreDataClass.swift in Sources */,
C4F8B1A1298AC288005C86A5 /* Countdown+CoreDataClass.swift in Sources */,
C4BA2B38299F82FF00CB4FBA /* Fakes.swift in Sources */,
C438C7F529812BB200BF3EF9 /* IntentHandler.swift in Sources */,
C4F8B19C298AC288005C86A5 /* AbstractTimer+CoreDataProperties.swift in Sources */,
C4BA2B1C299BE6A100CB4FBA /* IntervalGroup+CoreDataClass.swift in Sources */,
C4F8B1C0298ACA61005C86A5 /* Model+Extensions.swift in Sources */,
C4BA2B1F299BE6A100CB4FBA /* Countdown+CoreDataProperties.swift in Sources */,
C4F8B1A3298AC288005C86A5 /* Activity+CoreDataClass.swift in Sources */,
C4F8B1C3298ACBDB005C86A5 /* Sound.swift in Sources */,
C4F8B19A298AC288005C86A5 /* Alarm+CoreDataClass.swift in Sources */,
C438C80529813FB400BF3EF9 /* TimeInterval+Extensions.swift in Sources */,
C4BA2B1B299BE6A100CB4FBA /* Interval+CoreDataProperties.swift in Sources */,
@ -988,6 +995,7 @@
C4F8B1A0298AC288005C86A5 /* Activity+CoreDataProperties.swift in Sources */,
C438C81A2982BFF100BF3EF9 /* NSManagedContext+Extensions.swift in Sources */,
C4F8B19D298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */,
C4BA2B3C299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */,
C4BA2AF82996A4F000CB4FBA /* CustomSound+CoreDataProperties.swift in Sources */,
C4F8B199298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */,
C438C8012981327600BF3EF9 /* Persistence.swift in Sources */,

@ -10,19 +10,14 @@ import ActivityKit
import BackgroundTasks
import SwiftUI
enum Key: String {
case countdowns
case stopwatches
}
class Conductor: ObservableObject {
static let maestro: Conductor = Conductor()
@Published var soundPlayer: SoundPlayer? = nil
@UserDefault(Key.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval]
@UserDefault(Key.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date]
@UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval]
@UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date]
@Published private (set) var liveTimers: [LiveTimer] = []

@ -67,6 +67,8 @@ struct LeCountdownApp: App {
fileprivate func _onAppear() {
Sound.computeSoundDurationsIfNecessary()
// let voices = AVSpeechSynthesisVoice.speechVoices()
// let grouped = Dictionary(grouping: voices, by: { $0.language })
// for language in grouped.keys {

@ -0,0 +1,35 @@
//
// Fakes.swift
// LeCountdown
//
// Created by Laurent Morvillier on 17/02/2023.
//
import Foundation
import CoreData
extension Countdown {
static func fake(context: NSManagedObjectContext) -> Countdown {
let cd = Countdown(context: context)
cd.duration = 4 * 60.0
let activity = Activity(context: context)
activity.name = "Tea"
cd.activity = activity
return cd
}
}
extension Alarm {
static func fake(context: NSManagedObjectContext) -> Alarm {
let alarm = Alarm(context: context)
alarm.fireDate = Date()
let activity = Activity(context: context)
activity.name = "Wakeup"
alarm.activity = activity
return alarm
}
}

@ -13,37 +13,6 @@ enum TimerError: Error {
case notificationAuthorizationMissing
}
extension AbstractTimer {
var displayName: String {
return self.name ?? self.coolpic.emoji
}
var name: String? {
return self.activity?.name
}
var url: URL {
if let url = URL(string: self.stringId) {
return url
} else {
fatalError("Can't produce url with \(self.stringId)")
}
}
var coolpic: CoolPic {
if let image, let coolpic = CoolPic(rawValue: image) {
return coolpic
}
return CoolPic.allCases[0]
}
var imageName: String {
return self.coolpic.rawValue
}
}
extension AbstractSoundTimer {
var sounds: Set<Sound> {
@ -80,32 +49,6 @@ extension AbstractSoundTimer {
}
extension Countdown {
static func fake(context: NSManagedObjectContext) -> Countdown {
let cd = Countdown(context: context)
cd.duration = 4 * 60.0
let activity = Activity(context: context)
activity.name = "Tea"
cd.activity = activity
return cd
}
}
extension Alarm {
static func fake(context: NSManagedObjectContext) -> Alarm {
let alarm = Alarm(context: context)
alarm.fireDate = Date()
let activity = Activity(context: context)
activity.name = "Wakeup"
alarm.activity = activity
return alarm
}
}
extension Stopwatch {
var coolSound: Sound? {

@ -0,0 +1,39 @@
//
// Model+SharedExtensions.swift
// LeCountdown
//
// Created by Laurent Morvillier on 17/02/2023.
//
import Foundation
extension AbstractTimer {
var displayName: String {
return self.name ?? self.coolpic.emoji
}
var name: String? {
return self.activity?.name
}
var url: URL {
if let url = URL(string: self.stringId) {
return url
} else {
fatalError("Can't produce url with \(self.stringId)")
}
}
var coolpic: CoolPic {
if let image, let coolpic = CoolPic(rawValue: image) {
return coolpic
}
return CoolPic.allCases[0]
}
var imageName: String {
return self.coolpic.rawValue
}
}

@ -109,13 +109,6 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
}
}
// var duration: TimeInterval {
// switch self {
// case .trainhorn: return 7.8
// case .forestStream: return 300.1
// }
// }
var url: URL? {
let components = self.soundName.components(separatedBy: ".")
@ -144,4 +137,24 @@ enum Sound: Int, CaseIterable, Identifiable, Localized {
return CMTimeGetSeconds(duration)
}
static func computeSoundDurationsIfNecessary() {
Task {
for sound in Sound.allCases {
if Preferences.soundDurations[sound.rawValue] == nil {
if let duration = try? await sound.duration() {
Preferences.soundDurations[sound.rawValue] = duration
}
}
}
}
}
var formattedDuration: String {
if let duration = Preferences.soundDurations[self.rawValue] {
return duration.minuteSecond
} else {
return ""
}
}
}

@ -42,9 +42,9 @@ enum SoundPlayerError : Error {
throw SoundPlayerError.missingResourceError(file: soundFile)
}
let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playback)
try audioSession.setActive(true)
// let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
// try audioSession.setCategory(.playback)
// try audioSession.setActive(true)
_player = try AVAudioPlayer(contentsOf: url)
_player?.prepareToPlay()

@ -8,11 +8,17 @@
import Foundation
enum PreferenceKey: String {
case countdowns
case stopwatches
case showSilentModeAlert
case soundDurations
}
class Preferences {
@UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval]
@UserDefault(PreferenceKey.soundDurations.rawValue, defaultValue: [:]) static var soundDurations: [Int : TimeInterval]
static var hideSilentModeAlerts: Bool {
return UserDefaults.standard.bool(forKey: PreferenceKey.showSilentModeAlert.rawValue)
}

@ -10,76 +10,19 @@ import SwiftUI
struct PlaylistsView: View {
@EnvironmentObject var model: TimerModel
// @Environment(\.managedObjectContext) private var viewContext
// @FetchRequest(
// sortDescriptors: [NSSortDescriptor(keyPath: \CustomSound.text, ascending: true)],
// animation: .default)
// private var customSounds: FetchedResults<CustomSound>
//
// @State private var showNewSound: Bool = false
// @State private var name = ""
var body: some View {
Form {
// if customSounds.count > 0 {
//
// Section(Playlist.custom.localizedString) {
//
// ForEach(self.customSounds) { customSound in
// ToggleRow(item: customSound, selected: .constant(false)) { selected in
// // todo
// }
// }
// }
//
// }
ForEach(Playlist.selectable) { playlist in
PlaylistSectionView(playlist: playlist)
.environmentObject(self.model)
}
}
.navigationTitle("Sounds")
// .alert("", isPresented: $showNewSound) {
// TextField("Enter your name", text: $name)
// Button("OK", action: _submit)
// } message: {
// Text("Xcode will print whatever you type.")
// }
// .toolbar {
// ToolbarItem(placement: .navigationBarTrailing) {
// Button {
// self.showNewSound = true
// } label: {
// Image(systemName: "plus")
// }
// }
// }
}
// fileprivate func _submit() {
// print("You entered \(name)")
// TextToSpeechRecorder.record(speech: self.name) { result in
// switch result {
// case .success(let file):
//
// let cs = CustomSound(context: viewContext)
// cs.text = self.name
// cs.file = file.fileName
//
// try! viewContext.save() // TODO
//
// case .failure(let failure):
// print("error = \(failure)")
// break
// }
// }
// }
}
struct PlaylistSectionView: View {
@ -92,11 +35,22 @@ struct PlaylistSectionView: View {
Section {
let sounds = SoundCatalog.main.sounds(for: self.playlist)
ForEach(sounds) { sound in
ImageToggleRow(item: sound, selected: self.model.binding(sound: sound)) { selected in
self.model.selectSound(sound, selected: selected)
}.onTapGesture {
self._playSound(sound)
HStack {
HStack {
Image(systemName: "play.circle")
.foregroundColor(Color.accentColor)
Text(sound.localizedString)
}.onTapGesture {
self._playSound(sound)
}
Spacer()
RightAlignToggleRow(item: sound, selected: self.model.binding(sound: sound), keyPath: \.formattedDuration) { selected in
self.model.selectSound(sound, selected: selected)
}.frame(width: 120.0)
.foregroundColor(.gray)
.font(.caption)
}
}
} header: {
@ -119,23 +73,52 @@ struct ToggleRow<T : Localized>: View {
var handleSelection: (Bool) -> ()
var body: some View {
Toggle(item.localizedString, isOn: $selected)
Toggle(self.item.localizedString, isOn: $selected)
.onChange(of: self.selected, perform: handleSelection)
}
}
struct ImageToggleRow<T : Localized>: View {
struct RightAlignToggleRow<T : Localized>: View {
var item: T
@Binding var selected: Bool
var keyPath: KeyPath<T, String>
var handleSelection: (Bool) -> ()
var body: some View {
Toggle(isOn: $selected) {
HStack {
Spacer()
Text(self.item[keyPath: keyPath])
}
}.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)
// }
}
}
struct ImageToggleRow<T : Localized>: View {
var item: T
@Binding var selected: Bool
var handleSelection: (Bool) -> ()
var body: some View {
HStack {
Image(systemName: "play.circle").foregroundColor(Color.accentColor)
Image(systemName: "play.circle")
.foregroundColor(Color.accentColor)
ToggleRow(item: item, selected: $selected, handleSelection: handleSelection)
}
}
}
//struct PlaylistRow: View {

Loading…
Cancel
Save