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.
326 lines
9.7 KiB
326 lines
9.7 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 CountdownEditView : View {
|
|
|
|
@Environment(\.managedObjectContext) private var viewContext
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@StateObject var model: TimerModel = TimerModel()
|
|
|
|
var countdown: Countdown? = nil
|
|
var preset: Preset? = nil
|
|
|
|
@Binding var isPresented: Bool
|
|
|
|
@State var secondsString: String = ""
|
|
@State var minutesString: String = ""
|
|
@State var nameString: String = ""
|
|
|
|
@State var soundRepeatCount: Int16 = 0
|
|
@State var image: CoolPic = .pic1
|
|
|
|
@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
|
|
|
|
var tabSelection: Binding<Int>? = nil
|
|
|
|
// @FocusState private var textFieldIsFocused: Bool
|
|
|
|
@FetchRequest(sortDescriptors: [])
|
|
private var timers: FetchedResults<AbstractTimer>
|
|
|
|
@State var _isNewCountdown: Bool = false // false if editing an existing countdown
|
|
@State var _hasLoaded = false
|
|
|
|
@Environment(\.isPresented) var envIsPresented
|
|
|
|
init(isPresented: Binding<Bool>, countdown: Countdown? = nil) {
|
|
_isPresented = isPresented
|
|
self.countdown = countdown
|
|
}
|
|
|
|
init(isPresented: Binding<Bool>, preset: Preset, tabSelection: Binding<Int>) {
|
|
_isPresented = isPresented
|
|
self.preset = preset
|
|
self.tabSelection = tabSelection
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Rectangle()
|
|
.frame(width: 0.0, height: 0.0)
|
|
.onChange(of: envIsPresented) { newValue in
|
|
if !newValue && !self._isNewCountdown {
|
|
self._save() // save when leaving an edit screen
|
|
}
|
|
}
|
|
CountdownFormView(
|
|
secondsBinding: $secondsString,
|
|
minutesBinding: $minutesString,
|
|
nameBinding: $nameString,
|
|
imageBinding: $image,
|
|
repeatCountBinding: $soundRepeatCount)
|
|
.environmentObject(self.model)
|
|
.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._isNewCountdown {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
Button("Cancel") {
|
|
self._cancel()
|
|
}
|
|
}
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button("Save") {
|
|
self._save()
|
|
}
|
|
}
|
|
} else {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button {
|
|
self.deleteConfirmationShown = true
|
|
} label: {
|
|
Image(systemName: "trash")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Edit countdown")
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Business
|
|
|
|
fileprivate func _onAppear() {
|
|
|
|
self._isNewCountdown = (self.countdown == nil)
|
|
if !self._hasLoaded {
|
|
if let countdown {
|
|
self._loadCountdown(countdown)
|
|
} else if let preset {
|
|
self._loadPreset(preset)
|
|
}
|
|
self._hasLoaded = true
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate func _loadPreset(_ preset: Preset) {
|
|
self.nameString = preset.localizedName
|
|
|
|
let nf = NumberFormatter()
|
|
let minutes = Int(preset.duration / 60.0)
|
|
if minutes > 0 {
|
|
self.minutesString = nf.string(from: NSNumber(value: minutes)) ?? ""
|
|
}
|
|
|
|
let seconds = Int(preset.duration) - minutes * 60
|
|
if seconds > 0 {
|
|
self.secondsString = nf.string(from: NSNumber(value: seconds)) ?? ""
|
|
}
|
|
|
|
self.model.group = preset.intervalGroup
|
|
self.model.sounds = preset.sound
|
|
|
|
}
|
|
|
|
fileprivate func _loadCountdown(_ countdown: 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
|
|
}
|
|
|
|
self.model.sounds = countdown.sounds
|
|
|
|
// if let sound = Sound(rawValue: Int(countdown.sound)) {
|
|
// self.sound = sound
|
|
// }
|
|
self.soundRepeatCount = countdown.repeatCount
|
|
|
|
if let image = countdown.image, let coolpic = CoolPic(rawValue: image) {
|
|
self.image = coolpic
|
|
}
|
|
}
|
|
|
|
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._isNewCountdown {
|
|
let max: Int16
|
|
if let maxOrder = self.timers.map({ $0.order }).max() {
|
|
max = maxOrder + 1
|
|
} else {
|
|
max = 0
|
|
}
|
|
cd.order = max
|
|
}
|
|
|
|
cd.image = self.image.rawValue
|
|
cd.setSounds(self.model.sounds)
|
|
|
|
// cd.setPlaylists(self.playlists)
|
|
cd.repeatCount = self.soundRepeatCount
|
|
|
|
if !self.nameString.isEmpty {
|
|
|
|
let trimmed = self.nameString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
if let activity = cd.activity, let currentActivityName = activity.name, trimmed != currentActivityName {
|
|
|
|
switch self._rename {
|
|
case .none:
|
|
self.activityNameConfirmationShown = true
|
|
return
|
|
case .some(let rename):
|
|
if rename {
|
|
activity.name = trimmed
|
|
} else {
|
|
cd.activity = CoreDataRequests.getOrCreateActivity(name: trimmed)
|
|
}
|
|
}
|
|
} else {
|
|
cd.activity = CoreDataRequests.getOrCreateActivity(name: trimmed)
|
|
}
|
|
|
|
}
|
|
|
|
self._saveContext()
|
|
|
|
WidgetCenter.shared.reloadAllTimelines() // refreshes the visual of existing widgets
|
|
|
|
self._popOrDismiss()
|
|
}
|
|
|
|
fileprivate func _popOrDismiss() {
|
|
if self._isNewCountdown {
|
|
self.isPresented = false
|
|
} else {
|
|
dismiss()
|
|
}
|
|
|
|
if self.preset != nil {
|
|
self.tabSelection?.wrappedValue = 1
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
|
Logger.error(error)
|
|
self.errorShown = true
|
|
self.error = error
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct NewCountdownView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
NewCountdownView(isPresented: .constant(true))
|
|
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
|
|
}
|
|
}
|
|
|