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/NewCountdownView.swift

275 lines
8.1 KiB

//
// NewCountdownView.swift
// LeCountdown
//
// Created by Laurent Morvillier on 20/01/2023.
//
import SwiftUI
import CoreData
import WidgetKit
struct NewCountdownView : View {
@Environment(\.managedObjectContext) private var viewContext
@Binding var isPresented: Bool
var body: some View {
CountdownEditView(isPresented: $isPresented)
.environment(\.managedObjectContext, viewContext)
.navigationTitle("New countdown")
}
}
struct CountdownFormView : View {
var secondsBinding: Binding<String>
var minutesBinding: Binding<String>
var nameBinding: Binding<String>
var textFieldIsFocused: FocusState<Bool>.Binding
var body: some View {
Form {
Section(header: Text("Duration")) {
TextField("minutes", text: minutesBinding)
.keyboardType(.numberPad)
.focused(textFieldIsFocused)
TextField("seconds", text: secondsBinding)
.keyboardType(.numberPad)
.focused(textFieldIsFocused)
}
Section(header: Text("Name for tracking the activity")) {
TextField("name", text: nameBinding)
.focused(textFieldIsFocused)
}
Section(header: Text("Properties")) {
Text("Image")
Text("Sound")
}
}
}
}
struct CountdownEditView : View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.dismiss) private var dismiss
var countdown: Countdown? = nil
@Binding var isPresented: Bool
@State var secondsString: String = ""
@State var minutesString: String = ""
@State var nameString: String = ""
@State var deleteConfirmationShown: Bool = false
@State var activityNameConfirmationShown: Bool = false
@State fileprivate var _rename: Bool? = nil
@State var errorShown: Bool = false
@State var error: Error? = nil
@FocusState private var textFieldIsFocused: Bool
@FetchRequest(sortDescriptors: [])
private var countdowns: FetchedResults<Countdown>
@State var _isAdding: Bool = false
var body: some View {
NavigationStack {
CountdownFormView(secondsBinding: $secondsString,
minutesBinding: $minutesString,
nameBinding: $nameString,
textFieldIsFocused: $textFieldIsFocused)
.onAppear {
self._onAppear()
}
.confirmationDialog("", isPresented: $deleteConfirmationShown, actions: {
Button("Yes", role: .destructive) {
withAnimation {
self._delete()
}
}.keyboardShortcut(.defaultAction)
Button("No", role: .cancel) {}
}, message: {
Text("Do you really want to delete?")
})
.confirmationDialog("", isPresented: $activityNameConfirmationShown, actions: {
Button("Rename") {
self._rename = true
self._save()
}
Button("New activity") {
self._rename = false
self._save()
}
}, message: {
Text("Do you wish to rename or create a new activity")
})
.alert("", isPresented: $errorShown, actions: { }, message: {
Text(error?.localizedDescription ?? "error")
})
.toolbar {
if self._isAdding {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
self._cancel()
}
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
self._save()
}
}
if !self._isAdding {
ToolbarItem(placement: .bottomBar) {
Button {
self.deleteConfirmationShown = true
} label: {
Image(systemName: "trash")
}
}
}
ToolbarItemGroup(placement: .keyboard) {
Button {
textFieldIsFocused = false
} label: {
Image(systemName: "checkmark")
}
}
}
.navigationTitle("Edit countdown")
}
}
fileprivate func _onAppear() {
self._isAdding = (self.countdown == nil)
if let countdown {
let minutes = Int(countdown.duration / 60.0)
let seconds = countdown.duration - Double(minutes * 60)
if minutes > 0 {
self.minutesString = self._numberFormatter.string(from: NSNumber(value: minutes)) ?? ""
}
if seconds > 0 {
self.secondsString = self._numberFormatter.string(from: NSNumber(value: seconds)) ?? ""
}
if let name = countdown.activity?.name, !name.isEmpty {
self.nameString = name
}
}
}
fileprivate let _numberFormatter = NumberFormatter()
fileprivate var _seconds: Double {
return self._numberFormatter.number(from: self.secondsString)?.doubleValue ?? 0.0
}
fileprivate var _minutes: Double {
return self._numberFormatter.number(from: self.minutesString)?.doubleValue ?? 0.0
}
fileprivate func _cancel() {
viewContext.rollback()
self.isPresented = false
}
fileprivate func _save() {
let cd: Countdown
if let countdown {
cd = countdown
} else {
cd = Countdown(context: viewContext)
}
cd.duration = self._minutes * 60.0 + self._seconds
if self._isAdding {
let max = self.countdowns.map { $0.order }.max() ?? 0
cd.order = max + 1
}
if !self.nameString.isEmpty {
if let activity = cd.activity, let currentActivityName = activity.name, self.nameString != currentActivityName {
switch self._rename {
case .none:
self.activityNameConfirmationShown = true
return
case .some(let rename):
if rename {
activity.name = self.nameString
} else {
cd.activity = CoreDataRequests.getOrCreateActivity(name: self.nameString)
}
}
} else {
cd.activity = CoreDataRequests.getOrCreateActivity(name: self.nameString)
}
}
self._saveContext()
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets
self._popOrDismiss()
}
fileprivate func _popOrDismiss() {
if self._isAdding {
self.isPresented = false
} else {
dismiss()
}
}
fileprivate func _delete() {
guard let countdown else {
return
}
viewContext.delete(countdown)
self._saveContext()
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets
self._popOrDismiss()
}
fileprivate func _saveContext() {
do {
try viewContext.save()
} catch {
self.errorShown = true
self.error = error
}
}
}
struct NewCountdownView_Previews: PreviewProvider {
static var previews: some View {
NewCountdownView(isPresented: .constant(true))
.environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
}
}