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