You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
10 KiB
366 lines
10 KiB
//
|
|
// StartView.swift
|
|
// LeCountdown
|
|
//
|
|
// Created by Laurent Morvillier on 22/05/2023.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct StartView: View {
|
|
|
|
@Environment(\.managedObjectContext) private var viewContext
|
|
@StateObject var model: PresetSelectionModel = PresetSelectionModel()
|
|
|
|
@Binding var isPresented: Bool
|
|
|
|
@State var showTimerScreen: Bool = false
|
|
@State var showMultiTimerScreen: Bool = false
|
|
@State var showStopwatchScreen: Bool = false
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0.5) {
|
|
|
|
Text("Select some of the predefined timers and customize them, or create your own")
|
|
.fontWeight(.medium)
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
.background(Color.accentColor)
|
|
.foregroundColor(.white)
|
|
|
|
PresetSelectionView(model: self.model).monospaced()
|
|
|
|
VStack(spacing: 4.0) {
|
|
|
|
Button {
|
|
self.showTimerScreen = true
|
|
} label: {
|
|
ImageButton(stringKey: "Create your own timer", systemImage: "timer")
|
|
}.sheet(isPresented: self.$showTimerScreen) {
|
|
NewCountdownView(isPresented: $showTimerScreen, hasRanges: false)
|
|
.environment(\.managedObjectContext, viewContext)
|
|
}
|
|
|
|
Button {
|
|
self.showMultiTimerScreen = true
|
|
} label: {
|
|
ImageButton(stringKey: "Create an advanced timer", detailsStringKey: "Steps & repeat", systemImage: "crown")
|
|
}
|
|
.sheet(isPresented: self.$showMultiTimerScreen) {
|
|
NewCountdownView(isPresented: $showMultiTimerScreen, hasRanges: true)
|
|
.environment(\.managedObjectContext, viewContext)
|
|
}
|
|
|
|
Button {
|
|
self.showStopwatchScreen = true
|
|
} label: {
|
|
ImageButton(stringKey: "Create your own stopwatch", systemImage: "stopwatch")
|
|
}.sheet(isPresented: self.$showStopwatchScreen) {
|
|
NewStopwatchView(isPresented: $showStopwatchScreen)
|
|
.environment(\.managedObjectContext, viewContext)
|
|
}
|
|
|
|
Button {
|
|
self._done()
|
|
} label: {
|
|
Text("Done")
|
|
.font(.title2).fontWeight(.semibold)
|
|
.padding()
|
|
.frame(maxWidth: .infinity)
|
|
.foregroundColor(.white)
|
|
.background(Color.accentColor)
|
|
.cornerRadius(12.0)
|
|
// .padding(.horizontal, 4.0)
|
|
|
|
}
|
|
}
|
|
// .font(.footnote)
|
|
.padding(4.0)
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
}
|
|
|
|
struct ImageButton: View {
|
|
|
|
var stringKey: LocalizedStringKey
|
|
var detailsStringKey: LocalizedStringKey?
|
|
var systemImage: String
|
|
|
|
var body: some View {
|
|
HStack(spacing: 10.0) {
|
|
Image(systemName: self.systemImage)
|
|
.fontWeight(.medium)
|
|
.frame(width: 30.0)
|
|
VStack(alignment: .leading) {
|
|
Text(self.stringKey)
|
|
.fontWeight(.bold)
|
|
if let detailsStringKey {
|
|
Text(detailsStringKey).font(.footnote)
|
|
}
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.foregroundColor(.white)
|
|
.background(Color.accentColor)
|
|
.cornerRadius(12.0)
|
|
}
|
|
|
|
}
|
|
|
|
class Customization: ObservableObject {
|
|
|
|
var preset: Preset
|
|
|
|
@Published var added: Bool = false
|
|
@Published var duration: Double = 0.0
|
|
@Published var timerModel: TimerModel = TimerModel()
|
|
|
|
init(preset: Preset) {
|
|
self.preset = preset
|
|
self.duration = preset.duration
|
|
self.timerModel.soundModel.selectPlaylist(preset.playlist, selected: true)
|
|
self.added = false
|
|
}
|
|
|
|
func toggleAdd() {
|
|
self.added = !self.added
|
|
}
|
|
|
|
func createTimer() -> AbstractTimer {
|
|
let context = PersistenceController.shared.container.viewContext
|
|
let countdown = Countdown(context: context)
|
|
countdown.activity = CoreDataRequests.getOrCreateActivity(name: preset.localizedName)
|
|
|
|
for range in preset.ranges(context: context) {
|
|
countdown.addToTimeRanges(range)
|
|
}
|
|
|
|
countdown.playableIds = self.timerModel.soundModel.playableIds
|
|
return countdown
|
|
}
|
|
|
|
}
|
|
|
|
class PresetSelectionModel: ObservableObject {
|
|
|
|
@Published var customizations: [Preset : Customization] = [:]
|
|
|
|
init() {
|
|
self.customizations = Preset.allCases.reduce(into: [Preset: Customization]()) { $0[$1] = Customization(preset: $1) }
|
|
}
|
|
|
|
}
|
|
|
|
struct DurationButtonView: View {
|
|
|
|
@ObservedObject var customization: Customization
|
|
@State var showDurationSheet: Bool = false
|
|
|
|
var body: some View {
|
|
Button {
|
|
self.showDurationSheet = true
|
|
} label: {
|
|
Image(systemName: "timer")
|
|
Text(customization.duration.hourMinuteSecond)
|
|
}
|
|
.sheet(isPresented: $showDurationSheet) {
|
|
DurationSheetView(duration: self.$customization.duration)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct DurationSheetView: View {
|
|
|
|
@Binding var duration: TimeInterval
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading) {
|
|
NavigationStack {
|
|
TimePickerView(duration: self.$duration)
|
|
.navigationTitle("Duration")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
.presentationDetents([.height(240.0)])
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct SoundButtonView: View {
|
|
|
|
@ObservedObject var soundModel: SoundModel
|
|
@State var showSoundSheet: Bool = false
|
|
|
|
var body: some View {
|
|
|
|
Button {
|
|
self.showSoundSheet = true
|
|
} label: {
|
|
Image(systemName: "music.note")
|
|
Text(self.soundModel.soundSelection(short: true))
|
|
}.sheet(isPresented: $showSoundSheet) {
|
|
NavigationStack {
|
|
PlaylistsView(model: self.soundModel,
|
|
catalog: .ring)
|
|
.navigationTitle("Sounds")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
.presentationDetents([.height(360.0)])
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct CustomizationRowView: View {
|
|
|
|
var preset: Preset
|
|
@StateObject var customization: Customization
|
|
|
|
var body: some View {
|
|
HStack {
|
|
|
|
Button {
|
|
self.customization.toggleAdd()
|
|
} label: {
|
|
|
|
HStack {
|
|
|
|
let image = self.customization.added ? "checkmark.circle.fill" : "circle"
|
|
Image(systemName: image)
|
|
.padding(.trailing, 8.0)
|
|
.font(.title2)
|
|
.foregroundColor(Color.accentColor)
|
|
|
|
Text(preset.localizedName)
|
|
|
|
Spacer()
|
|
|
|
VStack(alignment: .trailing) {
|
|
|
|
DurationButtonView(customization: self.customization)
|
|
|
|
SoundButtonView(soundModel: self.customization.timerModel.soundModel)
|
|
}.buttonStyle(.bordered)
|
|
|
|
}.font(.callout)
|
|
}
|
|
|
|
}
|
|
.onChange(of: self.customization.duration) { _ in
|
|
self.customization.added = true
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
struct PresetSelectionView: View {
|
|
|
|
@ObservedObject var model: PresetSelectionModel
|
|
|
|
var body: some View {
|
|
|
|
List {
|
|
ForEach(PresetSection.allCases) { section in
|
|
Section(section.localizedName.uppercased()) {
|
|
|
|
ForEach(section.presets.indices, id: \.self) { i in
|
|
|
|
let preset = section.presets[i]
|
|
let customization = self.model.customizations[preset]!
|
|
|
|
CustomizationRowView(preset: preset, customization: customization)
|
|
|
|
}
|
|
}
|
|
}
|
|
}.listStyle(.inset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
struct ConfigurationButtonView: View {
|
|
|
|
var preset: Preset
|
|
@Binding var duration: TimeInterval
|
|
|
|
@State var selectedPreset: Preset? = nil
|
|
|
|
var body: some View {
|
|
|
|
Button {
|
|
self.selectedPreset = preset
|
|
} label: {
|
|
Image(systemName: "gearshape.fill")
|
|
}
|
|
.buttonStyle(.plain)
|
|
// .foregroundColor(.gray)
|
|
.sheet(item: $selectedPreset) { preset in
|
|
ConfigurationView(preset: preset,
|
|
model: TimerModel(),
|
|
duration: self.$duration)
|
|
.presentationDetents([.height(320.0)])
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
struct ConfigurationView: View {
|
|
|
|
var preset: Preset
|
|
var model: TimerModel
|
|
@Binding var duration: TimeInterval
|
|
|
|
var body: some View {
|
|
|
|
List {
|
|
SoundLinkView(soundModel: self.model.soundModel,
|
|
catalog: .ring,
|
|
title: NSLocalizedString("Sound", comment: ""))
|
|
|
|
TimePickerView(duration: self.$duration)
|
|
|
|
}
|
|
.listStyle(.plain)
|
|
.onAppear {
|
|
self.model.soundModel.setPlayables([preset.playlist])
|
|
self.duration = preset.duration
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
struct StartView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
StartView(isPresented: .constant(true))
|
|
ConfigurationView(preset: Preset.blackTea,
|
|
model: TimerModel(),
|
|
duration: .constant(60.0))
|
|
DurationSheetView(duration: .constant(1.0))
|
|
}
|
|
}
|
|
|