From 4eb74fe4d92d636d8fc5910c3f437b3d2a121812 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 24 May 2023 15:32:18 +0200 Subject: [PATCH] StartView integration --- LeCountdown/LeCountdownApp.swift | 23 +- LeCountdown/Sound/Sound.swift | 2 +- LeCountdown/Utils/Preferences.swift | 2 + .../Views/Countdown/NewCountdownView.swift | 52 ---- LeCountdown/Views/PresetsView.swift | 4 +- LeCountdown/Views/StartView.swift | 242 +++++++++--------- 6 files changed, 148 insertions(+), 177 deletions(-) diff --git a/LeCountdown/LeCountdownApp.swift b/LeCountdown/LeCountdownApp.swift index 96c2fb5..e37f648 100644 --- a/LeCountdown/LeCountdownApp.swift +++ b/LeCountdown/LeCountdownApp.swift @@ -19,7 +19,9 @@ struct LeCountdownApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @Environment(\.scenePhase) var scenePhase - + + @State var showStartView: Bool = false + init() { UIPageControl.appearance().currentPageIndicatorTintColor = .systemPink @@ -34,13 +36,11 @@ struct LeCountdownApp: App { var body: some Scene { WindowGroup { - ZStack { - -// StartView() - - CompactHomeView() - .environment(\.managedObjectContext, persistenceController.container.viewContext) - } + CompactHomeView() + .environment(\.managedObjectContext, persistenceController.container.viewContext) + .fullScreenCover(isPresented: $showStartView) { + StartView(isPresented: $showStartView) + } .onAppear { self._onAppear() } @@ -62,10 +62,17 @@ struct LeCountdownApp: App { } + fileprivate func _shouldShowStartView() -> Bool { + let count = persistenceController.container.viewContext.count(entityName: "AbstractTimer") + return count == 0 && Preferences.hasShownStartView == false + } + fileprivate func _onAppear() { Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))") + self.showStartView = self._shouldShowStartView() + self._patch() let containerAvailable = self.isICloudContainerAvailable() Logger.log("isICloudContainerAvailable = \(containerAvailable)") diff --git a/LeCountdown/Sound/Sound.swift b/LeCountdown/Sound/Sound.swift index 7df0a64..01894c4 100644 --- a/LeCountdown/Sound/Sound.swift +++ b/LeCountdown/Sound/Sound.swift @@ -77,7 +77,7 @@ enum Playlist: String, CaseIterable, Identifiable, Localized { case .nature: return NSLocalizedString("Nature", comment: "") case .stephanBodzin: - return "Stephan Bodzin" + return "Boavista" case .custom: return NSLocalizedString("Custom", comment: "") case .relax: diff --git a/LeCountdown/Utils/Preferences.swift b/LeCountdown/Utils/Preferences.swift index 689397b..ec415cc 100644 --- a/LeCountdown/Utils/Preferences.swift +++ b/LeCountdown/Utils/Preferences.swift @@ -8,6 +8,7 @@ import Foundation enum PreferenceKey: String { + case hasShownStartView case installDate case countdowns case pausedCountdowns @@ -38,6 +39,7 @@ class Preferences { @UserDefault(PreferenceKey.cloudKitSchemaInitialized.rawValue, defaultValue: false) static var cloudKitSchemaInitialized: Bool @UserDefault(PreferenceKey.defaultVolume.rawValue, defaultValue: 0.5) static var defaultVolume: Float @UserDefault(PreferenceKey.installDate.rawValue, defaultValue: nil) static var installDate: Date? + @UserDefault(PreferenceKey.hasShownStartView.rawValue, defaultValue: false) static var hasShownStartView: Bool static var hideSilentModeAlerts: Bool { return UserDefaults.standard.bool(forKey: PreferenceKey.showSilentModeAlert.rawValue) diff --git a/LeCountdown/Views/Countdown/NewCountdownView.swift b/LeCountdown/Views/Countdown/NewCountdownView.swift index 48669d9..9c2f7fc 100644 --- a/LeCountdown/Views/Countdown/NewCountdownView.swift +++ b/LeCountdown/Views/Countdown/NewCountdownView.swift @@ -52,11 +52,6 @@ struct CountdownEditView : View { @Binding var isPresented: Bool @State var nameString: String = "" - -// @State var secondsString: String = "" -// @State var minutesString: String = "" -// @State var hoursString: String = "" - @State var duration: TimeInterval = 0.0 @State var soundRepeatCount: Int16 = 0 @@ -74,8 +69,6 @@ struct CountdownEditView : View { @Environment(\.isPresented) var envIsPresented - @State var shouldScrollToTop: Bool = false - @FocusState private var focusedField: CountdownField? init(isPresented: Binding, countdown: Countdown? = nil) { @@ -88,8 +81,6 @@ struct CountdownEditView : View { self.preset = preset } - fileprivate var _formId = "formId" - var body: some View { NavigationStack { @@ -103,24 +94,15 @@ struct CountdownEditView : View { } } -// Form { - -// EmptyView().id("anchor") CountdownFormView( focusedField: _focusedField, nameBinding: $nameString, -// secondsBinding: $secondsString, -// minutesBinding: $minutesString, -// hoursBinding: $hoursString, durationBinding: $duration, imageBinding: $image, repeatCountBinding: $soundRepeatCount) .environmentObject(self.model) -// BasePresetsView { preset in -// self._loadPreset(preset) -// } // } .toolbar { ToolbarItemGroup(placement: .keyboard) { @@ -129,22 +111,6 @@ struct CountdownEditView : View { } label: { Image(systemName: "keyboard.chevron.compact.down") } -// Spacer() -// Button { -// self.focusPreviousField($focusedField) -// } label: { -// Image(systemName: "chevron.up") -// } -// Button { -// self.focusNextField($focusedField) -// } label: { -// Image(systemName: "chevron.down") -// } - } - } - .onChange(of: self.shouldScrollToTop) { newValue in - withAnimation { - reader.scrollTo("anchor") } } @@ -223,27 +189,9 @@ struct CountdownEditView : View { fileprivate func _loadPreset(_ preset: Preset) { self.nameString = preset.localizedName - self.duration = preset.duration - -// let nf = NumberFormatter() -// let minutes = Int(preset.duration / 60.0) -// if minutes > 0 { -// self.minutesString = nf.string(from: NSNumber(value: minutes)) ?? "" -// } else { -// self.minutesString = "" -// } -// -// let seconds = Int(preset.duration) - minutes * 60 -// if seconds > 0 { -// self.secondsString = nf.string(from: NSNumber(value: seconds)) ?? "" -// } else { -// self.secondsString = "" -// } - self.model.group = preset.intervalGroup self.model.soundModel.loadPreset(preset) - self.shouldScrollToTop.toggle() } fileprivate func _loadCountdown(_ countdown: Countdown) { diff --git a/LeCountdown/Views/PresetsView.swift b/LeCountdown/Views/PresetsView.swift index c5a9e62..8103e24 100644 --- a/LeCountdown/Views/PresetsView.swift +++ b/LeCountdown/Views/PresetsView.swift @@ -201,9 +201,9 @@ enum PresetSection: Int, Identifiable, CaseIterable { // case workout case mindfullness - case cooking - case tea case move + case tea + case cooking var presets: [Preset] { switch self { diff --git a/LeCountdown/Views/StartView.swift b/LeCountdown/Views/StartView.swift index d42f8b3..8d18184 100644 --- a/LeCountdown/Views/StartView.swift +++ b/LeCountdown/Views/StartView.swift @@ -7,16 +7,74 @@ 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 { var preset: Preset @Published var added: Bool = false - @Published var duration: Double = 0.0 { - didSet { - self.added = true - } - } + @Published var duration: Double = 0.0 @Published var timerModel: TimerModel = TimerModel() init(preset: Preset) { @@ -34,6 +92,7 @@ class Customization: ObservableObject { func createTimer() -> AbstractTimer { let context = PersistenceController.shared.container.viewContext let countdown = Countdown(context: context) + countdown.activity = CoreDataRequests.getOrCreateActivity(name: preset.localizedName) countdown.duration = self.duration countdown.playableIds = self.timerModel.soundModel.playableIds return countdown @@ -43,11 +102,7 @@ class Customization: ObservableObject { class PresetSelectionModel: ObservableObject { -// @Published var addedPresets: Set = [] @Published var customizations: [Preset : Customization] = [:] -// @Published var expanded: Set = [] - -// @Published var duration: [TimeInterval] = [] init() { self.customizations = Preset.allCases.reduce(into: [Preset: Customization]()) { $0[$1] = Customization(preset: $1) } @@ -57,7 +112,7 @@ class PresetSelectionModel: ObservableObject { struct DurationButtonView: View { - @StateObject var customization: Customization + @ObservedObject var customization: Customization @State var showDurationSheet: Bool = false var body: some View { @@ -68,8 +123,21 @@ struct DurationButtonView: View { Text(customization.duration.hourMinuteSecond) } .sheet(isPresented: $showDurationSheet) { - TimePickerView(duration: self.$customization.duration) - .presentationDetents([.height(320.0)]) + DurationSheetView(duration: self.$customization.duration) + } + } + +} + +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 { - @StateObject var soundModel: SoundModel + @ObservedObject var soundModel: SoundModel @State var showSoundSheet: Bool = false 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 { - - List { - ForEach(PresetSection.allCases) { section in - Section(section.localizedName.uppercased()) { - - ForEach(section.presets.indices, id: \.self) { i in + HStack { - let preset = section.presets[i] - let customization = self.model.customizations[preset]! - HStack { - - Button { - self._addOrRemove(preset: preset) - } label: { + Button { + self.customization.toggleAdd() + } label: { - HStack { + HStack { - let added = customization.added - let image = added ? "checkmark.circle.fill" : "circle" - Image(systemName: image) - .padding(.trailing, 8.0) - .font(.title2) - .foregroundColor(Color.accentColor) + let image = self.customization.added ? "checkmark.circle.fill" : "circle" + Image(systemName: image) + .padding(.trailing, 8.0) + .font(.title2) + .foregroundColor(Color.accentColor) - Text(preset.localizedName) + Text(preset.localizedName) - Spacer() - - VStack(alignment: .trailing) { - - DurationButtonView(customization: customization) - - SoundButtonView(soundModel: customization.timerModel.soundModel) - }.buttonStyle(.bordered) - - }.font(.callout) - } + 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 + } - } - } - } - }.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 { - - @StateObject var model: PresetSelectionModel = PresetSelectionModel() +struct PresetSelectionView: View { - @State var showAddScreen: Bool = false + @ObservedObject var model: PresetSelectionModel 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) - } + + List { + ForEach(PresetSection.allCases) { section in + Section(section.localizedName.uppercased()) { + + ForEach(section.presets.indices, id: \.self) { i in - Button { - self._done() - } label: { - Text("Done") - .font(.title2).fontWeight(.semibold) - .padding() - .frame(maxWidth: .infinity) -// .foregroundColor(.white) - .background(.white) - } + let preset = section.presets[i] + let customization = self.model.customizations[preset]! + + CustomizationRowView(preset: preset, customization: customization) - }.background(.gray) - - } - - fileprivate func _done() { - - let customizations = self.model.customizations.values.filter { $0.added } - for custo in customizations { - let _ = custo.createTimer() - } + } + } + } + }.listStyle(.inset) - 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 { static var previews: some View { - StartView() + StartView(isPresented: .constant(true)) ConfigurationView(preset: Preset.blackTea, model: TimerModel(), duration: .constant(60.0)) + DurationSheetView(duration: .constant(1.0)) } }