Multiples fixes and improvements

main
Laurent 2 years ago
parent c453446c89
commit 2b062b04a4
  1. 4
      LeCountdown.xcodeproj/project.pbxproj
  2. 24
      LeCountdown/Conductor.swift
  3. 3
      LeCountdown/Model/CoreDataRequests.swift
  4. 5
      LeCountdown/Model/Generation/Record+CoreDataProperties.swift
  5. 2
      LeCountdown/Model/LeCountdown.xcdatamodeld/.xccurrentversion
  6. 53
      LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.5.xcdatamodel/contents
  7. 2
      LeCountdown/Sound/Sound.swift
  8. 24
      LeCountdown/Views/ContentView.swift
  9. 1
      LeCountdown/Views/Countdown/CountdownFormView.swift
  10. 11
      LeCountdown/Views/PresetsView.swift
  11. 2
      LeCountdown/Views/Reusable/SoundFormView.swift
  12. 15
      LeCountdown/Views/Reusable/SoundSelectionView.swift
  13. 10
      LeCountdown/Views/SettingsView.swift
  14. 2
      LeCountdown/Views/StartView.swift
  15. 1
      LeCountdown/Views/Stopwatch/StopwatchFormView.swift
  16. 1
      LeCountdown/en.lproj/Localizable.strings
  17. 7
      LeCountdown/fr.lproj/Localizable.strings

@ -412,6 +412,7 @@
C445FA8E2987B83B0054D761 /* SoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundPlayer.swift; sourceTree = "<group>"; }; C445FA8E2987B83B0054D761 /* SoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundPlayer.swift; sourceTree = "<group>"; };
C445FA902987C0CF0054D761 /* LeCountdown.0.2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.2.xcdatamodel; sourceTree = "<group>"; }; C445FA902987C0CF0054D761 /* LeCountdown.0.2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.2.xcdatamodel; sourceTree = "<group>"; };
C445FA912987CC8A0054D761 /* Sound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sound.swift; sourceTree = "<group>"; }; C445FA912987CC8A0054D761 /* Sound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sound.swift; sourceTree = "<group>"; };
C454892C2A28D9610047D39E /* LeCountdown.0.6.5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.5.xcdatamodel; sourceTree = "<group>"; };
C4556F6A29E40B7800DEB40B /* FileLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogger.swift; sourceTree = "<group>"; }; C4556F6A29E40B7800DEB40B /* FileLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogger.swift; sourceTree = "<group>"; };
C4556F6E29E40BED00DEB40B /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; }; C4556F6E29E40BED00DEB40B /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
C4556F7029E40DCF00DEB40B /* Codable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Extensions.swift"; sourceTree = "<group>"; }; C4556F7029E40DCF00DEB40B /* Codable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Extensions.swift"; sourceTree = "<group>"; };
@ -2017,6 +2018,7 @@
C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */ = { C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */ = {
isa = XCVersionGroup; isa = XCVersionGroup;
children = ( children = (
C454892C2A28D9610047D39E /* LeCountdown.0.6.5.xcdatamodel */,
C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */, C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */,
C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */, C4A16DBD29D1C9DE00143D5E /* LeCountdown.0.6.3.xcdatamodel */,
C4BA2B6B29A4C47100CB4FBA /* LeCountdown.0.6.2.xcdatamodel */, C4BA2B6B29A4C47100CB4FBA /* LeCountdown.0.6.2.xcdatamodel */,
@ -2030,7 +2032,7 @@
C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */, C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */,
C4060DCB297AE73D003FAB80 /* LeCountdown.xcdatamodel */, C4060DCB297AE73D003FAB80 /* LeCountdown.xcdatamodel */,
); );
currentVersion = C4A16DCA29D323CF00143D5E /* LeCountdown.0.6.4.xcdatamodel */; currentVersion = C454892C2A28D9610047D39E /* LeCountdown.0.6.5.xcdatamodel */;
path = LeCountdown.xcdatamodeld; path = LeCountdown.xcdatamodeld;
sourceTree = "<group>"; sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel; versionGroupType = wrapper.xcdatamodel;

@ -137,17 +137,12 @@ class Conductor: ObservableObject {
return self.cancelledCountdowns.contains(where: { $0 == countdown.stringId }) return self.cancelledCountdowns.contains(where: { $0 == countdown.stringId })
} }
// func notifyUser(countdownId: String) { fileprivate func _recordActivity(countdownId: String, cancelled: Bool) {
// // self._playSound(timerId: countdownId)
// self._endCountdown(countdownId: countdownId, cancel: false)
// }
fileprivate func _recordActivity(countdownId: String) {
let context = PersistenceController.shared.container.viewContext let context = PersistenceController.shared.container.viewContext
if let countdown: Countdown = context.object(stringId: countdownId), if let countdown: Countdown = context.object(stringId: countdownId),
let dateInterval = self.currentCountdowns[countdownId] { let dateInterval = self.currentCountdowns[countdownId] {
do { do {
try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval) try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval, cancelled: cancelled)
} catch { } catch {
Logger.error(error) Logger.error(error)
// TODO: show error to user // TODO: show error to user
@ -167,7 +162,6 @@ class Conductor: ObservableObject {
if Preferences.playConfirmationSound { if Preferences.playConfirmationSound {
self._playConfirmationSound(timer: countdown) self._playConfirmationSound(timer: countdown)
} }
handler(.success(end)) handler(.success(end))
} catch { } catch {
FileLogger.log("start error : \(error.localizedDescription)") FileLogger.log("start error : \(error.localizedDescription)")
@ -214,7 +208,7 @@ class Conductor: ObservableObject {
CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) CountdownScheduler.master.cancelCurrentNotifications(countdownId: id)
self.cancelSoundPlayer(id: id) self.cancelSoundPlayer(id: id)
self.cancelledCountdowns.append(id) self.cancelledCountdowns.append(id)
self._endCountdown(countdownId: id, cancel: true) self._recordAndRemoveCountdown(countdownId: id, cancel: true)
self.pausedCountdowns.removeValue(forKey: id) self.pausedCountdowns.removeValue(forKey: id)
if Preferences.playCancellationSound { if Preferences.playCancellationSound {
@ -270,13 +264,9 @@ class Conductor: ObservableObject {
} }
} }
fileprivate func _endCountdown(countdownId: String, cancel: Bool) { fileprivate func _recordAndRemoveCountdown(countdownId: String, cancel: Bool) {
DispatchQueue.main.async { DispatchQueue.main.async {
#if DEBUG self._recordActivity(countdownId: countdownId, cancelled: cancel)
if !cancel {
self._recordActivity(countdownId: countdownId)
}
#endif
self.currentCountdowns.removeValue(forKey: countdownId) self.currentCountdowns.removeValue(forKey: countdownId)
self._endLiveActivity(timerId: countdownId) self._endLiveActivity(timerId: countdownId)
} }
@ -313,7 +303,7 @@ class Conductor: ObservableObject {
Conductor.maestro.currentStopwatches[stopwatch.stringId] = lsw Conductor.maestro.currentStopwatches[stopwatch.stringId] = lsw
do { do {
try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: lsw.start, end: end)) try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: lsw.start, end: end), cancelled: false)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -363,7 +353,7 @@ class Conductor: ObservableObject {
let now = Date() let now = Date()
for (key, value) in self.currentCountdowns { for (key, value) in self.currentCountdowns {
if value.end < now || self.cancelledCountdowns.contains(key) { if value.end < now || self.cancelledCountdowns.contains(key) {
self._endCountdown(countdownId: key, cancel: false) self._recordAndRemoveCountdown(countdownId: key, cancel: false)
} }
} }
} }

@ -43,7 +43,7 @@ class CoreDataRequests {
return activity return activity
} }
static func recordActivity(timer: AbstractTimer, dateInterval: DateInterval) throws { static func recordActivity(timer: AbstractTimer, dateInterval: DateInterval, cancelled: Bool) throws {
guard let activity = timer.activity else { guard let activity = timer.activity else {
return return
@ -54,6 +54,7 @@ class CoreDataRequests {
record.start = dateInterval.start record.start = dateInterval.start
record.end = dateInterval.end record.end = dateInterval.end
record.activity = activity record.activity = activity
record.cancelled = cancelled
try context.save() try context.save()
} }

@ -2,7 +2,7 @@
// Record+CoreDataProperties.swift // Record+CoreDataProperties.swift
// LeCountdown // LeCountdown
// //
// Created by Laurent Morvillier on 21/02/2023. // Created by Laurent Morvillier on 01/06/2023.
// //
// //
@ -18,9 +18,10 @@ extension Record {
@NSManaged public var duration: Double @NSManaged public var duration: Double
@NSManaged public var end: Date? @NSManaged public var end: Date?
@NSManaged public var month: Int16
@NSManaged public var start: Date? @NSManaged public var start: Date?
@NSManaged public var year: Int16 @NSManaged public var year: Int16
@NSManaged public var month: Int16 @NSManaged public var cancelled: Bool
@NSManaged public var activity: Activity? @NSManaged public var activity: Activity?
} }

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

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22A400" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="AbstractSoundTimer" representedClassName="AbstractSoundTimer" isAbstract="YES" parentEntity="AbstractTimer" elementID="soundList" syncable="YES">
<attribute name="confirmationSoundList" optional="YES" attributeType="String"/>
<attribute name="playableIds" optional="YES" attributeType="String"/>
<attribute name="repeatCount" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
</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="cancelled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="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>

@ -56,7 +56,7 @@ enum Catalog {
var playlists: [Playlist] { var playlists: [Playlist] {
switch self { switch self {
case .ring: return [.nature, .stephanBodzin, .relax] case .ring: return [.stephanBodzin, .nature, .relax]
case .confirmation: return [.shorts] case .confirmation: return [.shorts]
} }
} }

@ -52,10 +52,8 @@ struct ContentView<T : AbstractTimer>: View {
Spacer() Spacer()
#if !DEBUG #if !TARGET_IPHONE_SIMULATOR
SiriVolumeView(timer: self.boringContext.siriTimer, siriTipShown: self.$siriTipShown) SiriVolumeView(timer: self.boringContext.siriTimer, siriTipShown: self.$siriTipShown)
#endif #endif
LiveTimerListView() LiveTimerListView()
@ -205,13 +203,10 @@ struct MainToolbarView: ToolbarContent {
self.showStatsSheet.toggle() self.showStatsSheet.toggle()
} }
} label: { } label: {
Image(systemName: "chart.bar.doc.horizontal") Image(systemName: "scroll")
} }
.sheet(isPresented: self.$showStatsSheet, content: { .sheet(isPresented: self.$showStatsSheet, content: {
NavigationStack { NavigationStack {
// AppleMusicPickerView() { media in
//
// }
ActivitiesView() ActivitiesView()
} }
}) })
@ -225,8 +220,10 @@ struct MainToolbarView: ToolbarContent {
Image(systemName: "gearshape.fill") Image(systemName: "gearshape.fill")
} }
.sheet(isPresented: self.$showSettingsSheet, content: { .sheet(isPresented: self.$showSettingsSheet, content: {
SettingsView() NavigationStack {
.presentationDetents([.height(280.0)]) SettingsView()
.navigationBarTitleDisplayMode(.inline)
}.presentationDetents([.height(290.0)])
}) })
} }
Button { Button {
@ -293,15 +290,6 @@ class TimerSpot : Identifiable, Equatable {
} }
//struct ContentView_Previews: PreviewProvider {
// static var previews: some View {
// ContentView<Countdown>()
// .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
// .environmentObject(Conductor.maestro)
//
// }
//}
struct Toolbar_Previews: PreviewProvider { struct Toolbar_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
NavigationStack { NavigationStack {

@ -34,6 +34,7 @@ struct CountdownFormView : View {
Section(header: Text("Name for tracking the activity")) { Section(header: Text("Name for tracking the activity")) {
TextField("name", text: nameBinding) TextField("name", text: nameBinding)
.focused($focusedField, equals: .name) .focused($focusedField, equals: .name)
.submitLabel(.continue)
.onSubmit { .onSubmit {
self.focusedField = nil self.focusedField = nil
} }

@ -204,6 +204,7 @@ enum PresetSection: Int, Identifiable, CaseIterable {
case move case move
case tea case tea
case cooking case cooking
case more
var presets: [Preset] { var presets: [Preset] {
switch self { switch self {
@ -212,6 +213,7 @@ enum PresetSection: Int, Identifiable, CaseIterable {
// case .workout: return [.runningSplits] // case .workout: return [.runningSplits]
case .mindfullness: return [.nap, .meditation, .writing, .reading] case .mindfullness: return [.nap, .meditation, .writing, .reading]
case .move: return [.workout, .stretching, .toothbrushing] case .move: return [.workout, .stretching, .toothbrushing]
case .more: return [.work]
} }
} }
@ -222,6 +224,7 @@ enum PresetSection: Int, Identifiable, CaseIterable {
case .mindfullness: return NSLocalizedString("Self", comment: "") case .mindfullness: return NSLocalizedString("Self", comment: "")
case .move: return NSLocalizedString("Move", comment: "") case .move: return NSLocalizedString("Move", comment: "")
case .tea: return NSLocalizedString("Tea", comment: "") case .tea: return NSLocalizedString("Tea", comment: "")
case .more: return NSLocalizedString("More", comment: "")
} }
} }
} }
@ -236,6 +239,7 @@ struct CountdownInterval {
var sound: Sound? var sound: Sound?
} }
// idee: coherence cardiaque
enum Preset: Int, Identifiable, CaseIterable { enum Preset: Int, Identifiable, CaseIterable {
var id: Int { return self.rawValue } var id: Int { return self.rawValue }
@ -255,7 +259,8 @@ enum Preset: Int, Identifiable, CaseIterable {
case reading case reading
case stretching case stretching
case cleaning case cleaning
case work
var localizedName: String { var localizedName: String {
switch self { switch self {
case .hardBoiledEggs: return NSLocalizedString("Hard boiled eggs", comment: "") case .hardBoiledEggs: return NSLocalizedString("Hard boiled eggs", comment: "")
@ -274,6 +279,7 @@ enum Preset: Int, Identifiable, CaseIterable {
case .reading: return NSLocalizedString("Reading", comment: "") case .reading: return NSLocalizedString("Reading", comment: "")
case .stretching: return NSLocalizedString("Stretching", comment: "") case .stretching: return NSLocalizedString("Stretching", comment: "")
case .cleaning: return NSLocalizedString("Cleaning", comment: "") case .cleaning: return NSLocalizedString("Cleaning", comment: "")
case .work: return NSLocalizedString("Work", comment: "")
} }
} }
@ -306,12 +312,13 @@ enum Preset: Int, Identifiable, CaseIterable {
case .writing: return 30 * 60.0 case .writing: return 30 * 60.0
case .reading: return 20 * 60.0 case .reading: return 20 * 60.0
case .cleaning: return 60 * 60.0 case .cleaning: return 60 * 60.0
case .work: return 60 * 60.0
} }
} }
var playlist: Playlist { var playlist: Playlist {
switch self { switch self {
case .softBoiled, .mediumBoiledEggs, .hardBoiledEggs, .pasta, .rice, .toothbrushing, .workout, .stretching: case .softBoiled, .mediumBoiledEggs, .hardBoiledEggs, .pasta, .rice, .toothbrushing, .workout, .stretching, .work:
return .stephanBodzin return .stephanBodzin
case .meditation, .blackTea, .greenTea, .writing, .reading: case .meditation, .blackTea, .greenTea, .writing, .reading:
return .relax return .relax

@ -49,7 +49,7 @@ struct SoundFormView : View {
SoundLinkView(soundModel: self.model.confirmationSoundModel, SoundLinkView(soundModel: self.model.confirmationSoundModel,
catalog: .confirmation, catalog: .confirmation,
title: NSLocalizedString("Confirmation Sound", comment: "")) title: NSLocalizedString("Start Sound", comment: ""))
}.sheet(isPresented: self.$imageSelectionSheetShown) { }.sheet(isPresented: self.$imageSelectionSheetShown) {
ImageSelectionView(showBinding: self.$imageSelectionSheetShown, ImageSelectionView(showBinding: self.$imageSelectionSheetShown,

@ -15,9 +15,18 @@ struct PlaylistsView: View {
var body: some View { var body: some View {
Form { VStack {
ForEach(catalog.playlists) { playlist in Form {
PlaylistSectionView(model: self.model, playlist: playlist)
Section {
EmptyView()
} header: {
Text("Select multiple sounds to randomize").font(.footnote)
}
ForEach(catalog.playlists) { playlist in
PlaylistSectionView(model: self.model, playlist: playlist)
}
} }
} }
.navigationTitle("Sounds") .navigationTitle("Sounds")

@ -23,10 +23,10 @@ struct SettingsView: View {
.onChange(of: self.confirmationSound) { newValue in .onChange(of: self.confirmationSound) { newValue in
Preferences.playConfirmationSound = newValue Preferences.playConfirmationSound = newValue
} }
Toggle("Play cancellation sound", isOn: self.$cancellationSound) // Toggle("Play cancellation sound", isOn: self.$cancellationSound)
.onChange(of: self.cancellationSound) { newValue in // .onChange(of: self.cancellationSound) { newValue in
Preferences.playCancellationSound = newValue // Preferences.playCancellationSound = newValue
} // }
HStack { HStack {
Text("Default Volume") Text("Default Volume")
Spacer() Spacer()
@ -56,6 +56,8 @@ struct SettingsView: View {
}.sheet(isPresented: $showMailView) { }.sheet(isPresented: $showMailView) {
MailView(isShowing: $showMailView) MailView(isShowing: $showMailView)
} }
.navigationTitle("Settings")
} }

@ -173,7 +173,7 @@ struct SoundButtonView: View {
.navigationTitle("Sounds") .navigationTitle("Sounds")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
.presentationDetents([.height(320.0)]) .presentationDetents([.height(360.0)])
} }
} }

@ -27,6 +27,7 @@ struct StopwatchFormView: View {
Section(header: Text("Name for tracking the activity")) { Section(header: Text("Name for tracking the activity")) {
TextField("name", text: nameBinding) TextField("name", text: nameBinding)
.focused($focusedField, equals: .name) .focused($focusedField, equals: .name)
.submitLabel(.continue)
.onSubmit { .onSubmit {
self.focusedField = nil self.focusedField = nil
} }

@ -1,3 +1,4 @@
"You'll find your timers here. Start by creating them on the left screen" = "You'll find your timers here.\nStart by creating using the top right button!"; "You'll find your timers here. Start by creating them on the left screen" = "You'll find your timers here.\nStart by creating using the top right button!";
"Disclaimer" = "This is a beta version of Enchant.\n\nPlease don't depend on the app for critical events :)\n\n If you have some feedback or an issue, please tell me about it through the settings or within Testflight!"; "Disclaimer" = "This is a beta version of Enchant.\n\nPlease don't depend on the app for critical events :)\n\n If you have some feedback or an issue, please tell me about it through the settings or within Testflight!";
"Widget Tip" = "Quickly launch your timers with widget. You can add them by modifying your home or lock screen."; "Widget Tip" = "Quickly launch your timers with widget. You can add them by modifying your home or lock screen.";
"Play confirmation sound" = "Play sound on start";

@ -248,7 +248,7 @@
"Settings" = "Réglages"; "Settings" = "Réglages";
"Pasta" = "Pasta"; "Pasta" = "Pasta";
"Rice" = "Riz"; "Rice" = "Riz";
"Play confirmation sound" = "Jouer son de confirmation"; "Play confirmation sound" = "Jouer son au démarrage";
"Play cancellation sound" = "Jouer son d'annulation"; "Play cancellation sound" = "Jouer son d'annulation";
"Contact us" = "Contactez-nous"; "Contact us" = "Contactez-nous";
"Confirmation" = "Confirmation"; "Confirmation" = "Confirmation";
@ -259,7 +259,7 @@
"Timer" = "Minuteur"; "Timer" = "Minuteur";
"Stopwatch" = "Chronomètre"; "Stopwatch" = "Chronomètre";
"Duration" = "Durée"; "Duration" = "Durée";
"Confirmation Sound" = "Son de démarrage"; "Start Sound" = "Son de démarrage";
"Name" = "Nom"; "Name" = "Nom";
"Hours" = "Heures"; "Hours" = "Heures";
"Default Volume" = "Volume par défaut"; "Default Volume" = "Volume par défaut";
@ -278,3 +278,6 @@
"Create your own" = "Créez les vôtres"; "Create your own" = "Créez les vôtres";
"Select some of the predefined timers and customize them, or create your own" = "Sélectionnez et personnalisez les minuteurs, ou créez les vôtres"; "Select some of the predefined timers and customize them, or create your own" = "Sélectionnez et personnalisez les minuteurs, ou créez les vôtres";
"Learn more" = "En savoir plus..."; "Learn more" = "En savoir plus...";
"Work" = "Travail";
"More" = "Plus";
"Select multiple sounds to randomize" = "Sélectionnez plusieurs sons pour varier les sonneries";

Loading…
Cancel
Save