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.
LeCountdown/LeCountdown/Views/StartView.swift

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))
}
}