diff --git a/LaunchIntents/Info.plist b/LaunchIntents/Info.plist index 34469a7..6e03edf 100644 --- a/LaunchIntents/Info.plist +++ b/LaunchIntents/Info.plist @@ -12,7 +12,7 @@ IntentsSupported - SelectCountdownIntent + SelectTimerIntent NSExtensionPointIdentifier diff --git a/LaunchIntents/IntentHandler.swift b/LaunchIntents/IntentHandler.swift index 244740a..9208bad 100644 --- a/LaunchIntents/IntentHandler.swift +++ b/LaunchIntents/IntentHandler.swift @@ -7,38 +7,46 @@ import Intents -class IntentHandler: INExtension, SelectCountdownIntentHandling { +class IntentHandler: INExtension, SelectTimerIntentHandling { - func resolveCountdown(for intent: SelectCountdownIntent) async -> [CountdownPropertiesResolutionResult] { + func resolveTimer(for intent: SelectTimerIntent) async -> [TimerPropertiesResolutionResult] { print("***resolveCountdown") - if let properties = intent.countdown?.first { - return [CountdownPropertiesResolutionResult.success(with: properties)] + if let properties = intent.timer?.first { + return [TimerPropertiesResolutionResult.success(with: properties)] } - return [CountdownPropertiesResolutionResult.needsValue()] + return [TimerPropertiesResolutionResult.needsValue()] } - func provideCountdownOptionsCollection(for intent: SelectCountdownIntent) async throws -> INObjectCollection { + func provideTimerOptionsCollection(for intent: SelectTimerIntent) async throws -> INObjectCollection { print("*** provideCountdownOptionsCollection") do { - let countdowns = try IntentDataProvider.main.countdowns() + let timers = try IntentDataProvider.main.timers() - let properties: [CountdownProperties] = countdowns.map { countdown in + let properties: [TimerProperties] = timers.map { timer in let displayName: String - let formattedDuration = countdown.duration.minuteSecond - if let name = countdown.activity?.name, !name.isEmpty { - displayName = "\(name) (\(formattedDuration))" - } else { - displayName = formattedDuration + switch timer { + case let countdown as Countdown: + let formattedDuration = countdown.duration.minuteSecond + if let name = timer.activity?.name, !name.isEmpty { + displayName = "\(name) (\(formattedDuration))" + } else { + displayName = formattedDuration + } + break + case let stopwatch as Stopwatch: + displayName = stopwatch.name ?? "no name" + default: + displayName = "no name" } - let cp = CountdownProperties(identifier: countdown.objectID.uriRepresentation().absoluteString, display: displayName) + let cp = TimerProperties(identifier: timer.objectID.uriRepresentation().absoluteString, display: displayName) return cp } - let collection: INObjectCollection = INObjectCollection(items: properties) + let collection: INObjectCollection = INObjectCollection(items: properties) print("*** provide \(properties.count) countdowns") return collection diff --git a/LaunchWidget/LaunchWidget.intentdefinition b/LaunchWidget/LaunchWidget.intentdefinition index 8050ce0..36377bc 100644 --- a/LaunchWidget/LaunchWidget.intentdefinition +++ b/LaunchWidget/LaunchWidget.intentdefinition @@ -28,7 +28,7 @@ INIntentLastParameterTag 3 INIntentName - SelectCountdown + SelectTimer INIntentParameters @@ -88,7 +88,7 @@ INIntentParameterCustomDisambiguation INIntentParameterDisplayName - Countdown + Timer INIntentParameterDisplayNameID lE8mOk INIntentParameterDisplayPriority @@ -96,9 +96,9 @@ INIntentParameterFixedSizeArray 1 INIntentParameterName - countdown + timer INIntentParameterObjectType - CountdownProperties + TimerProperties INIntentParameterObjectTypeNamespace 88xZPY INIntentParameterPromptDialogs @@ -119,7 +119,7 @@ INIntentParameterPromptDialogCustom INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${countdown}’. + There are ${count} options matching ‘${timer}’. INIntentParameterPromptDialogFormatStringID gtJyOP INIntentParameterPromptDialogType @@ -129,7 +129,7 @@ INIntentParameterPromptDialogCustom INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${countdown}’? + Just to confirm, you wanted ‘${timer}’? INIntentParameterPromptDialogFormatStringID nntWsg INIntentParameterPromptDialogType @@ -165,7 +165,7 @@ INIntentTitle - Select Countdown + Select Timer INIntentTitleID Dm6sPw INIntentType @@ -178,13 +178,13 @@ INTypeDisplayName - CountdownProperties + TimerProperties INTypeDisplayNameID ZTfW1g INTypeLastPropertyTag 102 INTypeName - CountdownProperties + TimerProperties INTypeProperties diff --git a/LaunchWidget/LaunchWidget.swift b/LaunchWidget/LaunchWidget.swift index ff34545..d715859 100644 --- a/LaunchWidget/LaunchWidget.swift +++ b/LaunchWidget/LaunchWidget.swift @@ -11,33 +11,33 @@ import Intents struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { - SimpleEntry(countdowns: [], date: Date(), configuration: SelectCountdownIntent()) + SimpleEntry(timers: [], date: Date(), configuration: SelectTimerIntent()) } - func getSnapshot(for configuration: SelectCountdownIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { + func getSnapshot(for configuration: SelectTimerIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { - guard let cp = configuration.countdown + guard let cp = configuration.timer else { completion(placeholder(in: context)) return } - let countdowns: [Countdown] = cp.compactMap { + let timers: [AbstractTimer] = cp.compactMap { if let id = $0.identifier { - return IntentDataProvider.main.countdown(id: id) + return IntentDataProvider.main.timer(id: id) } else { return nil } } - let entry = SimpleEntry(countdowns: countdowns, + let entry = SimpleEntry(timers: timers, date: Date(), configuration: configuration) completion(entry) } - func getTimeline(for configuration: SelectCountdownIntent, in context: Context, completion: @escaping (Timeline) -> ()) { + func getTimeline(for configuration: SelectTimerIntent, in context: Context, completion: @escaping (Timeline) -> ()) { getSnapshot(for: configuration, in: context) { entry in let timeline = Timeline(entries: [entry], policy: .atEnd) @@ -48,28 +48,28 @@ struct Provider: IntentTimelineProvider { } struct SimpleEntry: TimelineEntry { - let countdowns: [Countdown] + let timers: [AbstractTimer] let date: Date - let configuration: SelectCountdownIntent + let configuration: SelectTimerIntent } struct CountdownSimpleWidgetView: View { - let countdown: Countdown + let timer: AbstractTimer var body: some View { - SingleCountdownView(countdown: countdown) - .widgetURL(countdown.url) + SingleTimerView(timer: timer) + .widgetURL(timer.url) } } struct CountdownMultiWidgetView: View { - let countdowns: [Countdown] + let timers: [AbstractTimer] var body: some View { - MultiCountdownView(countdowns: countdowns) + MultiCountdownView(timers: timers) } } @@ -93,23 +93,23 @@ struct LaunchWidgetEntryView : View { var body: some View { switch family { case .systemSmall, .accessoryInline: - if let countdown = entry.countdowns.first { - CountdownSimpleWidgetView(countdown: countdown) - .background(Image(countdown.imageName)) + if let timer = entry.timers.first { + CountdownSimpleWidgetView(timer: timer) + .background(Image(timer.imageName)) } else { VoidView() } case .accessoryCircular: - if let countdown = entry.countdowns.first { - LockScreenCountdownView(countdown: countdown) + if let countdown = entry.timers.first { + LockScreenCountdownView(timer: countdown) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.black) } else { VoidView() } case .accessoryRectangular: - if let countdown = entry.countdowns.first { - LockScreenCountdownView(countdown: countdown) + if let timer = entry.timers.first { + LockScreenCountdownView(timer: timer) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.black) .cornerRadius(16.0) @@ -117,7 +117,7 @@ struct LaunchWidgetEntryView : View { VoidView() } default: - MultiCountdownView(countdowns: entry.countdowns) + MultiCountdownView(timers: entry.timers) } } @@ -128,7 +128,7 @@ struct LaunchWidget: Widget { var body: some WidgetConfiguration { IntentConfiguration(kind: kind, - intent: SelectCountdownIntent.self, + intent: SelectTimerIntent.self, provider: Provider()) { entry in LaunchWidgetEntryView(entry: entry) } @@ -143,11 +143,11 @@ struct LaunchWidget_Previews: PreviewProvider { let fake = Countdown.fake(context: PersistenceController.preview.container.viewContext) - LaunchWidgetEntryView(entry: SimpleEntry(countdowns: [fake], date: Date(), configuration: SelectCountdownIntent())) + LaunchWidgetEntryView(entry: SimpleEntry(timers: [fake], date: Date(), configuration: SelectTimerIntent())) .previewContext(WidgetPreviewContext(family: .systemSmall)) - LaunchWidgetEntryView(entry: SimpleEntry(countdowns: [fake, fake, fake, fake], date: Date(), configuration: SelectCountdownIntent())) + LaunchWidgetEntryView(entry: SimpleEntry(timers: [fake, fake, fake, fake], date: Date(), configuration: SelectTimerIntent())) .previewContext(WidgetPreviewContext(family: .systemMedium)) - LaunchWidgetEntryView(entry: SimpleEntry(countdowns: [fake], date: Date(), configuration: SelectCountdownIntent())) + LaunchWidgetEntryView(entry: SimpleEntry(timers: [fake], date: Date(), configuration: SelectTimerIntent())) .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) } diff --git a/LaunchWidget/SingleCountdownView.swift b/LaunchWidget/SingleTimerView.swift similarity index 68% rename from LaunchWidget/SingleCountdownView.swift rename to LaunchWidget/SingleTimerView.swift index ed6e59b..974515e 100644 --- a/LaunchWidget/SingleCountdownView.swift +++ b/LaunchWidget/SingleTimerView.swift @@ -9,18 +9,20 @@ import SwiftUI import WidgetKit import CoreData -struct SingleCountdownView: View { +struct SingleTimerView: View { @Environment(\.widgetFamily) var family: WidgetFamily - var countdown: Countdown + var timer: AbstractTimer var body: some View { VStack { HStack { VStack(alignment: .leading) { - Text(countdown.displayName.uppercased()) - Text(countdown.duration.minuteSecond) + Text(timer.displayName.uppercased()) + if let countdown = timer as? Countdown { + Text(countdown.duration.minuteSecond) + } } Spacer() } @@ -30,7 +32,7 @@ struct SingleCountdownView: View { .monospaced() .foregroundColor(Color.white) .font(self.font) - .widgetURL(countdown.url) + .widgetURL(timer.url) } private var font: Font { @@ -51,17 +53,19 @@ struct LockScreenCountdownView: View { @Environment(\.widgetFamily) var family: WidgetFamily - var countdown: Countdown + var timer: AbstractTimer var body: some View { VStack { - Text(countdown.displayName.uppercased()) - Text(countdown.duration.minuteSecond) + Text(timer.displayName.uppercased()) + if let countdown = timer as? Countdown { + Text(countdown.duration.minuteSecond) + } } .monospaced() .foregroundColor(Color.white) .font(self.font) - .widgetURL(countdown.url) + .widgetURL(timer.url) } private var font: Font { @@ -86,11 +90,11 @@ struct MultiCountdownView: View { GridItem(spacing: 10.0), ] - var countdowns: [Countdown] + var timers: [AbstractTimer] var body: some View { - if countdowns.isEmpty { + if timers.isEmpty { VoidView() } else { @@ -99,22 +103,24 @@ struct MultiCountdownView: View { spacing: 10.0 ) { - ForEach(countdowns) { countdown in + ForEach(timers) { timer in - Link(destination: countdown.url) { + Link(destination: timer.url) { HStack { VStack(alignment: .leading) { Spacer() - Text(countdown.displayName.uppercased()) - Text(countdown.duration.minuteSecond) + Text(timer.displayName.uppercased()) + if let countdown = timer as? Countdown { + Text(countdown.duration.minuteSecond) + } Spacer() } Spacer() } .padding(.horizontal) .font(.callout) - .background(Image(countdown.imageName)) + .background(Image(timer.imageName)) .foregroundColor(.white) .monospaced() .cornerRadius(16.0) @@ -157,10 +163,10 @@ struct MultiCountdownView: View { struct CountdownView_Previews: PreviewProvider { static var previews: some View { - SingleCountdownView(countdown: Countdown.fake(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .systemSmall)).background(.black) - LockScreenCountdownView(countdown: Countdown.fake(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .accessoryRectangular)) - LockScreenCountdownView(countdown: Countdown.fake(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .accessoryCircular)) - MultiCountdownView(countdowns: self.countdowns(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .systemMedium)) + SingleTimerView(timer: Countdown.fake(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .systemSmall)).background(.black) + LockScreenCountdownView(timer: Countdown.fake(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .accessoryRectangular)) + LockScreenCountdownView(timer: Countdown.fake(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .accessoryCircular)) + MultiCountdownView(timers: self.countdowns(context: PersistenceController.preview.container.viewContext)).previewContext(WidgetPreviewContext(family: .systemMedium)) } static func countdowns(context: NSManagedObjectContext) -> [Countdown] { diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index c201cdf..940e84f 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -32,7 +32,7 @@ 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 */; }; - C438C7EB2981266F00BF3EF9 /* SingleCountdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7E92981260D00BF3EF9 /* SingleCountdownView.swift */; }; + C438C7EB2981266F00BF3EF9 /* SingleTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7E92981260D00BF3EF9 /* SingleTimerView.swift */; }; C438C7F229812BB200BF3EF9 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C438C7F129812BB200BF3EF9 /* Intents.framework */; }; C438C7F529812BB200BF3EF9 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7F429812BB200BF3EF9 /* IntentHandler.swift */; }; C438C7F929812BB200BF3EF9 /* LaunchIntents.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C438C7F029812BB200BF3EF9 /* LaunchIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -225,7 +225,7 @@ C438C7DB2981216200BF3EF9 /* LaunchWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = LaunchWidget.intentdefinition; sourceTree = ""; }; C438C7DC2981216300BF3EF9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C438C7DE2981216300BF3EF9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C438C7E92981260D00BF3EF9 /* SingleCountdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleCountdownView.swift; sourceTree = ""; }; + C438C7E92981260D00BF3EF9 /* SingleTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTimerView.swift; sourceTree = ""; }; C438C7F029812BB200BF3EF9 /* LaunchIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = LaunchIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; }; C438C7F129812BB200BF3EF9 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; C438C7F429812BB200BF3EF9 /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; @@ -425,7 +425,7 @@ C438C7D52981216200BF3EF9 /* LaunchWidgetBundle.swift */, C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */, C438C7D92981216200BF3EF9 /* LaunchWidget.swift */, - C438C7E92981260D00BF3EF9 /* SingleCountdownView.swift */, + C438C7E92981260D00BF3EF9 /* SingleTimerView.swift */, C438C7DB2981216200BF3EF9 /* LaunchWidget.intentdefinition */, C438C7DC2981216300BF3EF9 /* Assets.xcassets */, C438C7DE2981216300BF3EF9 /* Info.plist */, @@ -882,7 +882,7 @@ C4F8B1AF298AC451005C86A5 /* Countdown+CoreDataProperties.swift in Sources */, C445FA932987CF280054D761 /* Sound.swift in Sources */, C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */, - C438C7EB2981266F00BF3EF9 /* SingleCountdownView.swift in Sources */, + C438C7EB2981266F00BF3EF9 /* SingleTimerView.swift in Sources */, C438C7D62981216200BF3EF9 /* LaunchWidgetBundle.swift in Sources */, C4F8B18B298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */, C4F8B195298AC288005C86A5 /* Activity+CoreDataClass.swift in Sources */, diff --git a/LeCountdown/Info.plist b/LeCountdown/Info.plist index 0327e78..1e9948b 100644 --- a/LeCountdown/Info.plist +++ b/LeCountdown/Info.plist @@ -8,7 +8,7 @@ NSUserActivityTypes - SelectCountdownIntent + SelectTimerIntent UIApplicationSceneManifest diff --git a/LeCountdown/Widget/IntentDataProvider.swift b/LeCountdown/Widget/IntentDataProvider.swift index 522a1a9..7b7e7fb 100644 --- a/LeCountdown/Widget/IntentDataProvider.swift +++ b/LeCountdown/Widget/IntentDataProvider.swift @@ -12,16 +12,16 @@ class IntentDataProvider { static let main: IntentDataProvider = IntentDataProvider() - func countdowns() throws -> [Countdown] { + func timers() throws -> [AbstractTimer] { let context = PersistenceController.shared.container.viewContext - let request: NSFetchRequest = Countdown.fetchRequest() - request.sortDescriptors = [NSSortDescriptor(keyPath: (\Countdown.order), ascending: true)] + let request: NSFetchRequest = AbstractTimer.fetchRequest() + request.sortDescriptors = [NSSortDescriptor(keyPath: (\AbstractTimer.order), ascending: true)] return try context.fetch(request) } - func countdown(id: String) -> Countdown? { + func timer(id: String) -> AbstractTimer? { let context = PersistenceController.shared.container.viewContext - return context.object(stringId: id) as? Countdown + return context.object(stringId: id) as? AbstractTimer } }