Fixes stuff

release
Laurent 3 years ago
parent 218fa29d8d
commit 31f4d2963f
  1. 4
      LeCountdown.xcodeproj/project.pbxproj
  2. 59
      LeCountdown/Views/Components/View+Extension.swift
  3. 197
      LeCountdown/Views/ContentView.swift
  4. 75
      LeCountdown/Views/Countdown/CountdownFormView.swift
  5. 12
      LeCountdown/Views/Countdown/NewCountdownView.swift
  6. 43
      LeCountdown/Views/DialView.swift
  7. 2
      LeCountdown/Views/NewDataView.swift
  8. 6
      LeCountdown/Views/PresetsView.swift

@ -109,6 +109,7 @@
C4BA2B23299BE82E00CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2AEF2996A11900CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift */; };
C4BA2B25299D35C100CB4FBA /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B24299D35C100CB4FBA /* HomeView.swift */; };
C4BA2B2D299E2DEE00CB4FBA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */; };
C4BA2B2F299E69A000CB4FBA /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */; };
C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; };
C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; };
C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; };
@ -291,6 +292,7 @@
C4BA2B0E299BE61E00CB4FBA /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = "<group>"; };
C4BA2B24299D35C100CB4FBA /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
C4BA2B2C299E2DEE00CB4FBA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = "<group>"; };
C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = "<group>"; };
C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = "<group>"; };
C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
@ -623,6 +625,7 @@
C4F8B165298A9ABB005C86A5 /* SoundImageFormView.swift */,
C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */,
C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */,
C4BA2B2E299E69A000CB4FBA /* View+Extension.swift */,
);
path = Components;
sourceTree = "<group>";
@ -881,6 +884,7 @@
C4BA2B0F299BE61E00CB4FBA /* Interval+CoreDataClass.swift in Sources */,
C4F8B164298A9A92005C86A5 /* AlarmFormView.swift in Sources */,
C40FDB622992985C0042A390 /* TextToSpeechRecorder.swift in Sources */,
C4BA2B2F299E69A000CB4FBA /* View+Extension.swift in Sources */,
C4060DF7297AFEF2003FAB80 /* NewCountdownView.swift in Sources */,
C4060DCC297AE73D003FAB80 /* LeCountdown.xcdatamodeld in Sources */,
C4F8B17C298AC234005C86A5 /* Record+CoreDataClass.swift in Sources */,

@ -0,0 +1,59 @@
//
// View+Extension.swift
// LeCountdown
//
// Created by Laurent Morvillier on 16/02/2023.
//
import Foundation
import SwiftUI
extension View {
/// Focuses next field in sequence, from the given `FocusState`.
/// Requires a currently active focus state and a next field available in the sequence.
///
/// Example usage:
/// ```
/// .onSubmit { self.focusNextField($focusedField) }
/// ```
/// Given that `focusField` is an enum that represents the focusable fields. For example:
/// ```
/// @FocusState private var focusedField: Field?
/// enum Field: Int, Hashable {
/// case name
/// case country
/// case city
/// }
/// ```
func focusNextField<F: RawRepresentable>(_ field: FocusState<F?>.Binding) where F.RawValue == Int {
guard let currentValue = field.wrappedValue else { return }
let nextValue = currentValue.rawValue + 1
if let newValue = F.init(rawValue: nextValue) {
field.wrappedValue = newValue
}
}
/// Focuses previous field in sequence, from the given `FocusState`.
/// Requires a currently active focus state and a previous field available in the sequence.
///
/// Example usage:
/// ```
/// .onSubmit { self.focusNextField($focusedField) }
/// ```
/// Given that `focusField` is an enum that represents the focusable fields. For example:
/// ```
/// @FocusState private var focusedField: Field?
/// enum Field: Int, Hashable {
/// case name
/// case country
/// case city
/// }
/// ```
func focusPreviousField<F: RawRepresentable>(_ field: FocusState<F?>.Binding) where F.RawValue == Int {
guard let currentValue = field.wrappedValue else { return }
let nextValue = currentValue.rawValue - 1
if let newValue = F.init(rawValue: nextValue) {
field.wrappedValue = newValue
}
}
}

@ -42,11 +42,11 @@ class TimerSpot : Identifiable, Equatable {
}
class TimersModel : ObservableObject {
@Published var spots: [TimerSpot] = []
}
//class TimersModel : ObservableObject {
//
// @Published var spots: [TimerSpot] = []
//
//}
struct ContentView<T : AbstractTimer>: View {
@ -55,30 +55,17 @@ struct ContentView<T : AbstractTimer>: View {
@Environment(\.managedObjectContext) private var viewContext
@StateObject fileprivate var model: TimersModel = TimersModel()
// @StateObject fileprivate var model: TimersModel = TimersModel()
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \T.order, ascending: true)],
animation: .default)
private var timers: FetchedResults<T> {
didSet {
self._buildItemsList()
}
}
private var timers: FetchedResults<T>
var coreDataPublisher: NotificationCenter.Publisher { NotificationCenter.default
.publisher(for: .NSManagedObjectContextDidSave, object: viewContext) }
var cloudkitPublisher: NotificationCenter.Publisher { NotificationCenter.default
.publisher(for: Notification.Name(rawValue: "NSPersistentStoreRemoteChangeNotificationOptionKey"), object: viewContext) }
// var coreDataPublisher: NotificationCenter.Publisher { NotificationCenter.default
// .publisher(for: .NSManagedObjectContextDidSave, object: viewContext) }
@State private var isEditing: Bool = false {
didSet {
if self.isEditing == false {
self._saveOrder()
}
self._buildItemsList()
}
}
@State private var isEditing: Bool = false
fileprivate let itemSpacing: CGFloat = 10.0
@ -101,43 +88,41 @@ struct ContentView<T : AbstractTimer>: View {
spacing: itemSpacing
) {
if !self.isEditing {
ForEach(self.model.spots) { spot in
if let timer = spot.timer {
DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width)
.environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)
.environmentObject(boringContext)
} else {
Color.clear
.frame(width: width, height: 80.0)
.cornerRadius(20.0)
}
}
ReorderableForEach(items: Array(timers)) { timer in
} else {
ReorderableForEach(items: self.model.spots) { spot in
if let timer = spot.timer {
DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width)
.environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)
.environmentObject(boringContext)
} else {
Color(white: 0.9)
.frame(width: width, height: 80.0)
.cornerRadius(20.0)
}
} moveAction: { from, to in
self._reorderSpots(from: from, to: to)
}
DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width)
.environment(\.managedObjectContext, viewContext)
.environmentObject(Conductor.maestro)
.environmentObject(boringContext)
} moveAction: { from, to in
self._reorder(from: from, to: to)
}
// if !self.isEditing {
//
//
// }
// else {
//
// ReorderableForEach(items: self.model.spots) { spot in
//
// if let timer = spot.timer {
// DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width)
// .environment(\.managedObjectContext, viewContext)
// .environmentObject(Conductor.maestro)
// .environmentObject(boringContext)
//
// } else {
//
// Color(white: 0.9)
// .frame(width: width, height: 80.0)
// .cornerRadius(20.0)
// }
//
// } moveAction: { from, to in
// self._reorderSpots(from: from, to: to)
// }
// }
}
}.padding(.horizontal, itemSpacing)
@ -188,13 +173,13 @@ struct ContentView<T : AbstractTimer>: View {
// self._buildItemsList()
// }
// })
.onReceive(coreDataPublisher, perform: { _ in
withAnimation {
self._buildItemsList()
}
})
// .onReceive(coreDataPublisher, perform: { _ in
// withAnimation {
// self._buildItemsList()
// }
// })
.onAppear {
self._buildItemsList()
// self._buildItemsList()
self._askPermissions()
}
.onOpenURL { url in
@ -203,32 +188,32 @@ struct ContentView<T : AbstractTimer>: View {
}
fileprivate func _buildItemsList() {
var spots: [TimerSpot] = []
let more: Int = self.isEditing ? 20 : 0 // add 20 empty spots when editing
let count = max(self.timers.count, Int(self.timers.last?.order ?? 0) + 1) + more
for i in 0..<count {
let timer = self.timers.first(where: { $0.order == i })
let spot = TimerSpot(order: Int16(i), timer: timer)
spots.append(spot)
}
self.model.spots = spots
}
// fileprivate func _buildItemsList() {
//
// var spots: [TimerSpot] = []
//
// let more: Int = self.isEditing ? 20 : 0 // add 20 empty spots when editing
//
// let count = max(self.timers.count, Int(self.timers.last?.order ?? 0) + 1) + more
//
// for i in 0..<count {
// let timer = self.timers.first(where: { $0.order == i })
// let spot = TimerSpot(order: Int16(i), timer: timer)
// spots.append(spot)
// }
// self.model.spots = spots
// }
fileprivate func _saveOrder() {
for (i, spot) in self.model.spots.enumerated() {
spot.setOrder(order: Int16(i))
}
do {
try viewContext.save()
} catch {
self.boringContext.error = error
}
}
// fileprivate func _saveOrder() {
// for (i, spot) in self.model.spots.enumerated() {
// spot.setOrder(order: Int16(i))
// }
// do {
// try viewContext.save()
// } catch {
// self.boringContext.error = error
// }
// }
// MARK: - Subviews
@ -249,25 +234,25 @@ struct ContentView<T : AbstractTimer>: View {
// MARK: - Business
fileprivate func _reorderSpots(from: IndexSet, to: Int) {
var spots: [TimerSpot] = self.model.spots
spots.move(fromOffsets: from, toOffset: to)
self.model.spots = spots
}
// fileprivate func _reorder(from: IndexSet, to: Int) {
// var timers: [AbstractTimer] = Array(self.timers)
// timers.move(fromOffsets: from, toOffset: to)
// for (i, countdown) in timers.enumerated() {
// countdown.order = Int16(i)
// }
// do {
// try viewContext.save()
// } catch {
// self.boringContext.error = error
// }
// fileprivate func _reorderSpots(from: IndexSet, to: Int) {
// var spots: [TimerSpot] = self.model.spots
// spots.move(fromOffsets: from, toOffset: to)
// self.model.spots = spots
// }
fileprivate func _reorder(from: IndexSet, to: Int) {
var timers: [AbstractTimer] = Array(self.timers)
timers.move(fromOffsets: from, toOffset: to)
for (i, countdown) in timers.enumerated() {
countdown.order = Int16(i)
}
do {
try viewContext.save()
} catch {
self.boringContext.error = error
}
}
fileprivate func _askPermissions() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .criticalAlert]) { success, error in
print("requestAuthorization > success = \(success), error = \(String(describing: error))")

@ -7,7 +7,16 @@
import SwiftUI
struct CountdownFormView : View {
enum CountdownField: Int, Hashable {
case minutes
case seconds
case name
}
@FocusState private var focusedField: CountdownField?
@EnvironmentObject var model: TimerModel
@ -19,56 +28,59 @@ struct CountdownFormView : View {
var repeatCountBinding: Binding<Int16>
var textFieldIsFocused: FocusState<Bool>.Binding
// var textFieldIsFocused: FocusState<Bool>.Binding
var intervalRepeatBinding: Binding<Int>? = nil
var body: some View {
Form {
Section {
TextField("minutes", text: minutesBinding)
Section("Duration") {
TextField("Minutes", text: minutesBinding)
.keyboardType(.numberPad)
.focused(textFieldIsFocused)
TextField("seconds", text: secondsBinding)
.focused($focusedField, equals: .minutes)
.onSubmit {
self.focusNextField($focusedField)
}
TextField("Seconds", text: secondsBinding)
.keyboardType(.numberPad)
.focused(textFieldIsFocused)
} header: {
HStack {
Text("Duration")
Spacer()
Button {
self._addInterval()
} label: {
Label("add interval", image: "plus")
// Image(systemName: "plus")
.focused($focusedField, equals: .seconds)
.onSubmit {
self.focusNextField($focusedField)
}
}
} footer: {
if intervalRepeatBinding != nil {
HStack {
Stepper("Repeat", value: intervalRepeatBinding!, in: 0...20)
Text(self.intervalRepeatBinding!.wrappedValue.formatted())
TextField("Name (activates tracking)", text: nameBinding)
.focused($focusedField, equals: .name)
.onSubmit {
self.focusNextField($focusedField)
}
}
}
Section(header: Text("Name for tracking the activity")) {
TextField("name", text: nameBinding)
.focused(textFieldIsFocused)
}
SoundImageFormView(
imageBinding: imageBinding,
repeatCountBinding: repeatCountBinding)
.environmentObject(self.model)
}.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button {
self.focusedField = nil
} 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")
}
}
}
}
fileprivate func _addInterval() {
}
}
struct CountdownFormView_Previews: PreviewProvider {
@ -82,7 +94,6 @@ struct CountdownFormView_Previews: PreviewProvider {
nameBinding: .constant(""),
imageBinding: .constant(.pic3),
repeatCountBinding: .constant(2),
textFieldIsFocused: $textFieldIsFocused,
intervalRepeatBinding: .constant(2))
.environmentObject(TimerModel())
}

@ -51,7 +51,7 @@ struct CountdownEditView : View {
var tabSelection: Binding<Int>? = nil
@FocusState private var textFieldIsFocused: Bool
// @FocusState private var textFieldIsFocused: Bool
@FetchRequest(sortDescriptors: [])
private var timers: FetchedResults<AbstractTimer>
@ -86,8 +86,7 @@ struct CountdownEditView : View {
minutesBinding: $minutesString,
nameBinding: $nameString,
imageBinding: $image,
repeatCountBinding: $soundRepeatCount,
textFieldIsFocused: $textFieldIsFocused)
repeatCountBinding: $soundRepeatCount)
.environmentObject(self.model)
.onAppear {
self._onAppear()
@ -139,13 +138,6 @@ struct CountdownEditView : View {
}
}
}
ToolbarItemGroup(placement: .keyboard) {
Button {
textFieldIsFocused = false
} label: {
Image(systemName: "keyboard.chevron.compact.down")
}
}
}
.navigationTitle("Edit countdown")
}

@ -24,7 +24,7 @@ struct DialView: View {
var body: some View {
ZStack {
Image(timer.imageName).resizable().saturation(self.isEditingBinding.wrappedValue ? 0.0 : 1.0)
Image(self.timer.imageName).resizable().saturation(self.isEditingBinding.wrappedValue ? 0.0 : 1.0)
switch self.isEditingBinding.wrappedValue {
case false:
@ -42,23 +42,34 @@ struct DialView: View {
}
}
case true:
VStack {
Spacer()
self._dialView().padding(.horizontal)
Spacer()
}
HStack {
Spacer()
NavigationLink {
self._editView(timer: timer, isPresented: $boringContext.isShowingNewData)
} label: {
Image(systemName: "gearshape.fill")
.font(.system(size: 30.0))
.padding(.horizontal)
.foregroundColor(Color.accentColor)
NavigationLink {
self._editView(timer: timer, isPresented: $boringContext.isShowingNewData)
} label: {
VStack {
Spacer()
self._dialView().padding(.horizontal)
Spacer()
}
}
// VStack {
// Spacer()
// self._dialView().padding(.horizontal)
// Spacer()
// }
//
// HStack {
// Spacer()
// NavigationLink {
// self._editView(timer: timer, isPresented: $boringContext.isShowingNewData)
// } label: {
// Image(systemName: "gearshape.fill")
// .font(.system(size: 30.0))
// .padding(.horizontal)
// .foregroundColor(Color.accentColor)
// }
// }
}
}

@ -43,7 +43,7 @@ struct NewDataView: View {
}
}
.pickerStyle(.segmented)
.padding()
.padding(.horizontal)
TabView(selection: $selection) {
NewCountdownView(isPresented: $isPresented)

@ -10,7 +10,7 @@ import SwiftUI
enum PresetSection: Int, Identifiable, CaseIterable {
var id: Int { return self.rawValue }
case workout
// case workout
case chill
case cooking
case tea
@ -20,7 +20,7 @@ enum PresetSection: Int, Identifiable, CaseIterable {
switch self {
case .cooking: return [.softBoiled, .mediumBoiledEggs, .hardBoiledEggs]
case .tea: return [.greenTea, .blackTea]
case .workout: return [.runningSplits]
// case .workout: return [.runningSplits]
case .chill: return [.nap, .meditation]
case .other: return [.toothbrushing]
}
@ -29,7 +29,7 @@ enum PresetSection: Int, Identifiable, CaseIterable {
var localizedName: String {
switch self {
case .cooking: return NSLocalizedString("Cooking", comment: "")
case .workout: return NSLocalizedString("Workout", comment: "")
// case .workout: return NSLocalizedString("Workout", comment: "")
case .chill: return NSLocalizedString("Chill", comment: "")
case .other: return NSLocalizedString("Other", comment: "")
case .tea: return NSLocalizedString("Tea", comment: "")

Loading…
Cancel
Save