From 83a74df852a44e4b00cbaf4d306592f336ef04f6 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 24 Nov 2023 17:43:47 +0100 Subject: [PATCH] wip for intervals --- LeCountdown.xcodeproj/project.pbxproj | 2 +- LeCountdown/Conductor.swift | 106 +++++++++++++----- ...bstractSoundTimer+CoreDataProperties.swift | 3 +- .../Countdown+CoreDataProperties.swift | 3 +- .../LeCountdown.0.6.6.xcdatamodel/contents | 2 +- LeCountdown/Model/LiveTimer.swift | 1 + .../Model/Model+SharedExtensions.swift | 2 +- LeCountdown/Sound/DelaySoundPlayer.swift | 6 +- LeCountdown/Views/Alarm/NewAlarmView.swift | 2 +- .../Views/Countdown/CountdownFormView.swift | 28 +++-- .../Views/Countdown/NewCountdownView.swift | 9 +- .../Views/Countdown/RangeFormView.swift | 13 ++- LeCountdown/Views/LiveTimerListView.swift | 14 ++- .../Views/Reusable/SoundFormView.swift | 15 +-- LeCountdown/Views/StartView.swift | 17 ++- .../Views/{Reusable => }/TimerModel.swift | 8 +- LeCountdown/en.lproj/Localizable.strings | 4 + LeCountdown/fr.lproj/Localizable.strings | 6 +- 18 files changed, 159 insertions(+), 82 deletions(-) rename LeCountdown/Views/{Reusable => }/TimerModel.swift (95%) diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 9723771..864c695 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -802,6 +802,7 @@ C4E5D68929BB7953008E7465 /* SettingsView.swift */, C4286EB62A1B98420070D075 /* StartView.swift */, C4E5D68529BB369E008E7465 /* TimersView.swift */, + C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */, ); path = Views; sourceTree = ""; @@ -979,7 +980,6 @@ C4F8B165298A9ABB005C86A5 /* SoundFormView.swift */, C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */, C4286EA52A150A7E0070D075 /* TimePickerView.swift */, - C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */, C473C33829ACDBD70056B38A /* TipView.swift */, C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */, C4742B5E2984205000D5D950 /* ViewModifiers.swift */, diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 7b1f4ca..eb9a7c1 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -29,9 +29,49 @@ enum CountdownState { case cancelled } -struct CountdownSpan { +struct CountdownSequence: Codable { + var spans: [CountdownSpan] + + var currentSpan: CountdownSpan { + let now = Date() + let current: CountdownSpan? = self.spans.first { span in + return span.interval.start < now && span.interval.end > now + } + return current ?? self.spans.last ?? CountdownSpan(interval: DateInterval(start: Date(), end: Date()), name: "none") + } + + var currentEnd: Date { + return self.currentSpan.interval.end + } + + var dateInterval: DateInterval { + let firstSpan = self.spans.first ?? CountdownSequence.defaultSpan + let lastSpan = self.spans.last ?? CountdownSequence.defaultSpan + return DateInterval(start: firstSpan.start, end: lastSpan.end) + } + + var end: Date { + if let lastSpan = self.spans.last { + return lastSpan.end + } else { + fatalError("no spans") + } + } + + private static let defaultSpan = CountdownSpan(interval: DateInterval(start: Date(), end: Date()), name: "none") +} + +struct CountdownSpan: Codable { var interval: DateInterval var name: String? + + var start: Date { + return self.interval.start + } + + var end: Date { + return self.interval.end + } } class Conductor: ObservableObject { @@ -44,7 +84,7 @@ class Conductor: ObservableObject { fileprivate var beats: Timer? = nil - @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval] + @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : CountdownSequence] @UserDefault(PreferenceKey.pausedCountdowns.rawValue, defaultValue: [:]) static var savedPausedCountdowns: [String : TimeInterval] @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : LiveStopWatch] @@ -57,14 +97,15 @@ class Conductor: ObservableObject { self.currentStopwatches = Conductor.savedStopwatches self.pausedCountdowns = Conductor.savedPausedCountdowns - self.beats = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in + self.beats = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in self._cleanupCountdowns() + self._buildLiveTimers() }) } @Published var cancelledCountdowns: [String] = [] - @Published var currentCountdowns: [String : DateInterval] = [:] { + @Published var currentCountdowns: [String : CountdownSequence] = [:] { didSet { Conductor.savedCountdowns = currentCountdowns // Logger.log("**** currentCountdowns didSet, count = \(currentCountdowns.count)") @@ -96,10 +137,10 @@ class Conductor: ObservableObject { fileprivate func _buildLiveTimers() { - let liveCountdowns: [LiveTimer] = self.currentCountdowns.map { - return LiveTimer(id: $0, date: $1.end) + let liveCountdowns: [LiveTimer] = self.currentCountdowns.map { id, sequence in + let currentSpan = sequence.currentSpan + return LiveTimer(id: id, name: currentSpan.name, date: currentSpan.end) } - // add countdown if not present for liveCountdown in liveCountdowns { if let index = self.liveTimers.firstIndex(where: { $0.id == liveCountdown.id }) { @@ -147,9 +188,9 @@ class Conductor: ObservableObject { fileprivate func _recordActivity(countdownId: String, cancelled: Bool) { let context = PersistenceController.shared.container.viewContext if let countdown: Countdown = context.object(stringId: countdownId), - let dateInterval = self.currentCountdowns[countdownId] { + let sequence: CountdownSequence = self.currentCountdowns[countdownId] { do { - try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval, cancelled: cancelled) + try CoreDataRequests.recordActivity(timer: countdown, dateInterval: sequence.dateInterval, cancelled: cancelled) } catch { Logger.error(error) // TODO: show error to user @@ -169,20 +210,29 @@ class Conductor: ObservableObject { do { var totalDuration = 0.0 + var spans: [CountdownSpan] = [] + let now = Date() - for _ in 0...countdown.repeatCount { + for _ in 0.. Date() { + } else if let end = self.currentCountdowns[id]?.end, end > Date() { return .inprogress } else { return .finished @@ -263,11 +315,11 @@ class Conductor: ObservableObject { } func pauseCountdown(id: TimerID) { - guard let interval = self.currentCountdowns[id] else { + guard let sequence = self.currentCountdowns[id] else { return } - let remainingTime = interval.end.timeIntervalSince(Date()) + let remainingTime = sequence.currentSpan.end.timeIntervalSince(Date()) self.pausedCountdowns[id] = remainingTime // cancel stuff @@ -280,7 +332,9 @@ class Conductor: ObservableObject { let context = PersistenceController.shared.container.viewContext if let countdown: Countdown = context.object(stringId: id), let remainingTime = self.pausedCountdowns[id] { - _ = try self._scheduleSoundPlayer(countdown: countdown, in: remainingTime) + + // TODO: RESUME +// _ = try self._scheduleSoundPlayer(countdown: countdown, in: remainingTime) self.pausedCountdowns.removeValue(forKey: id) } else { throw AppError.timerNotFound(id: id) @@ -338,7 +392,7 @@ class Conductor: ObservableObject { func restoreSoundPlayers() { - for (countdownId, interval) in self.currentCountdowns { + for (countdownId, span) in self.currentCountdowns { if !self._delayedSoundPlayers.contains(where: { $0.key == countdownId }) { let context = PersistenceController.shared.container.viewContext @@ -346,9 +400,11 @@ class Conductor: ObservableObject { do { let sound: Sound = countdown.someSound ?? Sound.default - let soundPlayer = try DelaySoundPlayer(timerID: countdownId, sound: sound) + let soundPlayer = try DelaySoundPlayer(sound: sound) self._delayedSoundPlayers[countdownId] = soundPlayer - try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount)) + + // TODO: RESTORE +// try soundPlayer.restore(for: span.interval.end, repeatCount: Int(countdown.repeatCount)) FileLogger.log("Restored sound player for \(self._timerName(countdownId))") } catch { Logger.error(error) diff --git a/LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift b/LeCountdown/Model/Generation/AbstractSoundTimer+CoreDataProperties.swift index 8dc96c7..3940017 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 17/05/2023. +// Created by Laurent Morvillier on 24/11/2023. // // @@ -18,6 +18,5 @@ extension AbstractSoundTimer { @NSManaged public var confirmationSoundList: String? @NSManaged public var playableIds: String? - @NSManaged public var repeatCount: Int16 } diff --git a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift index 79481a5..b69b70e 100644 --- a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift +++ b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift @@ -2,7 +2,7 @@ // Countdown+CoreDataProperties.swift // LeCountdown // -// Created by Laurent Morvillier on 23/11/2023. +// Created by Laurent Morvillier on 24/11/2023. // // @@ -16,6 +16,7 @@ extension Countdown { return NSFetchRequest(entityName: "Countdown") } + @NSManaged public var repeatCount: Int16 @NSManaged public var timeRanges: NSSet? } diff --git a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents index 9fab959..37c2a4b 100644 --- a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents @@ -3,7 +3,6 @@ - @@ -19,6 +18,7 @@ + diff --git a/LeCountdown/Model/LiveTimer.swift b/LeCountdown/Model/LiveTimer.swift index cdf517f..9181c12 100644 --- a/LeCountdown/Model/LiveTimer.swift +++ b/LeCountdown/Model/LiveTimer.swift @@ -10,6 +10,7 @@ import CoreData struct LiveTimer: Identifiable, Comparable { var id: String + var name: String? var date: Date var endDate: Date? diff --git a/LeCountdown/Model/Model+SharedExtensions.swift b/LeCountdown/Model/Model+SharedExtensions.swift index ad099fc..3117dec 100644 --- a/LeCountdown/Model/Model+SharedExtensions.swift +++ b/LeCountdown/Model/Model+SharedExtensions.swift @@ -64,7 +64,7 @@ extension Countdown { var formattedDuration: String { let durations: [String] = self.sortedRanges().map { $0.duration.hourMinuteSecond } var formatted: String - if durations.count > 2 { + if durations.count > 1 { formatted = durations.joined(separator: " / ") } else { formatted = durations.first ?? "none" diff --git a/LeCountdown/Sound/DelaySoundPlayer.swift b/LeCountdown/Sound/DelaySoundPlayer.swift index 2a7ad09..7aee07c 100644 --- a/LeCountdown/Sound/DelaySoundPlayer.swift +++ b/LeCountdown/Sound/DelaySoundPlayer.swift @@ -12,12 +12,12 @@ import AVFoundation fileprivate var _player: AVAudioPlayer - fileprivate var _timerID: TimerID +// fileprivate var _playerId: String fileprivate var _timer: Timer? = nil - init(timerID: TimerID, sound: Sound) throws { - self._timerID = timerID + init(sound: Sound) throws { +// self._playerId = playerId let soundFile = try sound.soundFile() diff --git a/LeCountdown/Views/Alarm/NewAlarmView.swift b/LeCountdown/Views/Alarm/NewAlarmView.swift index ad4183e..fb6c622 100644 --- a/LeCountdown/Views/Alarm/NewAlarmView.swift +++ b/LeCountdown/Views/Alarm/NewAlarmView.swift @@ -183,7 +183,7 @@ struct AlarmEditView: View { // a.setSounds(self.sounds) // a.setPlaylists(self.playlists) - a.repeatCount = self.soundRepeatCount +// a.repeatCount = self.soundRepeatCount if !self.nameString.isEmpty { diff --git a/LeCountdown/Views/Countdown/CountdownFormView.swift b/LeCountdown/Views/Countdown/CountdownFormView.swift index 34ea336..8acaf28 100644 --- a/LeCountdown/Views/Countdown/CountdownFormView.swift +++ b/LeCountdown/Views/Countdown/CountdownFormView.swift @@ -26,11 +26,8 @@ struct CountdownFormView : View { var durationBinding: Binding var imageBinding: Binding - var repeatCountBinding: Binding var hasRanges: Bool - var intervalRepeatBinding: Binding? = nil - @State var showRangeSheet = false @State var selectedRange: TimeRange? = nil @@ -40,7 +37,7 @@ struct CountdownFormView : View { Form { if self.hasRanges { - Section(header: Text("Name for tracking the activity")) { + Section(header: Text("Name")) { TextField("name", text: nameBinding) .focused($focusedField, equals: .name) .submitLabel(.continue) @@ -59,20 +56,29 @@ struct CountdownFormView : View { } } + Section { + HStack { + Stepper("Repeat Count", value: self.$model.repeatCount, in: 1...100) + Spacer() + Text(self.model.repeatCount.formatted()) + .frame(width: 24.0) + } + } + Section { Button { self._addInterval() } label: { HStack { Image(systemName: "plus.circle") - Text("Add range") + Text("Add interval") } } } } else { - Section(header: Text("Name for tracking the activity")) { + Section(header: Text("Name")) { TextField("name", text: nameBinding) .focused($focusedField, equals: .name) .submitLabel(.continue) @@ -91,11 +97,11 @@ struct CountdownFormView : View { SoundFormView( model: self.model, - imageBinding: self.imageBinding, - repeatCountBinding: self.repeatCountBinding) + imageBinding: self.imageBinding) } .sheet(item: self.$selectedRange) { item in - RangeFormView(timeRange: item, selectedItem: self.$selectedRange) + RangeFormView(timeRange: item, + selectedItem: self.$selectedRange) } } @@ -117,9 +123,7 @@ struct CountdownFormView_Previews: PreviewProvider { nameBinding: .constant(""), durationBinding: .constant(0.0), imageBinding: .constant(.pic3), - repeatCountBinding: .constant(2), - hasRanges: true, - intervalRepeatBinding: .constant(2)) + hasRanges: true) .environmentObject(TimerModel()) } } diff --git a/LeCountdown/Views/Countdown/NewCountdownView.swift b/LeCountdown/Views/Countdown/NewCountdownView.swift index f8cc078..c1158ff 100644 --- a/LeCountdown/Views/Countdown/NewCountdownView.swift +++ b/LeCountdown/Views/Countdown/NewCountdownView.swift @@ -55,7 +55,7 @@ struct CountdownEditView : View { @State var nameString: String = "" @State var duration: TimeInterval = 0.0 - @State var soundRepeatCount: Int16 = 0 +// @State var soundRepeatCount: Int16 = 0 @State var image: CoolPic = .pic1 @State var deleteConfirmationShown: Bool = false @@ -107,7 +107,6 @@ struct CountdownEditView : View { nameBinding: $nameString, durationBinding: $duration, imageBinding: $image, - repeatCountBinding: $soundRepeatCount, hasRanges: self.hasRanges) .environmentObject(self.model) @@ -205,8 +204,6 @@ struct CountdownEditView : View { fileprivate func _loadCountdown(_ countdown: Countdown) { -// self.duration = countdown.duration - let ranges = countdown.sortedRanges() if ranges.count > 1 { self.model.ranges = ranges @@ -222,7 +219,7 @@ struct CountdownEditView : View { self.model.soundModel.setPlayables(countdown.playables) self.model.confirmationSoundModel.setPlayables(countdown.confirmationPlayables) - self.soundRepeatCount = countdown.repeatCount + self.model.repeatCount = countdown.repeatCount if let image = countdown.image, let coolpic = CoolPic(rawValue: image) { @@ -266,7 +263,7 @@ struct CountdownEditView : View { cd.image = self.image.rawValue cd.playableIds = self.model.soundModel.playableIds cd.setConfirmationSounds(self.model.confirmationSoundModel.sounds) - cd.repeatCount = self.soundRepeatCount + cd.repeatCount = self.model.repeatCount if self.model.ranges.count > 0 { diff --git a/LeCountdown/Views/Countdown/RangeFormView.swift b/LeCountdown/Views/Countdown/RangeFormView.swift index 3e3eaf4..ed596ce 100644 --- a/LeCountdown/Views/Countdown/RangeFormView.swift +++ b/LeCountdown/Views/Countdown/RangeFormView.swift @@ -13,6 +13,7 @@ struct RangeFormView: View { @Binding var selectedItem: TimeRange? + @State var namePlaceholder = "name" @State var name: String = "" @State var duration: TimeInterval = 0.0 @@ -20,7 +21,7 @@ struct RangeFormView: View { Form { Section(header: Text("Name")) { - TextField("name", text: self.$name) + TextField(self.namePlaceholder, text: self.$name) } Section { @@ -32,7 +33,11 @@ struct RangeFormView: View { Section { Button { - self.timeRange.name = self.name + if self.name.isEmpty { + self.timeRange.name = self.namePlaceholder + } else { + self.timeRange.name = self.name + } self.timeRange.duration = self.duration self.selectedItem = nil } label: { @@ -44,8 +49,8 @@ struct RangeFormView: View { } } }.onAppear { - if let name = self.timeRange.name { - self.name = name + if let name = self.timeRange.name, !name.isEmpty { + self.namePlaceholder = name } self.duration = self.timeRange.duration } diff --git a/LeCountdown/Views/LiveTimerListView.swift b/LeCountdown/Views/LiveTimerListView.swift index 024b073..c285e7e 100644 --- a/LeCountdown/Views/LiveTimerListView.swift +++ b/LeCountdown/Views/LiveTimerListView.swift @@ -38,7 +38,10 @@ struct LiveTimerListView: View { VStack { ForEach(conductor.liveTimers) { liveTimer in if let timer: AbstractTimer = liveTimer.timer(context: self.viewContext) { - LiveTimerView(timer: timer, date: liveTimer.date, endDate: liveTimer.endDate) + LiveTimerView(timer: timer, + date: liveTimer.date, + endDate: liveTimer.endDate, + name: liveTimer.name) } } @@ -71,12 +74,15 @@ struct LiveTimerView: View { var timer: AbstractTimer var date: Date var endDate: Date? + var name: String? var body: some View { switch self.timer { case let cd as Countdown: - LiveCountdownView(countdown: cd, date: self.date) + LiveCountdownView(countdown: cd, + date: self.date, + name: self.name) case let sw as Stopwatch: LiveStopwatchView(stopwatch: sw, date: self.date, endDate: self.endDate) default: @@ -166,6 +172,7 @@ struct LiveCountdownView: View { @State var countdown: Countdown var date: Date + var name: String? @State var showCancelConfirmationPopup: Bool = false @@ -200,7 +207,8 @@ struct LiveCountdownView: View { TimeView(text: NSLocalizedString("Cancelled", comment: "")) } - Text(self.countdown.displayName.uppercased()) + let name = self.name ?? self.countdown.displayName + Text(name.uppercased()) .foregroundColor(self.colorScheme == .dark ? .white : .black) } diff --git a/LeCountdown/Views/Reusable/SoundFormView.swift b/LeCountdown/Views/Reusable/SoundFormView.swift index 37eda61..34ce72f 100644 --- a/LeCountdown/Views/Reusable/SoundFormView.swift +++ b/LeCountdown/Views/Reusable/SoundFormView.swift @@ -13,7 +13,7 @@ struct SoundFormView : View { var imageBinding: Binding - var repeatCountBinding: Binding? = nil +// var repeatCountBinding: Binding? = nil var optionalSound: Binding? = nil @State var imageSelectionSheetShown: Bool = false @@ -37,16 +37,6 @@ struct SoundFormView : View { catalog: .ring, title: NSLocalizedString("Sound", comment: "") ) - if self.repeatCountBinding != nil { - Picker("Repeat Count", selection: self.repeatCountBinding!) { - ForEach(0..<6) { - let count = Int16($0) - Text("\(count)").tag(count) - } - - } - } - SoundLinkView(soundModel: self.model.confirmationSoundModel, catalog: .confirmation, title: NSLocalizedString("Start Sound", comment: "")) @@ -83,8 +73,7 @@ struct SoundImageFormView_Previews: PreviewProvider { static var previews: some View { Form { SoundFormView(model: TimerModel(), - imageBinding: .constant(.pic1), - repeatCountBinding: .constant(2)) + imageBinding: .constant(.pic1)) } } } diff --git a/LeCountdown/Views/StartView.swift b/LeCountdown/Views/StartView.swift index 4b6218e..930ac42 100644 --- a/LeCountdown/Views/StartView.swift +++ b/LeCountdown/Views/StartView.swift @@ -22,7 +22,7 @@ struct StartView: View { VStack(spacing: 0.5) { Text("Select some of the predefined timers and customize them, or create your own") - .font(.callout) + .fontWeight(.medium) .frame(maxWidth: .infinity) .padding() .background(Color.accentColor) @@ -44,7 +44,7 @@ struct StartView: View { Button { self.showMultiTimerScreen = true } label: { - ImageButton(stringKey: "Create a timer with phases", systemImage: "plus.square.on.square") + ImageButton(stringKey: "Create an advanced timer", detailsStringKey: "Steps & repeat", systemImage: "crown") } .sheet(isPresented: self.$showMultiTimerScreen) { NewCountdownView(isPresented: $showMultiTimerScreen, hasRanges: true) @@ -103,12 +103,21 @@ struct StartView: View { struct ImageButton: View { var stringKey: LocalizedStringKey + var detailsStringKey: LocalizedStringKey? var systemImage: String var body: some View { - HStack { + HStack(spacing: 10.0) { Image(systemName: self.systemImage) - Text(self.stringKey) + .fontWeight(.medium) + .frame(width: 30.0) + VStack(alignment: .leading) { + Text(self.stringKey) + .fontWeight(.bold) + if let detailsStringKey { + Text(detailsStringKey).font(.footnote) + } + } Spacer() } .padding() diff --git a/LeCountdown/Views/Reusable/TimerModel.swift b/LeCountdown/Views/TimerModel.swift similarity index 95% rename from LeCountdown/Views/Reusable/TimerModel.swift rename to LeCountdown/Views/TimerModel.swift index 0970171..6e3c573 100644 --- a/LeCountdown/Views/Reusable/TimerModel.swift +++ b/LeCountdown/Views/TimerModel.swift @@ -20,16 +20,16 @@ class TimerModel: ObservableObject { @Published var soundModel: SoundModel = SoundModel() @Published var confirmationSoundModel: SoundModel = SoundModel() -// @Published var group: CountdownIntervalGroup = -// CountdownIntervalGroup(repeatCount: 0, intervals: []) - @Published var ranges: [TimeRange] = [] + @Published var repeatCount: Int16 = 1 func addInterval(context: NSManagedObjectContext) -> TimeRange { let timeRange = TimeRange(context: context) + let index: String = (self.ranges.count + 1).formatted() + timeRange.name = NSLocalizedString("Step", comment: "") + " " + index + self.ranges.append(timeRange) return timeRange -// self.editedRange = timeRange } // @Published var editedRange: TimeRange? = nil diff --git a/LeCountdown/en.lproj/Localizable.strings b/LeCountdown/en.lproj/Localizable.strings index 2412a94..f224eb1 100644 --- a/LeCountdown/en.lproj/Localizable.strings +++ b/LeCountdown/en.lproj/Localizable.strings @@ -3,3 +3,7 @@ "Widget Tip" = "Quickly launch your timers with widgets. You can add them by modifying your home or lock screen."; "Play confirmation sound" = "Play sound on start"; "Create your first timer or stopwatch!" = "Create your first timer or stopwatch!"; +"Step" = "Step"; +"Create an advanced timer" = "Create an advanced timer"; +"Steps & repeat" = "Steps & repeat"; +"Add interval" = "Add interval"; diff --git a/LeCountdown/fr.lproj/Localizable.strings b/LeCountdown/fr.lproj/Localizable.strings index dcc9bae..7b2be1d 100644 --- a/LeCountdown/fr.lproj/Localizable.strings +++ b/LeCountdown/fr.lproj/Localizable.strings @@ -161,7 +161,7 @@ "Rename" = "Renommer"; /* No comment provided by engineer. */ -"Repeat Count" = "Nombre de répétitions"; +"Repeat Count" = "Nombre de boucles"; /* No comment provided by engineer. */ "Save" = "Sauvegarder"; @@ -292,3 +292,7 @@ "tap to copy email" = "Tapez pour copier l'email"; "Write a review!" = "Écrivez un avis !"; "Adjust volume on launch" = "Ajuster le volume au lancement"; +"Step" = "Étape"; +"Create an advanced timer" = "Créer un minuteur avancé"; +"Steps & repeat" = "Intervalles et répétitions"; +"Add interval" = "Ajouter un intervalle";