From 92671c60c91b41017d3fc98be53e65a4659e2453 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 25 Jan 2023 11:59:58 +0100 Subject: [PATCH] Widget working --- LaunchIntents/Info.plist | 24 ++++ LaunchIntents/IntentHandler.swift | 70 +++++++++ LaunchIntents/LaunchIntents.entitlements | 10 ++ LaunchWidget/LaunchWidget.intentdefinition | 10 +- LaunchWidget/LaunchWidget.swift | 38 ++--- LeCountdown.xcodeproj/project.pbxproj | 158 +++++++++++++++++++++ LeCountdown/Info.plist | 7 + LeCountdown/IntentDataProvider.swift | 21 +++ LeCountdown/LeCountdown.entitlements | 10 ++ LeCountdown/Persistence.swift | 16 +++ 10 files changed, 342 insertions(+), 22 deletions(-) create mode 100644 LaunchIntents/Info.plist create mode 100644 LaunchIntents/IntentHandler.swift create mode 100644 LaunchIntents/LaunchIntents.entitlements create mode 100644 LeCountdown/IntentDataProvider.swift create mode 100644 LeCountdown/LeCountdown.entitlements diff --git a/LaunchIntents/Info.plist b/LaunchIntents/Info.plist new file mode 100644 index 0000000..34469a7 --- /dev/null +++ b/LaunchIntents/Info.plist @@ -0,0 +1,24 @@ + + + + + NSExtension + + NSExtensionAttributes + + IntentsRestrictedWhileLocked + + IntentsRestrictedWhileProtectedDataUnavailable + + IntentsSupported + + SelectCountdownIntent + + + NSExtensionPointIdentifier + com.apple.intents-service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).IntentHandler + + + diff --git a/LaunchIntents/IntentHandler.swift b/LaunchIntents/IntentHandler.swift new file mode 100644 index 0000000..64a4941 --- /dev/null +++ b/LaunchIntents/IntentHandler.swift @@ -0,0 +1,70 @@ +// +// IntentHandler.swift +// LaunchIntents +// +// Created by Laurent Morvillier on 25/01/2023. +// + +import Intents + +class IntentHandler: INExtension, SelectCountdownIntentHandling { + +// func resolveCountdown(for intent: SelectCountdownIntent, with completion: @escaping (CountdownPropertiesResolutionResult) -> Void) { +// } + + func resolveCountdown(for intent: SelectCountdownIntent) async -> CountdownPropertiesResolutionResult { + print("***resolveCountdown") + if let countdown = intent.countdown { + return CountdownPropertiesResolutionResult.success(with: countdown) + } else { + return CountdownPropertiesResolutionResult.needsValue() + } + } + +// func provideCountdownOptionsCollection(for intent: SelectCountdownIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { +// +// +// } + + func provideCountdownOptionsCollection(for intent: SelectCountdownIntent) async throws -> INObjectCollection { + print("*** provideCountdownOptionsCollection") + + do { + let countdowns = try IntentDataProvider.main.countdowns() + + let properties: [CountdownProperties] = countdowns.map { countdown in + + let displayName: String + let formattedDuration = countdown.duration.minuteSecond + if let name = countdown.name, !name.isEmpty { + displayName = "\(name) (\(formattedDuration))" + } else { + displayName = formattedDuration + } + + let cp = CountdownProperties(identifier: countdown.objectID.uriRepresentation().absoluteString, display: displayName) + cp.name = countdown.name + cp.duration = NSNumber(value: countdown.duration) + return cp + } + + let collection: INObjectCollection = INObjectCollection(items: properties) + print("*** provide \(properties.count) countdowns") + + return collection + } catch { + print("error = \(error)") + throw error +// completion(nil, error) + } + } + + + override func handler(for intent: INIntent) -> Any { + // This is the default implementation. If you want different objects to handle different intents, + // you can override this and return the handler you want for that particular intent. + + return self + } + +} diff --git a/LaunchIntents/LaunchIntents.entitlements b/LaunchIntents/LaunchIntents.entitlements new file mode 100644 index 0000000..a7c6d47 --- /dev/null +++ b/LaunchIntents/LaunchIntents.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.staxriver.countdown + + + diff --git a/LaunchWidget/LaunchWidget.intentdefinition b/LaunchWidget/LaunchWidget.intentdefinition index 239e495..006d76f 100644 --- a/LaunchWidget/LaunchWidget.intentdefinition +++ b/LaunchWidget/LaunchWidget.intentdefinition @@ -18,7 +18,7 @@ INIntentCategory - start + information INIntentDescriptionID dIYBJB INIntentEligibleForWidgets @@ -45,7 +45,7 @@ INIntentParameterName countdown INIntentParameterObjectType - Countdown + CountdownProperties INIntentParameterObjectTypeNamespace 88xZPY INIntentParameterPromptDialogs @@ -116,20 +116,20 @@ INIntentType Custom INIntentVerb - Start + View INTypes INTypeDisplayName - Countdown + CountdownProperties INTypeDisplayNameID ZTfW1g INTypeLastPropertyTag 102 INTypeName - Countdown + CountdownProperties INTypeProperties diff --git a/LaunchWidget/LaunchWidget.swift b/LaunchWidget/LaunchWidget.swift index 3369c88..1c810c5 100644 --- a/LaunchWidget/LaunchWidget.swift +++ b/LaunchWidget/LaunchWidget.swift @@ -11,27 +11,31 @@ import Intents struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { - SimpleEntry(id: "", name: "Tea", duration: 4 * 60.0, date: Date(), configuration: ConfigurationIntent()) + SimpleEntry(id: "", name: "Tea", duration: 4 * 60.0, date: Date(), configuration: SelectCountdownIntent()) } - func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { - let entry = SimpleEntry(id: "", name: "Tea", duration: 4 * 60.0, date: Date(), configuration: configuration) + func getSnapshot(for configuration: SelectCountdownIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { + + guard let countdown = configuration.countdown, + let identifier = countdown.identifier, + let duration = countdown.duration else { + print("WARNING PLACEHOLDER!") + completion(placeholder(in: context)) + return + } + + let entry = SimpleEntry(id: identifier, name: countdown.name, duration: duration.doubleValue, date: Date(), configuration: configuration) completion(entry) + } - func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { - var entries: [SimpleEntry] = [] + func getTimeline(for configuration: SelectCountdownIntent, in context: Context, completion: @escaping (Timeline) -> ()) { - // Generate a timeline consisting of five entries an hour apart, starting from the current date. - let currentDate = Date() - for hourOffset in 0 ..< 5 { - let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! - let entry = SimpleEntry(id: "", name: "Tea", duration: 4 * 60.0, date: entryDate, configuration: configuration) - entries.append(entry) + getSnapshot(for: configuration, in: context) { entry in + let timeline = Timeline(entries: [entry], policy: .atEnd) + completion(timeline) } - - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) + } } @@ -41,7 +45,7 @@ struct SimpleEntry: TimelineEntry { let duration: Double let date: Date - let configuration: ConfigurationIntent + let configuration: SelectCountdownIntent } struct CountdownWidgetView: View { @@ -84,7 +88,7 @@ struct LaunchWidget: Widget { var body: some WidgetConfiguration { IntentConfiguration(kind: kind, - intent: ConfigurationIntent.self, + intent: SelectCountdownIntent.self, provider: Provider()) { entry in LaunchWidgetEntryView(entry: entry) } @@ -96,7 +100,7 @@ struct LaunchWidget: Widget { struct LaunchWidget_Previews: PreviewProvider { static var previews: some View { - LaunchWidgetEntryView(entry: SimpleEntry(id: "", name: "Tea", duration: 3 * 60.0, date: Date(), configuration: ConfigurationIntent())) + LaunchWidgetEntryView(entry: SimpleEntry(id: "", name: "Tea", duration: 3 * 60.0, date: Date(), configuration: SelectCountdownIntent())) .previewContext(WidgetPreviewContext(family: .systemSmall)) } } diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 2b6f338..b028038 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -33,6 +33,15 @@ 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 */; }; + C438C7F929812BB200BF3EF9 /* LaunchIntents.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C438C7F029812BB200BF3EF9 /* LaunchIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + C438C7FD29812BF700BF3EF9 /* LaunchWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C438C7DB2981216200BF3EF9 /* LaunchWidget.intentdefinition */; }; + C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7FE2981300500BF3EF9 /* IntentDataProvider.swift */; }; + C438C800298130E900BF3EF9 /* IntentDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7FE2981300500BF3EF9 /* IntentDataProvider.swift */; }; + C438C8012981327600BF3EF9 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4060DC8297AE73D003FAB80 /* Persistence.swift */; }; + C438C802298132B900BF3EF9 /* LeCountdown.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */; }; + C438C80529813FB400BF3EF9 /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4060DF4297AE9A7003FAB80 /* TimeInterval+Extensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -57,6 +66,13 @@ remoteGlobalIDString = C438C7CD2981216200BF3EF9; remoteInfo = LaunchWidgetExtension; }; + C438C7F729812BB200BF3EF9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C4060DB4297AE73B003FAB80 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C438C7EF29812BB200BF3EF9; + remoteInfo = LaunchIntents; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -67,6 +83,7 @@ dstSubfolderSpec = 13; files = ( C438C7E32981216300BF3EF9 /* LaunchWidgetExtension.appex in Embed Foundation Extensions */, + C438C7F929812BB200BF3EF9 /* LaunchIntents.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -102,6 +119,13 @@ 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 /* CountdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownView.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 = ""; }; + C438C7F629812BB200BF3EF9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C438C7FE2981300500BF3EF9 /* IntentDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentDataProvider.swift; sourceTree = ""; }; + C438C80329813B2500BF3EF9 /* LaunchIntents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LaunchIntents.entitlements; sourceTree = ""; }; + C438C80429813B3100BF3EF9 /* LeCountdown.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LeCountdown.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -135,6 +159,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C438C7ED29812BB200BF3EF9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C438C7F229812BB200BF3EF9 /* Intents.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -145,6 +177,7 @@ C4060DD5297AE73D003FAB80 /* LeCountdownTests */, C4060DDF297AE73D003FAB80 /* LeCountdownUITests */, C438C7D42981216200BF3EF9 /* LaunchWidget */, + C438C7F329812BB200BF3EF9 /* LaunchIntents */, C438C7CF2981216200BF3EF9 /* Frameworks */, C4060DBD297AE73B003FAB80 /* Products */, ); @@ -157,6 +190,7 @@ C4060DD2297AE73D003FAB80 /* LeCountdownTests.xctest */, C4060DDC297AE73D003FAB80 /* LeCountdownUITests.xctest */, C438C7CE2981216200BF3EF9 /* LaunchWidgetExtension.appex */, + C438C7F029812BB200BF3EF9 /* LaunchIntents.appex */, ); name = Products; sourceTree = ""; @@ -174,6 +208,8 @@ C4060DC8297AE73D003FAB80 /* Persistence.swift */, C438C7C4298024E900BF3EF9 /* NSManagedContext+Extensions.swift */, C4060DF4297AE9A7003FAB80 /* TimeInterval+Extensions.swift */, + C438C7FE2981300500BF3EF9 /* IntentDataProvider.swift */, + C438C80429813B3100BF3EF9 /* LeCountdown.entitlements */, C4060DCD297AE73D003FAB80 /* Info.plist */, C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */, C4060DC5297AE73D003FAB80 /* Preview Content */, @@ -211,6 +247,7 @@ children = ( C438C7D02981216200BF3EF9 /* WidgetKit.framework */, C438C7D22981216200BF3EF9 /* SwiftUI.framework */, + C438C7F129812BB200BF3EF9 /* Intents.framework */, ); name = Frameworks; sourceTree = ""; @@ -228,6 +265,16 @@ path = LaunchWidget; sourceTree = ""; }; + C438C7F329812BB200BF3EF9 /* LaunchIntents */ = { + isa = PBXGroup; + children = ( + C438C7F429812BB200BF3EF9 /* IntentHandler.swift */, + C438C80329813B2500BF3EF9 /* LaunchIntents.entitlements */, + C438C7F629812BB200BF3EF9 /* Info.plist */, + ); + path = LaunchIntents; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -244,6 +291,7 @@ ); dependencies = ( C438C7E22981216300BF3EF9 /* PBXTargetDependency */, + C438C7F829812BB200BF3EF9 /* PBXTargetDependency */, ); name = LeCountdown; productName = LeCountdown; @@ -303,6 +351,23 @@ productReference = C438C7CE2981216200BF3EF9 /* LaunchWidgetExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + C438C7EF29812BB200BF3EF9 /* LaunchIntents */ = { + isa = PBXNativeTarget; + buildConfigurationList = C438C7FA29812BB200BF3EF9 /* Build configuration list for PBXNativeTarget "LaunchIntents" */; + buildPhases = ( + C438C7EC29812BB200BF3EF9 /* Sources */, + C438C7ED29812BB200BF3EF9 /* Frameworks */, + C438C7EE29812BB200BF3EF9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LaunchIntents; + productName = LaunchIntents; + productReference = C438C7F029812BB200BF3EF9 /* LaunchIntents.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -327,6 +392,9 @@ C438C7CD2981216200BF3EF9 = { CreatedOnToolsVersion = 14.2; }; + C438C7EF29812BB200BF3EF9 = { + CreatedOnToolsVersion = 14.2; + }; }; }; buildConfigurationList = C4060DB7297AE73B003FAB80 /* Build configuration list for PBXProject "LeCountdown" */; @@ -346,6 +414,7 @@ C4060DD1297AE73D003FAB80 /* LeCountdownTests */, C4060DDB297AE73D003FAB80 /* LeCountdownUITests */, C438C7CD2981216200BF3EF9 /* LaunchWidgetExtension */, + C438C7EF29812BB200BF3EF9 /* LaunchIntents */, ); }; /* End PBXProject section */ @@ -382,6 +451,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C438C7EE29812BB200BF3EF9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -391,6 +467,7 @@ files = ( C438C7EA2981260D00BF3EF9 /* CountdownView.swift in Sources */, C4060DC9297AE73D003FAB80 /* Persistence.swift in Sources */, + C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */, C4060DC2297AE73B003FAB80 /* ContentView.swift in Sources */, C438C7C12980228B00BF3EF9 /* CountdownScheduler.swift in Sources */, C438C7C929803CA000BF3EF9 /* AppDelegate.swift in Sources */, @@ -433,6 +510,19 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C438C7EC29812BB200BF3EF9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C438C7F529812BB200BF3EF9 /* IntentHandler.swift in Sources */, + C438C80529813FB400BF3EF9 /* TimeInterval+Extensions.swift in Sources */, + C438C802298132B900BF3EF9 /* LeCountdown.xcdatamodeld in Sources */, + C438C8012981327600BF3EF9 /* Persistence.swift in Sources */, + C438C7FD29812BF700BF3EF9 /* LaunchWidget.intentdefinition in Sources */, + C438C800298130E900BF3EF9 /* IntentDataProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -451,6 +541,11 @@ target = C438C7CD2981216200BF3EF9 /* LaunchWidgetExtension */; targetProxy = C438C7E12981216300BF3EF9 /* PBXContainerItemProxy */; }; + C438C7F829812BB200BF3EF9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C438C7EF29812BB200BF3EF9 /* LaunchIntents */; + targetProxy = C438C7F729812BB200BF3EF9 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -574,6 +669,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = LeCountdown/LeCountdown.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"LeCountdown/Preview Content\""; @@ -605,6 +701,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = LeCountdown/LeCountdown.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"LeCountdown/Preview Content\""; @@ -760,6 +857,58 @@ }; name = Release; }; + C438C7FB29812BB200BF3EF9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = LaunchIntents/LaunchIntents.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 526E96RFNP; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LaunchIntents/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LaunchIntents; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown.LaunchIntents; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C438C7FC29812BB200BF3EF9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = LaunchIntents/LaunchIntents.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 526E96RFNP; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LaunchIntents/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LaunchIntents; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown.LaunchIntents; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -808,6 +957,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C438C7FA29812BB200BF3EF9 /* Build configuration list for PBXNativeTarget "LaunchIntents" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C438C7FB29812BB200BF3EF9 /* Debug */, + C438C7FC29812BB200BF3EF9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ diff --git a/LeCountdown/Info.plist b/LeCountdown/Info.plist index 0352adb..4badefc 100644 --- a/LeCountdown/Info.plist +++ b/LeCountdown/Info.plist @@ -2,6 +2,13 @@ + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + NSUserActivityTypes SelectCountdownIntent diff --git a/LeCountdown/IntentDataProvider.swift b/LeCountdown/IntentDataProvider.swift new file mode 100644 index 0000000..8246aec --- /dev/null +++ b/LeCountdown/IntentDataProvider.swift @@ -0,0 +1,21 @@ +// +// IntentDataProvider.swift +// LeCountdown +// +// Created by Laurent Morvillier on 25/01/2023. +// + +import Foundation + +class IntentDataProvider { + + static let main: IntentDataProvider = IntentDataProvider() + + func countdowns() throws -> [Countdown] { + let context = PersistenceController.shared.container.viewContext + let request = Countdown.fetchRequest() + request.sortDescriptors = [NSSortDescriptor(keyPath: (\Countdown.order), ascending: true)] + return try context.fetch(request) + } + +} diff --git a/LeCountdown/LeCountdown.entitlements b/LeCountdown/LeCountdown.entitlements new file mode 100644 index 0000000..a7c6d47 --- /dev/null +++ b/LeCountdown/LeCountdown.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.staxriver.countdown + + + diff --git a/LeCountdown/Persistence.swift b/LeCountdown/Persistence.swift index 82214d6..854fdee 100644 --- a/LeCountdown/Persistence.swift +++ b/LeCountdown/Persistence.swift @@ -33,7 +33,12 @@ struct PersistenceController { let container: NSPersistentCloudKitContainer init(inMemory: Bool = false) { + + let storeURL = URL.storeURL(for: "group.com.staxriver.countdown", databaseName: "group.com.staxriver.countdown") + let storeDescription = NSPersistentStoreDescription(url: storeURL) + container = NSPersistentCloudKitContainer(name: "LeCountdown") + container.persistentStoreDescriptions = [storeDescription] if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } @@ -57,3 +62,14 @@ struct PersistenceController { container.viewContext.automaticallyMergesChangesFromParent = true } } + +fileprivate extension URL { + /// Returns a URL for the given app group and database pointing to the sqlite database. + static func storeURL(for appGroup: String, databaseName: String) -> URL { + guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + fatalError("Shared file container could not be created.") + } + + return fileContainer.appendingPathComponent("\(databaseName).sqlite") + } +}