|
|
|
@ -7,16 +7,74 @@ |
|
|
|
|
|
|
|
|
|
|
|
import SwiftUI |
|
|
|
import SwiftUI |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct StartView: View { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@StateObject var model: PresetSelectionModel = PresetSelectionModel() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Binding var isPresented: Bool |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@State var showAddScreen: Bool = false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
|
|
|
VStack(spacing: 0.5) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PresetSelectionView(model: self.model).monospaced() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button { |
|
|
|
|
|
|
|
self.showAddScreen = true |
|
|
|
|
|
|
|
} label: { |
|
|
|
|
|
|
|
HStack { |
|
|
|
|
|
|
|
Image(systemName: "plus.circle").font(.title) |
|
|
|
|
|
|
|
Text("Create your own").font(.title3) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.padding() |
|
|
|
|
|
|
|
.frame(maxWidth: .infinity) |
|
|
|
|
|
|
|
.foregroundColor(.white) |
|
|
|
|
|
|
|
.background(Color.accentColor) |
|
|
|
|
|
|
|
}.sheet(isPresented: self.$showAddScreen) { |
|
|
|
|
|
|
|
NewCountdownView(isPresented: $showAddScreen) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button { |
|
|
|
|
|
|
|
self._done() |
|
|
|
|
|
|
|
} label: { |
|
|
|
|
|
|
|
Text("Done") |
|
|
|
|
|
|
|
.font(.title2).fontWeight(.semibold) |
|
|
|
|
|
|
|
.padding() |
|
|
|
|
|
|
|
.frame(maxWidth: .infinity) |
|
|
|
|
|
|
|
.background(.white) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}.background(.gray) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fileprivate func _done() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let customizations = self.model.customizations.values.filter { $0.added } |
|
|
|
|
|
|
|
for custo in customizations { |
|
|
|
|
|
|
|
let _ = custo.createTimer() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
try context.save() |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Logger.error(error) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Preferences.hasShownStartView = true |
|
|
|
|
|
|
|
self.isPresented = false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class Customization: ObservableObject { |
|
|
|
class Customization: ObservableObject { |
|
|
|
|
|
|
|
|
|
|
|
var preset: Preset |
|
|
|
var preset: Preset |
|
|
|
|
|
|
|
|
|
|
|
@Published var added: Bool = false |
|
|
|
@Published var added: Bool = false |
|
|
|
@Published var duration: Double = 0.0 { |
|
|
|
@Published var duration: Double = 0.0 |
|
|
|
didSet { |
|
|
|
|
|
|
|
self.added = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
@Published var timerModel: TimerModel = TimerModel() |
|
|
|
@Published var timerModel: TimerModel = TimerModel() |
|
|
|
|
|
|
|
|
|
|
|
init(preset: Preset) { |
|
|
|
init(preset: Preset) { |
|
|
|
@ -34,6 +92,7 @@ class Customization: ObservableObject { |
|
|
|
func createTimer() -> AbstractTimer { |
|
|
|
func createTimer() -> AbstractTimer { |
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
let countdown = Countdown(context: context) |
|
|
|
let countdown = Countdown(context: context) |
|
|
|
|
|
|
|
countdown.activity = CoreDataRequests.getOrCreateActivity(name: preset.localizedName) |
|
|
|
countdown.duration = self.duration |
|
|
|
countdown.duration = self.duration |
|
|
|
countdown.playableIds = self.timerModel.soundModel.playableIds |
|
|
|
countdown.playableIds = self.timerModel.soundModel.playableIds |
|
|
|
return countdown |
|
|
|
return countdown |
|
|
|
@ -43,11 +102,7 @@ class Customization: ObservableObject { |
|
|
|
|
|
|
|
|
|
|
|
class PresetSelectionModel: ObservableObject { |
|
|
|
class PresetSelectionModel: ObservableObject { |
|
|
|
|
|
|
|
|
|
|
|
// @Published var addedPresets: Set<Preset> = [] |
|
|
|
|
|
|
|
@Published var customizations: [Preset : Customization] = [:] |
|
|
|
@Published var customizations: [Preset : Customization] = [:] |
|
|
|
// @Published var expanded: Set<Preset> = [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @Published var duration: [TimeInterval] = [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init() { |
|
|
|
init() { |
|
|
|
self.customizations = Preset.allCases.reduce(into: [Preset: Customization]()) { $0[$1] = Customization(preset: $1) } |
|
|
|
self.customizations = Preset.allCases.reduce(into: [Preset: Customization]()) { $0[$1] = Customization(preset: $1) } |
|
|
|
@ -57,7 +112,7 @@ class PresetSelectionModel: ObservableObject { |
|
|
|
|
|
|
|
|
|
|
|
struct DurationButtonView: View { |
|
|
|
struct DurationButtonView: View { |
|
|
|
|
|
|
|
|
|
|
|
@StateObject var customization: Customization |
|
|
|
@ObservedObject var customization: Customization |
|
|
|
@State var showDurationSheet: Bool = false |
|
|
|
@State var showDurationSheet: Bool = false |
|
|
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
var body: some View { |
|
|
|
@ -68,8 +123,21 @@ struct DurationButtonView: View { |
|
|
|
Text(customization.duration.hourMinuteSecond) |
|
|
|
Text(customization.duration.hourMinuteSecond) |
|
|
|
} |
|
|
|
} |
|
|
|
.sheet(isPresented: $showDurationSheet) { |
|
|
|
.sheet(isPresented: $showDurationSheet) { |
|
|
|
TimePickerView(duration: self.$customization.duration) |
|
|
|
DurationSheetView(duration: self.$customization.duration) |
|
|
|
.presentationDetents([.height(320.0)]) |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct DurationSheetView: View { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Binding var duration: TimeInterval |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
|
|
|
VStack(alignment: .leading) { |
|
|
|
|
|
|
|
Text("Duration").padding([.leading, .top]) |
|
|
|
|
|
|
|
TimePickerView(duration: self.$duration) |
|
|
|
|
|
|
|
.presentationDetents([.height(240.0)]) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -77,7 +145,7 @@ struct DurationButtonView: View { |
|
|
|
|
|
|
|
|
|
|
|
struct SoundButtonView: View { |
|
|
|
struct SoundButtonView: View { |
|
|
|
|
|
|
|
|
|
|
|
@StateObject var soundModel: SoundModel |
|
|
|
@ObservedObject var soundModel: SoundModel |
|
|
|
@State var showSoundSheet: Bool = false |
|
|
|
@State var showSoundSheet: Bool = false |
|
|
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
var body: some View { |
|
|
|
@ -96,126 +164,71 @@ struct SoundButtonView: View { |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
struct PresetSelectionView: View { |
|
|
|
struct CustomizationRowView: View { |
|
|
|
|
|
|
|
|
|
|
|
@StateObject var model: PresetSelectionModel |
|
|
|
var preset: Preset |
|
|
|
|
|
|
|
@StateObject var customization: Customization |
|
|
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
var body: some View { |
|
|
|
|
|
|
|
HStack { |
|
|
|
List { |
|
|
|
|
|
|
|
ForEach(PresetSection.allCases) { section in |
|
|
|
|
|
|
|
Section(section.localizedName.uppercased()) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ForEach(section.presets.indices, id: \.self) { i in |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let preset = section.presets[i] |
|
|
|
Button { |
|
|
|
let customization = self.model.customizations[preset]! |
|
|
|
self.customization.toggleAdd() |
|
|
|
HStack { |
|
|
|
} label: { |
|
|
|
|
|
|
|
|
|
|
|
Button { |
|
|
|
|
|
|
|
self._addOrRemove(preset: preset) |
|
|
|
|
|
|
|
} label: { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HStack { |
|
|
|
HStack { |
|
|
|
|
|
|
|
|
|
|
|
let added = customization.added |
|
|
|
let image = self.customization.added ? "checkmark.circle.fill" : "circle" |
|
|
|
let image = added ? "checkmark.circle.fill" : "circle" |
|
|
|
Image(systemName: image) |
|
|
|
Image(systemName: image) |
|
|
|
.padding(.trailing, 8.0) |
|
|
|
.padding(.trailing, 8.0) |
|
|
|
.font(.title2) |
|
|
|
.font(.title2) |
|
|
|
.foregroundColor(Color.accentColor) |
|
|
|
.foregroundColor(Color.accentColor) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(preset.localizedName) |
|
|
|
Text(preset.localizedName) |
|
|
|
|
|
|
|
|
|
|
|
Spacer() |
|
|
|
Spacer() |
|
|
|
|
|
|
|
|
|
|
|
VStack(alignment: .trailing) { |
|
|
|
VStack(alignment: .trailing) { |
|
|
|
|
|
|
|
|
|
|
|
DurationButtonView(customization: customization) |
|
|
|
DurationButtonView(customization: self.customization) |
|
|
|
|
|
|
|
|
|
|
|
SoundButtonView(soundModel: customization.timerModel.soundModel) |
|
|
|
SoundButtonView(soundModel: self.customization.timerModel.soundModel) |
|
|
|
}.buttonStyle(.bordered) |
|
|
|
}.buttonStyle(.bordered) |
|
|
|
|
|
|
|
|
|
|
|
}.font(.callout) |
|
|
|
}.font(.callout) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.onChange(of: self.customization.duration) { _ in |
|
|
|
|
|
|
|
self.customization.added = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}.listStyle(.inset) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fileprivate func _addOrRemove(preset: Preset) { |
|
|
|
|
|
|
|
self.model.customizations[preset]?.toggleAdd() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fileprivate func _added(preset: Preset) -> Bool { |
|
|
|
|
|
|
|
return self.model.customizations[preset]?.added ?? false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fileprivate func _image(preset: Preset) -> String { |
|
|
|
|
|
|
|
return self._added(preset: preset) ? "checkmark.circle.fill" : "circle" |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
struct StartView: View { |
|
|
|
struct PresetSelectionView: View { |
|
|
|
|
|
|
|
|
|
|
|
@StateObject var model: PresetSelectionModel = PresetSelectionModel() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@State var showAddScreen: Bool = false |
|
|
|
@ObservedObject var model: PresetSelectionModel |
|
|
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
var body: some View { |
|
|
|
VStack(spacing: 0.5) { |
|
|
|
|
|
|
|
|
|
|
|
List { |
|
|
|
PresetSelectionView(model: self.model).monospaced() |
|
|
|
ForEach(PresetSection.allCases) { section in |
|
|
|
|
|
|
|
Section(section.localizedName.uppercased()) { |
|
|
|
Button { |
|
|
|
|
|
|
|
self.showAddScreen = true |
|
|
|
ForEach(section.presets.indices, id: \.self) { i in |
|
|
|
} label: { |
|
|
|
|
|
|
|
HStack { |
|
|
|
|
|
|
|
Image(systemName: "plus.circle").font(.title) |
|
|
|
|
|
|
|
Text("Create your own").font(.title3) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.padding() |
|
|
|
|
|
|
|
.frame(maxWidth: .infinity) |
|
|
|
|
|
|
|
.foregroundColor(.white) |
|
|
|
|
|
|
|
.background(Color.accentColor) |
|
|
|
|
|
|
|
}.sheet(isPresented: self.$showAddScreen) { |
|
|
|
|
|
|
|
NewCountdownView(isPresented: $showAddScreen) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Button { |
|
|
|
let preset = section.presets[i] |
|
|
|
self._done() |
|
|
|
let customization = self.model.customizations[preset]! |
|
|
|
} label: { |
|
|
|
|
|
|
|
Text("Done") |
|
|
|
CustomizationRowView(preset: preset, customization: customization) |
|
|
|
.font(.title2).fontWeight(.semibold) |
|
|
|
|
|
|
|
.padding() |
|
|
|
|
|
|
|
.frame(maxWidth: .infinity) |
|
|
|
|
|
|
|
// .foregroundColor(.white) |
|
|
|
|
|
|
|
.background(.white) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}.background(.gray) |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}.listStyle(.inset) |
|
|
|
fileprivate func _done() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let customizations = self.model.customizations.values.filter { $0.added } |
|
|
|
|
|
|
|
for custo in customizations { |
|
|
|
|
|
|
|
let _ = custo.createTimer() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
|
|
|
|
do { |
|
|
|
|
|
|
|
try context.save() |
|
|
|
|
|
|
|
} catch { |
|
|
|
|
|
|
|
Logger.error(error) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
@ -275,9 +288,10 @@ struct ConfigurationView: View { |
|
|
|
|
|
|
|
|
|
|
|
struct StartView_Previews: PreviewProvider { |
|
|
|
struct StartView_Previews: PreviewProvider { |
|
|
|
static var previews: some View { |
|
|
|
static var previews: some View { |
|
|
|
StartView() |
|
|
|
StartView(isPresented: .constant(true)) |
|
|
|
ConfigurationView(preset: Preset.blackTea, |
|
|
|
ConfigurationView(preset: Preset.blackTea, |
|
|
|
model: TimerModel(), |
|
|
|
model: TimerModel(), |
|
|
|
duration: .constant(60.0)) |
|
|
|
duration: .constant(60.0)) |
|
|
|
|
|
|
|
DurationSheetView(duration: .constant(1.0)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|