Various fixes

release
Laurent 3 years ago
parent 92671c60c9
commit 1db60171e1
  1. 0
      LaunchWidget/CountdownView.swift
  2. 4
      LeCountdown.xcodeproj/project.pbxproj
  3. 3
      LeCountdown/AppDelegate.swift
  4. 32
      LeCountdown/ContentView.swift
  5. 65
      LeCountdown/CountdownScheduler.swift
  6. 2
      LeCountdown/LeCountdown.xcdatamodeld/LeCountdown.xcdatamodel/contents
  7. 1
      LeCountdown/LeCountdownApp.swift
  8. 4
      LeCountdown/NSManagedContext+Extensions.swift
  9. 88
      LeCountdown/NewCountdownView.swift

@ -31,7 +31,6 @@
C438C7E02981216300BF3EF9 /* LaunchWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C438C7DB2981216200BF3EF9 /* LaunchWidget.intentdefinition */; };
C438C7E32981216300BF3EF9 /* LaunchWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C438C7CE2981216200BF3EF9 /* LaunchWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C438C7E82981255D00BF3EF9 /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4060DF4297AE9A7003FAB80 /* TimeInterval+Extensions.swift */; };
C438C7EA2981260D00BF3EF9 /* CountdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7E92981260D00BF3EF9 /* CountdownView.swift */; };
C438C7EB2981266F00BF3EF9 /* CountdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7E92981260D00BF3EF9 /* CountdownView.swift */; };
C438C7F229812BB200BF3EF9 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C438C7F129812BB200BF3EF9 /* Intents.framework */; };
C438C7F529812BB200BF3EF9 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7F429812BB200BF3EF9 /* IntentHandler.swift */; };
@ -200,7 +199,6 @@
children = (
C4060DBF297AE73B003FAB80 /* LeCountdownApp.swift */,
C4060DC1297AE73B003FAB80 /* ContentView.swift */,
C438C7E92981260D00BF3EF9 /* CountdownView.swift */,
C4060DF6297AFEF2003FAB80 /* NewCountdownView.swift */,
C438C7C02980228B00BF3EF9 /* CountdownScheduler.swift */,
C4060DC3297AE73D003FAB80 /* Assets.xcassets */,
@ -258,6 +256,7 @@
C438C7D52981216200BF3EF9 /* LaunchWidgetBundle.swift */,
C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */,
C438C7D92981216200BF3EF9 /* LaunchWidget.swift */,
C438C7E92981260D00BF3EF9 /* CountdownView.swift */,
C438C7DB2981216200BF3EF9 /* LaunchWidget.intentdefinition */,
C438C7DC2981216300BF3EF9 /* Assets.xcassets */,
C438C7DE2981216300BF3EF9 /* Info.plist */,
@ -465,7 +464,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C438C7EA2981260D00BF3EF9 /* CountdownView.swift in Sources */,
C4060DC9297AE73D003FAB80 /* Persistence.swift in Sources */,
C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */,
C4060DC2297AE73B003FAB80 /* ContentView.swift in Sources */,

@ -29,7 +29,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
print("willPresent notification")
completionHandler([.banner, .sound])
AppEnvironment.sun.clearNotificationDate()
AppEnvironment.sun.clearNotificationDate(countdownId: notification.request.identifier)
}
}

@ -8,7 +8,30 @@
import SwiftUI
import CoreData
struct CountdownLiveView: View {
@EnvironmentObject var environment: AppEnvironment
@ObservedObject var countdown: Countdown
var body: some View {
VStack {
Text(countdown.name ?? "")
if let date = environment.notificationDates[countdown.stringId] {
Text(date, style: .timer)
} else {
Text(countdown.duration.minuteSecond)
}
}
.font(.title2)
}
}
struct ContentView: View {
@EnvironmentObject var environment: AppEnvironment
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
@ -43,13 +66,15 @@ struct ContentView: View {
Button {
self._launchCountdown(countdown)
} label: {
CountdownView(name: countdown.name, duration: countdown.duration)
CountdownLiveView(countdown: countdown)
.environmentObject(AppEnvironment.sun)
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fit)
.background(Color(red: 0.9, green: 0.95, blue: 1.0))
.cornerRadius(40.0)
}
// Text("ORder = \(countdown.order)")
NavigationLink {
CountdownEditView(countdown: countdown, isPresented: $isShowingNewCountdown)
@ -94,8 +119,7 @@ struct ContentView: View {
fileprivate func _startCountdownIfPossible(url: URL) {
var urlString = url.absoluteString
urlString.trimPrefix("start://countdown/")
let urlString = url.absoluteString
if let countdown = viewContext.object(stringId: urlString) as? Countdown {
self._launchCountdown(countdown)
} else {
@ -105,7 +129,7 @@ struct ContentView: View {
}
fileprivate func _launchCountdown(_ countdown: Countdown) {
CountdownScheduler.master.scheduleIfPossible(duration: countdown.duration) { result in
CountdownScheduler.master.scheduleIfPossible(countdown: countdown) { result in
switch result {
case .success(_):
break

@ -12,24 +12,21 @@ class CountdownScheduler {
static let master = CountdownScheduler()
func scheduleIfPossible(duration: Double, handler: @escaping (Result<Date?, Error>) -> Void) {
UNUserNotificationCenter.current().getPendingNotificationRequests { requests in
self.cancel()
self._scheduleCountdownNotification(duration: duration, handler: handler)
}
func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) {
self.cancelCurrentNotifications(countdown: countdown)
self._scheduleCountdownNotification(countdown: countdown, handler: handler)
}
func cancel() {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
AppEnvironment.sun.clearNotificationDate()
func cancelCurrentNotifications(countdown: Countdown) {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [countdown.stringId])
AppEnvironment.sun.clearNotificationDate(countdownId: countdown.stringId)
}
fileprivate func _scheduleCountdownNotification(duration: Double, handler: @escaping (Result<Date?, Error>) -> Void) {
fileprivate func _scheduleCountdownNotification(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> Void) {
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("It's time!", comment: "")
let duration = countdown.duration
let minutes = duration / 60.0
let minutesLabel = minutes > 1 ? NSLocalizedString("minutes", comment: "") : NSLocalizedString("minute", comment: "")
@ -39,18 +36,20 @@ class CountdownScheduler {
content.sound = UNNotificationSound.defaultCritical
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration, repeats: false)
let request = UNNotificationRequest(identifier: "com.staxriver.countdown", content: content, trigger: trigger)
let request = UNNotificationRequest(identifier: countdown.objectID.uriRepresentation().absoluteString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error {
handler(.failure(error))
print("Scheduling error = \(error)")
} else {
if let date = trigger.nextTriggerDate() {
AppEnvironment.sun.saveNotificationDate(date)
handler(.success(trigger.nextTriggerDate()))
DispatchQueue.main.async {
if let error {
handler(.failure(error))
print("Scheduling error = \(error)")
} else {
let backupDate = Date().addingTimeInterval(duration)
AppEnvironment.sun.saveNotificationDate(backupDate)
if let date = trigger.nextTriggerDate() {
AppEnvironment.sun.saveNotificationDate(date, countdown: countdown)
handler(.success(trigger.nextTriggerDate()))
} else {
let backupDate = Date().addingTimeInterval(duration)
AppEnvironment.sun.saveNotificationDate(backupDate, countdown: countdown)
}
}
}
}
@ -67,29 +66,31 @@ class AppEnvironment : ObservableObject {
static let sun: AppEnvironment = AppEnvironment()
init() {
self.notificationDate = UserDefaults.standard.value(forKey: Key.date.rawValue) as? Date
if let dates = UserDefaults.standard.value(forKey: Key.dates.rawValue) as? [String : Date] {
self.notificationDates = dates
}
}
@Published var notificationDate: Date? = nil {
@Published var notificationDates: [String : Date] = [:] {
didSet {
UserDefaults.standard.set(notificationDate, forKey: Key.date.rawValue)
UserDefaults.standard.set(notificationDates, forKey: Key.dates.rawValue)
}
}
enum Key : String {
case date
case dates
}
var hasNotificationDate: Bool {
return self.notificationDate != nil
}
// var hasNotificationDate: Bool {
// return self.notificationDates != nil
// }
func saveNotificationDate(_ date: Date) {
self.notificationDate = date
func saveNotificationDate(_ date: Date, countdown: Countdown) {
self.notificationDates[countdown.stringId] = date
}
func clearNotificationDate() {
self.notificationDate = nil
func clearNotificationDate(countdownId: String) {
self.notificationDates.removeValue(forKey: countdownId)
}
}

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22A400" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="Countdown" representedClassName="Countdown" syncable="YES" codeGenerationType="class">
<attribute name="duration" attributeType="Double" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="duration" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="image" optional="YES" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="order" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>

@ -17,6 +17,7 @@ struct LeCountdownApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(AppEnvironment.sun)
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}

@ -24,4 +24,8 @@ extension NSManagedObject {
return self.objectID.isTemporaryID
}
var stringId: String {
return self.objectID.uriRepresentation().absoluteString
}
}

@ -7,6 +7,7 @@
import SwiftUI
import CoreData
import WidgetKit
struct NewCountdownView : View {
@ -15,8 +16,9 @@ struct NewCountdownView : View {
@Binding var isPresented: Bool
var body: some View {
CountdownEditView(countdown: Countdown(context: viewContext), isPresented: $isPresented)
CountdownEditView(isPresented: $isPresented)
.environment(\.managedObjectContext, viewContext)
.navigationTitle("New countdown")
}
}
@ -26,7 +28,7 @@ struct CountdownEditView : View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.dismiss) private var dismiss
var countdown: Countdown
var countdown: Countdown? = nil
@Binding var isPresented: Bool
@ -44,6 +46,8 @@ struct CountdownEditView : View {
@FetchRequest(sortDescriptors: [])
private var countdowns: FetchedResults<Countdown>
@State var _isAdding: Bool = false
var body: some View {
NavigationStack {
@ -66,7 +70,7 @@ struct CountdownEditView : View {
Text("Sound")
}
}.onAppear {
self._initDuration()
self._onAppear()
}
.confirmationDialog("", isPresented: $deleteConfirmationShown, actions: {
Button("Yes", role: .destructive) {
@ -82,7 +86,7 @@ struct CountdownEditView : View {
Text(error?.localizedDescription ?? "error")
})
.toolbar {
if self.countdown.isTemporary {
if self._isAdding {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") {
self._cancel()
@ -94,11 +98,13 @@ struct CountdownEditView : View {
self._save()
}
}
ToolbarItem(placement: .bottomBar) {
Button {
self.deleteConfirmationShown = true
} label: {
Image(systemName: "trash")
if !self._isAdding {
ToolbarItem(placement: .bottomBar) {
Button {
self.deleteConfirmationShown = true
} label: {
Image(systemName: "trash")
}
}
}
ToolbarItemGroup(placement: .keyboard) {
@ -109,26 +115,31 @@ struct CountdownEditView : View {
}
}
}
.navigationTitle("New countdown")
.navigationTitle("Edit countdown")
}
}
fileprivate func _initDuration() {
fileprivate func _onAppear() {
let minutes = Int(self.countdown.duration / 60.0)
let seconds = self.countdown.duration - Double(minutes * 60)
self._isAdding = (self.countdown == nil)
if minutes > 0 {
self.minutesString = self._numberFormatter.string(from: NSNumber(value: minutes)) ?? ""
}
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 = self.countdown.name, !name.isEmpty {
self.nameString = name
if seconds > 0 {
self.secondsString = self._numberFormatter.string(from: NSNumber(value: seconds)) ?? ""
}
if let name = countdown.name, !name.isEmpty {
self.nameString = name
}
}
}
@ -150,29 +161,45 @@ struct CountdownEditView : View {
fileprivate func _save() {
let temporary = self.countdown.isTemporary
let cd: Countdown
if let countdown {
cd = countdown
} else {
cd = Countdown(context: viewContext)
}
countdown.duration = self._minutes * 60.0 + self._seconds
if temporary {
countdown.order = Int16(self.countdowns.count)
cd.duration = self._minutes * 60.0 + self._seconds
if self._isAdding {
cd.order = Int16(self.countdowns.count)
}
if !self.nameString.isEmpty {
countdown.name = self.nameString
cd.name = self.nameString
}
self._saveContext()
if temporary {
WidgetCenter.shared.reloadTimelines(ofKind: "com.staxriver.launch-widget") // refreshes the visual of existing widgets
self._popOrDismiss()
}
fileprivate func _popOrDismiss() {
if self._isAdding {
self.isPresented = false
} else {
dismiss()
}
}
fileprivate func _delete() {
viewContext.delete(self.countdown)
guard let countdown else {
return
}
viewContext.delete(countdown)
self._saveContext()
self._popOrDismiss()
}
fileprivate func _saveContext() {
@ -189,5 +216,6 @@ struct CountdownEditView : View {
struct NewCountdownView_Previews: PreviewProvider {
static var previews: some View {
NewCountdownView(isPresented: .constant(true))
.environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)
}
}

Loading…
Cancel
Save