parent
16fbfa1d65
commit
7ccb37479d
@ -0,0 +1,33 @@ |
|||||||
|
<?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="AbstractTimer" representedClassName="AbstractTimer" isAbstract="YES" syncable="YES" codeGenerationType="class"> |
||||||
|
<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" codeGenerationType="class"> |
||||||
|
<attribute name="name" attributeType="String" defaultValueString=""/> |
||||||
|
<relationship name="records" optional="YES" toMany="YES" deletionRule="Nullify" 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="SoundTimer" syncable="YES" codeGenerationType="class"> |
||||||
|
<attribute name="fireDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> |
||||||
|
</entity> |
||||||
|
<entity name="Countdown" representedClassName="Countdown" parentEntity="SoundTimer" syncable="YES" codeGenerationType="class"> |
||||||
|
<attribute name="duration" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/> |
||||||
|
</entity> |
||||||
|
<entity name="Record" representedClassName="Record" syncable="YES" codeGenerationType="class"> |
||||||
|
<attribute name="end" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/> |
||||||
|
<attribute name="start" attributeType="Date" defaultDateTimeInterval="696425400" usesScalarValueType="NO"/> |
||||||
|
<relationship name="activity" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Activity" inverseName="records" inverseEntity="Activity"/> |
||||||
|
</entity> |
||||||
|
<entity name="SoundTimer" representedClassName="SoundTimer" isAbstract="YES" parentEntity="AbstractTimer" syncable="YES" codeGenerationType="class"> |
||||||
|
<attribute name="repeats" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/> |
||||||
|
<attribute name="sound" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/> |
||||||
|
</entity> |
||||||
|
<entity name="Stopwatch" representedClassName="Stopwatch" parentEntity="AbstractTimer" syncable="YES" codeGenerationType="class"> |
||||||
|
<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> |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
// |
||||||
|
// AlarmFormView.swift |
||||||
|
// LeCountdown |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 01/02/2023. |
||||||
|
// |
||||||
|
|
||||||
|
import SwiftUI |
||||||
|
|
||||||
|
struct AlarmFormView: View { |
||||||
|
|
||||||
|
var dateBinding: Binding<Date> |
||||||
|
|
||||||
|
var imageBinding: Binding<CoolPic> |
||||||
|
var soundBinding: Binding<Sound> |
||||||
|
var repeatsBinding: Binding<Bool> |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
|
||||||
|
Form { |
||||||
|
DatePicker("Time", selection: dateBinding, displayedComponents: .hourAndMinute) |
||||||
|
SoundImageFormView(imageBinding: imageBinding, soundBinding: soundBinding, repeatsBinding: nil) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct AlarmFormView_Previews: PreviewProvider { |
||||||
|
static var previews: some View { |
||||||
|
AlarmFormView(dateBinding: .constant(Date()), |
||||||
|
imageBinding: .constant(.pic1), soundBinding: .constant(.trainhorn), repeatsBinding: .constant(true)) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,247 @@ |
|||||||
|
// |
||||||
|
// NewAlarmView.swift |
||||||
|
// LeCountdown |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 01/02/2023. |
||||||
|
// |
||||||
|
|
||||||
|
import SwiftUI |
||||||
|
import CoreData |
||||||
|
import WidgetKit |
||||||
|
|
||||||
|
struct NewAlarmView: View { |
||||||
|
|
||||||
|
@Environment(\.managedObjectContext) private var viewContext |
||||||
|
|
||||||
|
@Binding var isPresented: Bool |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
EditAlarmView(isPresented: $isPresented) |
||||||
|
.environment(\.managedObjectContext, viewContext) |
||||||
|
.navigationTitle("New countdown") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
struct EditAlarmView: View { |
||||||
|
|
||||||
|
@Environment(\.managedObjectContext) private var viewContext |
||||||
|
@Environment(\.dismiss) private var dismiss |
||||||
|
|
||||||
|
var alarm: Alarm? = nil |
||||||
|
|
||||||
|
@Binding var isPresented: Bool |
||||||
|
|
||||||
|
@State var time: Date = Date() |
||||||
|
|
||||||
|
@State var nameString: String = "" |
||||||
|
|
||||||
|
@State var sound: Sound = .trainhorn |
||||||
|
@State var soundRepeats: Bool = true |
||||||
|
@State var image: CoolPic = .pic1 |
||||||
|
|
||||||
|
@State var deleteConfirmationShown: Bool = false |
||||||
|
@State var activityNameConfirmationShown: Bool = false |
||||||
|
@State fileprivate var _rename: Bool? = nil |
||||||
|
|
||||||
|
@State var errorShown: Bool = false |
||||||
|
@State var error: Error? = nil |
||||||
|
|
||||||
|
@FocusState private var textFieldIsFocused: Bool |
||||||
|
|
||||||
|
// @FetchRequest(sortDescriptors: []) |
||||||
|
// private var countdowns: FetchedResults<Countdown> |
||||||
|
|
||||||
|
@State var _isAdding: Bool = false |
||||||
|
|
||||||
|
@Environment(\.isPresented) var envIsPresented |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
NavigationStack { |
||||||
|
Rectangle() |
||||||
|
.frame(width: 0.0, height: 0.0) |
||||||
|
.onChange(of: envIsPresented) { newValue in |
||||||
|
if !newValue && !self._isAdding { |
||||||
|
self._save() |
||||||
|
} |
||||||
|
} |
||||||
|
AlarmFormView(dateBinding: self.$time, |
||||||
|
imageBinding: self.$image, |
||||||
|
soundBinding: self.$sound, |
||||||
|
repeatsBinding: self.$soundRepeats) |
||||||
|
.onAppear { |
||||||
|
self._onAppear() |
||||||
|
} |
||||||
|
.confirmationDialog("", isPresented: $deleteConfirmationShown, actions: { |
||||||
|
Button("Yes", role: .destructive) { |
||||||
|
withAnimation { |
||||||
|
self._delete() |
||||||
|
} |
||||||
|
}.keyboardShortcut(.defaultAction) |
||||||
|
Button("No", role: .cancel) {} |
||||||
|
}, message: { |
||||||
|
Text("Do you really want to delete?") |
||||||
|
}) |
||||||
|
.confirmationDialog("", isPresented: $activityNameConfirmationShown, actions: { |
||||||
|
Button("Rename") { |
||||||
|
self._rename = true |
||||||
|
self._save() |
||||||
|
} |
||||||
|
Button("New activity") { |
||||||
|
self._rename = false |
||||||
|
self._save() |
||||||
|
} |
||||||
|
}, message: { |
||||||
|
Text("Do you wish to rename or create a new activity") |
||||||
|
}) |
||||||
|
|
||||||
|
.alert("", isPresented: $errorShown, actions: { }, message: { |
||||||
|
Text(error?.localizedDescription ?? "error") |
||||||
|
}) |
||||||
|
.toolbar { |
||||||
|
if self._isAdding { |
||||||
|
ToolbarItem(placement: .navigationBarLeading) { |
||||||
|
Button("Cancel") { |
||||||
|
self._cancel() |
||||||
|
} |
||||||
|
} |
||||||
|
ToolbarItem(placement: .navigationBarTrailing) { |
||||||
|
Button("Save") { |
||||||
|
self._save() |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
ToolbarItem(placement: .navigationBarTrailing) { |
||||||
|
Button { |
||||||
|
self.deleteConfirmationShown = true |
||||||
|
} label: { |
||||||
|
Image(systemName: "trash") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ToolbarItemGroup(placement: .keyboard) { |
||||||
|
Button { |
||||||
|
textFieldIsFocused = false |
||||||
|
} label: { |
||||||
|
Image(systemName: "checkmark") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.navigationTitle("Edit countdown") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _onAppear() { |
||||||
|
|
||||||
|
self._isAdding = (self.alarm == nil) |
||||||
|
|
||||||
|
if let alarm { |
||||||
|
|
||||||
|
if let fireDate = alarm.fireDate { |
||||||
|
self.time = fireDate |
||||||
|
} |
||||||
|
|
||||||
|
if let name = alarm.activity?.name, !name.isEmpty { |
||||||
|
self.nameString = name |
||||||
|
} |
||||||
|
|
||||||
|
if let sound = Sound(rawValue: Int(alarm.sound)) { |
||||||
|
self.sound = sound |
||||||
|
} |
||||||
|
|
||||||
|
if let image = alarm.image, let coolpic = CoolPic(rawValue: image) { |
||||||
|
self.image = coolpic |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
fileprivate func _cancel() { |
||||||
|
viewContext.rollback() |
||||||
|
self.isPresented = false |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _save() { |
||||||
|
|
||||||
|
let a: Alarm |
||||||
|
if let alarm { |
||||||
|
a = alarm |
||||||
|
} else { |
||||||
|
a = Alarm(context: viewContext) |
||||||
|
} |
||||||
|
|
||||||
|
// if self._isAdding { |
||||||
|
// let max = self.countdowns.map { $0.order }.max() ?? 0 |
||||||
|
// cd.order = max + 1 |
||||||
|
// } |
||||||
|
|
||||||
|
a.fireDate = self.time |
||||||
|
a.image = self.image.rawValue |
||||||
|
a.sound = Int16(self.sound.rawValue) |
||||||
|
a.repeats = true |
||||||
|
|
||||||
|
if !self.nameString.isEmpty { |
||||||
|
|
||||||
|
if let activity = a.activity, let currentActivityName = activity.name, self.nameString != currentActivityName { |
||||||
|
|
||||||
|
switch self._rename { |
||||||
|
case .none: |
||||||
|
self.activityNameConfirmationShown = true |
||||||
|
return |
||||||
|
case .some(let rename): |
||||||
|
if rename { |
||||||
|
activity.name = self.nameString |
||||||
|
} else { |
||||||
|
a.activity = CoreDataRequests.getOrCreateActivity(name: self.nameString) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
a.activity = CoreDataRequests.getOrCreateActivity(name: self.nameString) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
self._saveContext() |
||||||
|
|
||||||
|
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets |
||||||
|
|
||||||
|
self._popOrDismiss() |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _popOrDismiss() { |
||||||
|
if self._isAdding { |
||||||
|
self.isPresented = false |
||||||
|
} else { |
||||||
|
dismiss() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _delete() { |
||||||
|
|
||||||
|
guard let alarm else { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
viewContext.delete(alarm) |
||||||
|
self._saveContext() |
||||||
|
|
||||||
|
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets |
||||||
|
|
||||||
|
self._popOrDismiss() |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _saveContext() { |
||||||
|
do { |
||||||
|
try viewContext.save() |
||||||
|
} catch { |
||||||
|
self.errorShown = true |
||||||
|
self.error = error |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct NewAlarmView_Previews: PreviewProvider { |
||||||
|
static var previews: some View { |
||||||
|
NewAlarmView(isPresented: .constant(true)) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,235 @@ |
|||||||
|
// |
||||||
|
// NewStopwatchView.swift |
||||||
|
// LeCountdown |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 01/02/2023. |
||||||
|
// |
||||||
|
|
||||||
|
import SwiftUI |
||||||
|
import CoreData |
||||||
|
import WidgetKit |
||||||
|
|
||||||
|
struct NewStopwatchView: View { |
||||||
|
|
||||||
|
@Environment(\.managedObjectContext) private var viewContext |
||||||
|
@Environment(\.dismiss) private var dismiss |
||||||
|
|
||||||
|
var stopwatch: Stopwatch? = nil |
||||||
|
|
||||||
|
@Binding var isPresented: Bool |
||||||
|
|
||||||
|
@State var time: Date = Date() |
||||||
|
|
||||||
|
@State var nameString: String = "" |
||||||
|
|
||||||
|
@State var playSound: Bool = false |
||||||
|
@State var sound: Sound = .trainhorn |
||||||
|
@State var image: CoolPic = .pic1 |
||||||
|
|
||||||
|
@State var deleteConfirmationShown: Bool = false |
||||||
|
@State var activityNameConfirmationShown: Bool = false |
||||||
|
@State fileprivate var _rename: Bool? = nil |
||||||
|
|
||||||
|
@State var errorShown: Bool = false |
||||||
|
@State var error: Error? = nil |
||||||
|
|
||||||
|
@FocusState private var textFieldIsFocused: Bool |
||||||
|
|
||||||
|
// @FetchRequest(sortDescriptors: []) |
||||||
|
// private var countdowns: FetchedResults<Countdown> |
||||||
|
|
||||||
|
@State var _isAdding: Bool = false |
||||||
|
|
||||||
|
@Environment(\.isPresented) var envIsPresented |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
NavigationStack { |
||||||
|
Rectangle() |
||||||
|
.frame(width: 0.0, height: 0.0) |
||||||
|
.onChange(of: envIsPresented) { newValue in |
||||||
|
if !newValue && !self._isAdding { |
||||||
|
self._save() |
||||||
|
} |
||||||
|
} |
||||||
|
StopwatchFormView(nameBinding: self.$nameString, |
||||||
|
imageBinding: self.$image, |
||||||
|
soundBinding: self.$sound, playSoundBinding: self.$playSound, |
||||||
|
textFieldIsFocused: $textFieldIsFocused) |
||||||
|
.onAppear { |
||||||
|
self._onAppear() |
||||||
|
} |
||||||
|
.confirmationDialog("", isPresented: $deleteConfirmationShown, actions: { |
||||||
|
Button("Yes", role: .destructive) { |
||||||
|
withAnimation { |
||||||
|
self._delete() |
||||||
|
} |
||||||
|
}.keyboardShortcut(.defaultAction) |
||||||
|
Button("No", role: .cancel) {} |
||||||
|
}, message: { |
||||||
|
Text("Do you really want to delete?") |
||||||
|
}) |
||||||
|
.confirmationDialog("", isPresented: $activityNameConfirmationShown, actions: { |
||||||
|
Button("Rename") { |
||||||
|
self._rename = true |
||||||
|
self._save() |
||||||
|
} |
||||||
|
Button("New activity") { |
||||||
|
self._rename = false |
||||||
|
self._save() |
||||||
|
} |
||||||
|
}, message: { |
||||||
|
Text("Do you wish to rename or create a new activity") |
||||||
|
}) |
||||||
|
|
||||||
|
.alert("", isPresented: $errorShown, actions: { }, message: { |
||||||
|
Text(error?.localizedDescription ?? "error") |
||||||
|
}) |
||||||
|
.toolbar { |
||||||
|
if self._isAdding { |
||||||
|
ToolbarItem(placement: .navigationBarLeading) { |
||||||
|
Button("Cancel") { |
||||||
|
self._cancel() |
||||||
|
} |
||||||
|
} |
||||||
|
ToolbarItem(placement: .navigationBarTrailing) { |
||||||
|
Button("Save") { |
||||||
|
self._save() |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
ToolbarItem(placement: .navigationBarTrailing) { |
||||||
|
Button { |
||||||
|
self.deleteConfirmationShown = true |
||||||
|
} label: { |
||||||
|
Image(systemName: "trash") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ToolbarItemGroup(placement: .keyboard) { |
||||||
|
Button { |
||||||
|
textFieldIsFocused = false |
||||||
|
} label: { |
||||||
|
Image(systemName: "checkmark") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.navigationTitle("Edit countdown") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _onAppear() { |
||||||
|
|
||||||
|
self._isAdding = (self.stopwatch == nil) |
||||||
|
|
||||||
|
if let stopwatch { |
||||||
|
|
||||||
|
if let name = stopwatch.activity?.name, !name.isEmpty { |
||||||
|
self.nameString = name |
||||||
|
} |
||||||
|
|
||||||
|
if let sound = Sound(rawValue: Int(stopwatch.sound)) { |
||||||
|
self.sound = sound |
||||||
|
} |
||||||
|
|
||||||
|
if let image = stopwatch.image, let coolpic = CoolPic(rawValue: image) { |
||||||
|
self.image = coolpic |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _cancel() { |
||||||
|
viewContext.rollback() |
||||||
|
self.isPresented = false |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _save() { |
||||||
|
|
||||||
|
let sw: Stopwatch |
||||||
|
if let stopwatch { |
||||||
|
sw = stopwatch |
||||||
|
} else { |
||||||
|
sw = Stopwatch(context: viewContext) |
||||||
|
} |
||||||
|
|
||||||
|
// if self._isAdding { |
||||||
|
// let max = self.countdowns.map { $0.order }.max() ?? 0 |
||||||
|
// cd.order = max + 1 |
||||||
|
// } |
||||||
|
|
||||||
|
sw.image = self.image.rawValue |
||||||
|
|
||||||
|
if self.playSound { |
||||||
|
sw.sound = Int16(self.sound.rawValue) |
||||||
|
} else { |
||||||
|
// sw.sound = nil |
||||||
|
} |
||||||
|
|
||||||
|
if !self.nameString.isEmpty { |
||||||
|
|
||||||
|
if let activity = sw.activity, let currentActivityName = activity.name, self.nameString != currentActivityName { |
||||||
|
|
||||||
|
switch self._rename { |
||||||
|
case .none: |
||||||
|
self.activityNameConfirmationShown = true |
||||||
|
return |
||||||
|
case .some(let rename): |
||||||
|
if rename { |
||||||
|
activity.name = self.nameString |
||||||
|
} else { |
||||||
|
sw.activity = CoreDataRequests.getOrCreateActivity(name: self.nameString) |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
sw.activity = CoreDataRequests.getOrCreateActivity(name: self.nameString) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
self._saveContext() |
||||||
|
|
||||||
|
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets |
||||||
|
|
||||||
|
self._popOrDismiss() |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _popOrDismiss() { |
||||||
|
if self._isAdding { |
||||||
|
self.isPresented = false |
||||||
|
} else { |
||||||
|
dismiss() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _delete() { |
||||||
|
|
||||||
|
guard let stopwatch else { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
viewContext.delete(stopwatch) |
||||||
|
self._saveContext() |
||||||
|
|
||||||
|
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets |
||||||
|
|
||||||
|
self._popOrDismiss() |
||||||
|
} |
||||||
|
|
||||||
|
fileprivate func _saveContext() { |
||||||
|
do { |
||||||
|
try viewContext.save() |
||||||
|
} catch { |
||||||
|
self.errorShown = true |
||||||
|
self.error = error |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
struct NewStopwatchView_Previews: PreviewProvider { |
||||||
|
static var previews: some View { |
||||||
|
NewStopwatchView(isPresented: .constant(true)) |
||||||
|
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,89 @@ |
|||||||
|
// |
||||||
|
// SoundImageFormView.swift |
||||||
|
// LeCountdown |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 01/02/2023. |
||||||
|
// |
||||||
|
|
||||||
|
import SwiftUI |
||||||
|
|
||||||
|
struct SoundImageFormView : View { |
||||||
|
|
||||||
|
var imageBinding: Binding<CoolPic> |
||||||
|
var soundBinding: Binding<Sound> |
||||||
|
var repeatsBinding: Binding<Bool>? = nil |
||||||
|
var optionalSound: Binding<Bool>? = nil |
||||||
|
|
||||||
|
@State var imageSelectionSheetShown: Bool = false |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
|
||||||
|
Group { |
||||||
|
|
||||||
|
Section(header: Text("Properties")) { |
||||||
|
|
||||||
|
if self.optionalSound != nil { |
||||||
|
|
||||||
|
Toggle("Play sound on end", isOn: optionalSound!) |
||||||
|
|
||||||
|
if self.optionalSound?.wrappedValue == true { |
||||||
|
Picker(selection: soundBinding) { |
||||||
|
ForEach(Sound.allCases) { sound in |
||||||
|
Text(sound.localizedString).tag(sound) |
||||||
|
} |
||||||
|
} label: { |
||||||
|
Text("Sound") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
Picker(selection: soundBinding) { |
||||||
|
ForEach(Sound.allCases) { sound in |
||||||
|
Text(sound.localizedString).tag(sound) |
||||||
|
} |
||||||
|
} label: { |
||||||
|
Text("Sound") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if self.repeatsBinding != nil { |
||||||
|
Toggle("Sound repeats", isOn: repeatsBinding!) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Section(header: Text("Background")) { |
||||||
|
|
||||||
|
Button { |
||||||
|
self.imageSelectionSheetShown = true |
||||||
|
} label: { |
||||||
|
Group { |
||||||
|
if let image = self.imageBinding.wrappedValue { |
||||||
|
Image(image.rawValue).resizable() |
||||||
|
} else { |
||||||
|
Image(imageBinding.wrappedValue.rawValue).resizable() |
||||||
|
} |
||||||
|
} |
||||||
|
.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) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
struct SoundImageFormView_Previews: PreviewProvider { |
||||||
|
static var previews: some View { |
||||||
|
Form { |
||||||
|
SoundImageFormView(imageBinding: .constant(.pic1), |
||||||
|
soundBinding: .constant(.trainhorn), |
||||||
|
repeatsBinding: .constant(true)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
// |
||||||
|
// StopwatchFormView.swift |
||||||
|
// LeCountdown |
||||||
|
// |
||||||
|
// Created by Laurent Morvillier on 01/02/2023. |
||||||
|
// |
||||||
|
|
||||||
|
import SwiftUI |
||||||
|
|
||||||
|
struct StopwatchFormView: View { |
||||||
|
|
||||||
|
var nameBinding: Binding<String> |
||||||
|
var imageBinding: Binding<CoolPic> |
||||||
|
var soundBinding: Binding<Sound> |
||||||
|
var playSoundBinding: Binding<Bool> |
||||||
|
|
||||||
|
var textFieldIsFocused: FocusState<Bool>.Binding |
||||||
|
|
||||||
|
var body: some View { |
||||||
|
|
||||||
|
Form { |
||||||
|
Section(header: Text("Name for tracking the activity")) { |
||||||
|
TextField("name", text: nameBinding) |
||||||
|
.focused(textFieldIsFocused) |
||||||
|
} |
||||||
|
|
||||||
|
SoundImageFormView(imageBinding: imageBinding, |
||||||
|
soundBinding: soundBinding, |
||||||
|
optionalSound: playSoundBinding) |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct StopwatchFormView_Previews: PreviewProvider { |
||||||
|
|
||||||
|
@FocusState static var textFieldIsFocused: Bool |
||||||
|
|
||||||
|
static var previews: some View { |
||||||
|
StopwatchFormView(nameBinding: .constant(""), |
||||||
|
imageBinding: .constant(.pic1), |
||||||
|
soundBinding: .constant(.trainhorn), playSoundBinding: .constant(true), textFieldIsFocused: $textFieldIsFocused) |
||||||
|
} |
||||||
|
} |
||||||
@ -1,43 +0,0 @@ |
|||||||
// |
|
||||||
// DialView.swift |
|
||||||
// LeCountdown |
|
||||||
// |
|
||||||
// Created by Laurent Morvillier on 30/01/2023. |
|
||||||
// |
|
||||||
|
|
||||||
import SwiftUI |
|
||||||
|
|
||||||
struct DialView: View { |
|
||||||
|
|
||||||
@EnvironmentObject var environment: Conductor |
|
||||||
|
|
||||||
var name: String |
|
||||||
var duration: String |
|
||||||
|
|
||||||
var body: some View { |
|
||||||
|
|
||||||
VStack { |
|
||||||
HStack { |
|
||||||
Text(name.uppercased()).monospaced() |
|
||||||
Spacer() |
|
||||||
} |
|
||||||
HStack { |
|
||||||
Text(duration).monospaced() |
|
||||||
Spacer() |
|
||||||
} |
|
||||||
Spacer() |
|
||||||
}.padding() |
|
||||||
.frame(width: 200, height: 200) |
|
||||||
.foregroundColor(.white) |
|
||||||
.background(Color.cyan) |
|
||||||
.cornerRadius(32.0) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
struct DialView_Previews: PreviewProvider { |
|
||||||
|
|
||||||
static var previews: some View { |
|
||||||
DialView(name: "Running", duration: "2:00").environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue