StartView integration

main
Laurent 3 years ago
parent e76429b7cb
commit 4eb74fe4d9
  1. 23
      LeCountdown/LeCountdownApp.swift
  2. 2
      LeCountdown/Sound/Sound.swift
  3. 2
      LeCountdown/Utils/Preferences.swift
  4. 52
      LeCountdown/Views/Countdown/NewCountdownView.swift
  5. 4
      LeCountdown/Views/PresetsView.swift
  6. 242
      LeCountdown/Views/StartView.swift

@ -19,7 +19,9 @@ struct LeCountdownApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var scenePhase @Environment(\.scenePhase) var scenePhase
@State var showStartView: Bool = false
init() { init() {
UIPageControl.appearance().currentPageIndicatorTintColor = .systemPink UIPageControl.appearance().currentPageIndicatorTintColor = .systemPink
@ -34,13 +36,11 @@ struct LeCountdownApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ZStack { CompactHomeView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
// StartView() .fullScreenCover(isPresented: $showStartView) {
StartView(isPresented: $showStartView)
CompactHomeView() }
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
.onAppear { .onAppear {
self._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() { fileprivate func _onAppear() {
Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))") Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))")
self.showStartView = self._shouldShowStartView()
self._patch() self._patch()
let containerAvailable = self.isICloudContainerAvailable() let containerAvailable = self.isICloudContainerAvailable()
Logger.log("isICloudContainerAvailable = \(containerAvailable)") Logger.log("isICloudContainerAvailable = \(containerAvailable)")

@ -77,7 +77,7 @@ enum Playlist: String, CaseIterable, Identifiable, Localized {
case .nature: case .nature:
return NSLocalizedString("Nature", comment: "") return NSLocalizedString("Nature", comment: "")
case .stephanBodzin: case .stephanBodzin:
return "Stephan Bodzin" return "Boavista"
case .custom: case .custom:
return NSLocalizedString("Custom", comment: "") return NSLocalizedString("Custom", comment: "")
case .relax: case .relax:

@ -8,6 +8,7 @@
import Foundation import Foundation
enum PreferenceKey: String { enum PreferenceKey: String {
case hasShownStartView
case installDate case installDate
case countdowns case countdowns
case pausedCountdowns case pausedCountdowns
@ -38,6 +39,7 @@ class Preferences {
@UserDefault(PreferenceKey.cloudKitSchemaInitialized.rawValue, defaultValue: false) static var cloudKitSchemaInitialized: Bool @UserDefault(PreferenceKey.cloudKitSchemaInitialized.rawValue, defaultValue: false) static var cloudKitSchemaInitialized: Bool
@UserDefault(PreferenceKey.defaultVolume.rawValue, defaultValue: 0.5) static var defaultVolume: Float @UserDefault(PreferenceKey.defaultVolume.rawValue, defaultValue: 0.5) static var defaultVolume: Float
@UserDefault(PreferenceKey.installDate.rawValue, defaultValue: nil) static var installDate: Date? @UserDefault(PreferenceKey.installDate.rawValue, defaultValue: nil) static var installDate: Date?
@UserDefault(PreferenceKey.hasShownStartView.rawValue, defaultValue: false) static var hasShownStartView: Bool
static var hideSilentModeAlerts: Bool { static var hideSilentModeAlerts: Bool {
return UserDefaults.standard.bool(forKey: PreferenceKey.showSilentModeAlert.rawValue) return UserDefaults.standard.bool(forKey: PreferenceKey.showSilentModeAlert.rawValue)

@ -52,11 +52,6 @@ struct CountdownEditView : View {
@Binding var isPresented: Bool @Binding var isPresented: Bool
@State var nameString: String = "" @State var nameString: String = ""
// @State var secondsString: String = ""
// @State var minutesString: String = ""
// @State var hoursString: String = ""
@State var duration: TimeInterval = 0.0 @State var duration: TimeInterval = 0.0
@State var soundRepeatCount: Int16 = 0 @State var soundRepeatCount: Int16 = 0
@ -74,8 +69,6 @@ struct CountdownEditView : View {
@Environment(\.isPresented) var envIsPresented @Environment(\.isPresented) var envIsPresented
@State var shouldScrollToTop: Bool = false
@FocusState private var focusedField: CountdownField? @FocusState private var focusedField: CountdownField?
init(isPresented: Binding<Bool>, countdown: Countdown? = nil) { init(isPresented: Binding<Bool>, countdown: Countdown? = nil) {
@ -88,8 +81,6 @@ struct CountdownEditView : View {
self.preset = preset self.preset = preset
} }
fileprivate var _formId = "formId"
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@ -103,24 +94,15 @@ struct CountdownEditView : View {
} }
} }
// Form {
// EmptyView().id("anchor")
CountdownFormView( CountdownFormView(
focusedField: _focusedField, focusedField: _focusedField,
nameBinding: $nameString, nameBinding: $nameString,
// secondsBinding: $secondsString,
// minutesBinding: $minutesString,
// hoursBinding: $hoursString,
durationBinding: $duration, durationBinding: $duration,
imageBinding: $image, imageBinding: $image,
repeatCountBinding: $soundRepeatCount) repeatCountBinding: $soundRepeatCount)
.environmentObject(self.model) .environmentObject(self.model)
// BasePresetsView { preset in
// self._loadPreset(preset)
// }
// } // }
.toolbar { .toolbar {
ToolbarItemGroup(placement: .keyboard) { ToolbarItemGroup(placement: .keyboard) {
@ -129,22 +111,6 @@ struct CountdownEditView : View {
} label: { } label: {
Image(systemName: "keyboard.chevron.compact.down") 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) { fileprivate func _loadPreset(_ preset: Preset) {
self.nameString = preset.localizedName self.nameString = preset.localizedName
self.duration = preset.duration 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.group = preset.intervalGroup
self.model.soundModel.loadPreset(preset) self.model.soundModel.loadPreset(preset)
self.shouldScrollToTop.toggle()
} }
fileprivate func _loadCountdown(_ countdown: Countdown) { fileprivate func _loadCountdown(_ countdown: Countdown) {

@ -201,9 +201,9 @@ enum PresetSection: Int, Identifiable, CaseIterable {
// case workout // case workout
case mindfullness case mindfullness
case cooking
case tea
case move case move
case tea
case cooking
var presets: [Preset] { var presets: [Preset] {
switch self { switch self {

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

Loading…
Cancel
Save