diff --git a/LaunchIntents/IntentHandler.swift b/LaunchIntents/IntentHandler.swift index c35f7c7..8f124fe 100644 --- a/LaunchIntents/IntentHandler.swift +++ b/LaunchIntents/IntentHandler.swift @@ -7,34 +7,34 @@ import Intents -class IntentHandler: INExtension, SelectTimerIntentHandling, LaunchTimerIntentHandling { - - // MARK: - SelectTimerIntentHandling - - func resolveTimer(for intent: LaunchTimerIntent) async -> TimerIdentifierResolutionResult { - if let timer = intent.timer { - print("resolveTimer(for intent: LaunchTimerIntent) success !") - return .success(with: timer) - } - print("resolveTimer(for intent: LaunchTimerIntent) needsValue") - return .needsValue() - } - - func handle(intent: LaunchTimerIntent) async -> LaunchTimerIntentResponse { - if let timerIdentifier = intent.timer, - let timerId = timerIdentifier.identifier, - let timer = IntentDataProvider.main.timer(id: timerId) { - do { - let _ = try await TimerRouter.performAction(timer: timer) - print("handle(intent: LaunchTimerIntent) success !") - return .success(timer: timerIdentifier) - } catch { - return LaunchTimerIntentResponse(code: LaunchTimerIntentResponseCode(rawValue: 1)!, userActivity: nil) - } - } - print("handle(intent: LaunchTimerIntent) no timer") - return LaunchTimerIntentResponse(code: LaunchTimerIntentResponseCode(rawValue: 1)!, userActivity: nil) - } +class IntentHandler: INExtension, SelectTimerIntentHandling { +// +// // MARK: - SelectTimerIntentHandling +// +// func resolveTimer(for intent: LaunchTimerIntent) async -> TimerIdentifierResolutionResult { +// if let timer = intent.timer { +// print("resolveTimer(for intent: LaunchTimerIntent) success !") +// return .success(with: timer) +// } +// print("resolveTimer(for intent: LaunchTimerIntent) needsValue") +// return .needsValue() +// } +// +// func handle(intent: LaunchTimerIntent) async -> LaunchTimerIntentResponse { +// if let timerIdentifier = intent.timer, +// let timerId = timerIdentifier.identifier, +// let timer = IntentDataProvider.main.timer(id: timerId) { +// do { +// let _ = try await TimerRouter.performAction(timer: timer) +// print("handle(intent: LaunchTimerIntent) success !") +// return .success(timer: timerIdentifier) +// } catch { +// return LaunchTimerIntentResponse(code: LaunchTimerIntentResponseCode(rawValue: 1)!, userActivity: nil) +// } +// } +// print("handle(intent: LaunchTimerIntent) no timer") +// return LaunchTimerIntentResponse(code: LaunchTimerIntentResponseCode(rawValue: 1)!, userActivity: nil) +// } // MARK: - SelectTimerIntentHandling diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 556d8dd..d0632f7 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -75,7 +75,6 @@ C473C30729A91BCC0056B38A /* fr.xcloc in Resources */ = {isa = PBXBuildFile; fileRef = C473C30629A91BCB0056B38A /* fr.xcloc */; }; C473C30829A91BCC0056B38A /* fr.xcloc in Resources */ = {isa = PBXBuildFile; fileRef = C473C30629A91BCB0056B38A /* fr.xcloc */; }; C473C30929A91BCC0056B38A /* fr.xcloc in Resources */ = {isa = PBXBuildFile; fileRef = C473C30629A91BCB0056B38A /* fr.xcloc */; }; - C473C31329A925D50056B38A /* SiriIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C473C31629A925D50056B38A /* SiriIntents.intentdefinition */; }; C473C31829A926F50056B38A /* LaunchWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C473C31C29A926F50056B38A /* LaunchWidget.intentdefinition */; }; C473C31929A926F50056B38A /* LaunchWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C473C31C29A926F50056B38A /* LaunchWidget.intentdefinition */; }; C473C31A29A926F50056B38A /* LaunchWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C473C31C29A926F50056B38A /* LaunchWidget.intentdefinition */; }; @@ -157,7 +156,10 @@ C4BA2B6A29A4BE1800CB4FBA /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B6929A4BE1800CB4FBA /* Date+Extensions.swift */; }; C4BA2B7329A60CF000CB4FBA /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B7229A60CF000CB4FBA /* Shortcut.swift */; }; C4BA2B7929A65C1400CB4FBA /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */; }; - C4E5D66129B64C1B008E7465 /* SiriIntents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C473C31629A925D50056B38A /* SiriIntents.intentdefinition */; }; + C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */; }; + C4E5D66729B73AED008E7465 /* TimerIdentifierAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */; }; + C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; }; + C4E5D66D29B753D7008E7465 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */; }; C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; }; C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; }; C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; }; @@ -306,9 +308,7 @@ C473C2F829A8DC0A0056B38A /* LaunchWidgetAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchWidgetAttributes.swift; sourceTree = ""; }; C473C30229A91BB90056B38A /* en.xcloc */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = en.xcloc; path = Localizations/en.xcloc; sourceTree = ""; }; C473C30629A91BCB0056B38A /* fr.xcloc */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = fr.xcloc; path = Localizations/fr.xcloc; sourceTree = ""; }; - C473C32629AA307D0056B38A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/SiriIntents.intentdefinition; sourceTree = ""; }; C473C32729AA307D0056B38A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/LaunchWidget.intentdefinition; sourceTree = ""; }; - C473C32829AA30890056B38A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SiriIntents.strings; sourceTree = ""; }; C473C32929AA30890056B38A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchWidget.strings; sourceTree = ""; }; C473C32B29AA330E0056B38A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; C473C33829ACDBD70056B38A /* TipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipView.swift; sourceTree = ""; }; @@ -366,7 +366,11 @@ C4BA2B6B29A4C47100CB4FBA /* LeCountdown.0.6.2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.2.xcdatamodel; sourceTree = ""; }; C4BA2B7229A60CF000CB4FBA /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = ""; }; - C4E5D66329B64C7F008E7465 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SiriIntents.strings; sourceTree = ""; }; + C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTimerIntent.swift; sourceTree = ""; }; + C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerIdentifierAppEntity.swift; sourceTree = ""; }; + C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerShortcuts.swift; sourceTree = ""; }; + C4E5D66E29B753D7008E7465 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppShortcuts.strings; sourceTree = ""; }; + C4E5D67029B753DC008E7465 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppShortcuts.strings; sourceTree = ""; }; C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = ""; }; C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = ""; }; C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = ""; }; @@ -476,6 +480,7 @@ C438C8092981DDF800BF3EF9 /* Model */, C445FA8D2987B82E0054D761 /* Sound */, C4BA2B6629A3C49200CB4FBA /* Stats */, + C4E5D66829B73FAB008E7465 /* Intent */, C438C80A2981DE1A00BF3EF9 /* Utils */, C438C8082981DDD200BF3EF9 /* Widget */, C445FA962987D0CF0054D761 /* Sound_Assets */, @@ -484,8 +489,8 @@ C438C80429813B3100BF3EF9 /* LeCountdown.entitlements */, C473C30229A91BB90056B38A /* en.xcloc */, C473C30629A91BCB0056B38A /* fr.xcloc */, - C473C31629A925D50056B38A /* SiriIntents.intentdefinition */, C473C32A29AA330E0056B38A /* Localizable.strings */, + C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */, C4060DCD297AE73D003FAB80 /* Info.plist */, C4060DC5297AE73D003FAB80 /* Preview Content */, ); @@ -679,6 +684,16 @@ path = Stats; sourceTree = ""; }; + C4E5D66829B73FAB008E7465 /* Intent */ = { + isa = PBXGroup; + children = ( + C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */, + C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */, + C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */, + ); + path = Intent; + sourceTree = ""; + }; C4F8B188298AC248005C86A5 /* Generation */ = { isa = PBXGroup; children = ( @@ -914,6 +929,7 @@ C4BA2AE82995ACC200CB4FBA /* Clave_Loop_LLL.wav in Resources */, C445FA952987D01C0054D761 /* train_horn.mp3 in Resources */, C4BA2AE02995ABD200CB4FBA /* HighChords_Loop_River.wav in Resources */, + C4E5D66D29B753D7008E7465 /* AppShortcuts.strings in Resources */, C4060DC4297AE73D003FAB80 /* Assets.xcassets in Resources */, C473C30329A91BB90056B38A /* en.xcloc in Resources */, C4BA2AE62995AC3F00CB4FBA /* Loop_ToneSD_Boavista.wav in Resources */, @@ -987,6 +1003,7 @@ C4BA2B04299A42EF00CB4FBA /* NewDataView.swift in Sources */, C4BA2B4C299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */, + C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */, C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */, C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */, C4BA2B3E299FC86800CB4FBA /* StatsView.swift in Sources */, @@ -1006,7 +1023,6 @@ C473C33C29ACEC4F0056B38A /* Tip.swift in Sources */, C498E5A3298D720600E90DE0 /* TestView.swift in Sources */, C4BA2B06299A8F8D00CB4FBA /* PresetsView.swift in Sources */, - C473C31329A925D50056B38A /* SiriIntents.intentdefinition in Sources */, C473C33929ACDBD70056B38A /* TipView.swift in Sources */, C445FA8F2987B83B0054D761 /* SoundPlayer.swift in Sources */, C4F8B1A7298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */, @@ -1036,6 +1052,7 @@ C4BA2B49299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */, C4F8B17C298AC234005C86A5 /* Record+CoreDataClass.swift in Sources */, C4BA2B5B299FFAB000CB4FBA /* Logger.swift in Sources */, + C4E5D66729B73AED008E7465 /* TimerIdentifierAppEntity.swift in Sources */, C4F8B162298A9A1F005C86A5 /* NewAlarmView.swift in Sources */, C4742B5B298414B000D5D950 /* ImageSelectionView.swift in Sources */, C438C7C5298024E900BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, @@ -1047,6 +1064,7 @@ C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */, C4060DC0297AE73B003FAB80 /* LeCountdownApp.swift in Sources */, C473C31829A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, + C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */, C4742B5729840F6400D5D950 /* CoolPic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1146,7 +1164,6 @@ C4BA2AF82996A4F000CB4FBA /* CustomSound+CoreDataProperties.swift in Sources */, C4F8B199298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */, C438C8012981327600BF3EF9 /* Persistence.swift in Sources */, - C4E5D66129B64C1B008E7465 /* SiriIntents.intentdefinition in Sources */, C473C31A29A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C4BA2B5D299FFAB000CB4FBA /* Logger.swift in Sources */, C473C2FB29A8DC3A0056B38A /* LaunchWidgetAttributes.swift in Sources */, @@ -1192,16 +1209,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - C473C31629A925D50056B38A /* SiriIntents.intentdefinition */ = { - isa = PBXVariantGroup; - children = ( - C473C32629AA307D0056B38A /* Base */, - C473C32829AA30890056B38A /* fr */, - C4E5D66329B64C7F008E7465 /* en */, - ); - name = SiriIntents.intentdefinition; - sourceTree = ""; - }; C473C31C29A926F50056B38A /* LaunchWidget.intentdefinition */ = { isa = PBXVariantGroup; children = ( @@ -1219,6 +1226,15 @@ name = Localizable.strings; sourceTree = ""; }; + C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */ = { + isa = PBXVariantGroup; + children = ( + C4E5D66E29B753D7008E7465 /* fr */, + C4E5D67029B753DC008E7465 /* en */, + ); + name = AppShortcuts.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -1352,6 +1368,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LeCountdown/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Momo; INFOPLIST_KEY_NSAppleMusicUsageDescription = NSAppleMusicUsageDescription; INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1363,7 +1380,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown2; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1385,6 +1402,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LeCountdown/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Momo; INFOPLIST_KEY_NSAppleMusicUsageDescription = NSAppleMusicUsageDescription; INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1396,7 +1414,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown2; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1499,7 +1517,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown2.LaunchWidget; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown.LaunchWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1527,7 +1545,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown2.LaunchWidget; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown.LaunchWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1554,7 +1572,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown2.LaunchIntents; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown.LaunchIntents; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1581,7 +1599,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown2.LaunchIntents; + PRODUCT_BUNDLE_IDENTIFIER = com.staxriver.LeCountdown.LaunchIntents; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/LeCountdown/AppDelegate.swift b/LeCountdown/AppDelegate.swift index 14b651a..984a898 100644 --- a/LeCountdown/AppDelegate.swift +++ b/LeCountdown/AppDelegate.swift @@ -10,6 +10,13 @@ import UIKit class AppDelegate : NSObject, UIApplicationDelegate { + override init() { + super.init() + + NotificationCenter.default.addObserver(self, selector: #selector(_contextDidChange), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil) + + } + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { UNUserNotificationCenter.current().delegate = self @@ -27,6 +34,14 @@ class AppDelegate : NSObject, UIApplicationDelegate { return true } + @objc fileprivate func _contextDidChange(_ notification: Notification) { + TimerShortcuts.updateAppShortcutParameters() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + } extension AppDelegate: UNUserNotificationCenterDelegate { diff --git a/LeCountdown/Base.lproj/SiriIntents.intentdefinition b/LeCountdown/Base.lproj/SiriIntents.intentdefinition deleted file mode 100644 index 2ca7a64..0000000 --- a/LeCountdown/Base.lproj/SiriIntents.intentdefinition +++ /dev/null @@ -1,212 +0,0 @@ - - - - - INEnums - - INIntentDefinitionModelVersion - 1.2 - INIntentDefinitionNamespace - ggxqDx - INIntentDefinitionSystemVersion - 22A400 - INIntentDefinitionToolsBuildVersion - 14C18 - INIntentDefinitionToolsVersion - 14.2 - INIntents - - - INIntentCategory - start - INIntentDescription - Launch timers and stopwatches - INIntentDescriptionID - NdKydA - INIntentLastParameterTag - 2 - INIntentName - LaunchTimer - INIntentParameterCombinations - - timer - - INIntentParameterCombinationIsPrimary - - INIntentParameterCombinationSubtitle - Starts immediately - INIntentParameterCombinationSubtitleID - r4Ikzx - INIntentParameterCombinationSupportsBackgroundExecution - - INIntentParameterCombinationTitle - Launch ${timer} - INIntentParameterCombinationTitleID - JfqtH6 - - - INIntentParameters - - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Timer - INIntentParameterDisplayNameID - wU1mYs - INIntentParameterDisplayPriority - 1 - INIntentParameterName - timer - INIntentParameterObjectType - TimerIdentifier - INIntentParameterObjectTypeNamespace - ggxqDx - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - This is the mandatory prompt for your ${timer} - INIntentParameterPromptDialogFormatStringID - 6ZcaR8 - INIntentParameterPromptDialogType - Primary - - - INIntentParameterSupportsResolution - - INIntentParameterTag - 2 - INIntentParameterType - Object - - - INIntentResponse - - INIntentResponseCodes - - - INIntentResponseCodeFormatString - ${timer} has been launched - INIntentResponseCodeFormatStringID - E3Sz5n - INIntentResponseCodeName - success - INIntentResponseCodeSuccess - - - - INIntentResponseCodeName - failure - - - INIntentResponseLastParameterTag - 2 - INIntentResponseParameters - - - INIntentResponseParameterDisplayName - Timer - INIntentResponseParameterDisplayNameID - Vfpf1t - INIntentResponseParameterDisplayPriority - 1 - INIntentResponseParameterName - timer - INIntentResponseParameterObjectType - TimerIdentifier - INIntentResponseParameterObjectTypeNamespace - ggxqDx - INIntentResponseParameterTag - 2 - INIntentResponseParameterType - Object - - - - INIntentTitle - Launch Timer - INIntentTitleID - nrTIGB - INIntentType - Custom - INIntentVerb - Start - - - INTypes - - - INTypeDisplayName - Timer Identifier - INTypeDisplayNameID - 02RXTq - INTypeLastPropertyTag - 99 - INTypeName - TimerIdentifier - INTypeProperties - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 1 - INTypePropertyName - identifier - INTypePropertyTag - 1 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 2 - INTypePropertyName - displayString - INTypePropertyTag - 2 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 3 - INTypePropertyName - pronunciationHint - INTypePropertyTag - 3 - INTypePropertyType - String - - - INTypePropertyDefault - - INTypePropertyDisplayPriority - 4 - INTypePropertyName - alternativeSpeakableMatches - INTypePropertySupportsMultipleValues - - INTypePropertyTag - 4 - INTypePropertyType - SpeakableString - - - - - - diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 3363bc1..7dbe5a7 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -125,7 +125,7 @@ class Conductor: ObservableObject { // self._launchLiveActivity(countdown: countdown, endDate: date) - self._createTimerIntent(countdown) +// self._createTimerIntent(countdown) Logger.log("countdowns count = \(self.currentCountdowns.count)") @@ -156,7 +156,7 @@ class Conductor: ObservableObject { Conductor.maestro.currentStopwatches[stopwatch.stringId] = now self._launchLiveActivity(stopwatch: stopwatch, start: now) - self._createTimerIntent(stopwatch) +// self._createTimerIntent(stopwatch) } } @@ -233,16 +233,16 @@ class Conductor: ObservableObject { // MARK: - Intent - fileprivate func _createTimerIntent(_ timer: AbstractTimer) { - let intent = LaunchTimerIntent() - - let invocationPhrase = String(format: NSLocalizedString("Launch %@", comment: ""), timer.displayName) - intent.suggestedInvocationPhrase = String(format: invocationPhrase, timer.displayName) - intent.timer = TimerIdentifier(identifier: timer.stringId, display: timer.displayName) - - let interaction = INInteraction(intent: intent, response: nil) - interaction.donate() - } +// fileprivate func _createTimerIntent(_ timer: AbstractTimer) { +// let intent = LaunchTimerIntent() +// +// let invocationPhrase = String(format: NSLocalizedString("Launch %@", comment: ""), timer.displayName) +// intent.suggestedInvocationPhrase = String(format: invocationPhrase, timer.displayName) +// intent.timer = TimerIdentifier(identifier: timer.stringId, display: timer.displayName) +// +// let interaction = INInteraction(intent: intent, response: nil) +// interaction.donate() +// } fileprivate func _scheduleAppRefresh(countdown: Countdown) { let request = BGAppRefreshTaskRequest(identifier: BGTaskIdentifier.refresh.rawValue) diff --git a/LeCountdown/Info.plist b/LeCountdown/Info.plist index 40adea8..ba10e06 100644 --- a/LeCountdown/Info.plist +++ b/LeCountdown/Info.plist @@ -2,6 +2,19 @@ + INAlternativeAppNames + + + + INAlternativeAppName + Momo + + + + INAlternativeAppName + Gogo + + BGTaskSchedulerPermittedIdentifiers com.staxriver.lecountdown.refresh diff --git a/LeCountdown/Intent/StartTimerIntent.swift b/LeCountdown/Intent/StartTimerIntent.swift new file mode 100644 index 0000000..d76dfcd --- /dev/null +++ b/LeCountdown/Intent/StartTimerIntent.swift @@ -0,0 +1,68 @@ +// +// LaunchTimer.swift +// LeCountdown +// +// Created by Laurent Morvillier on 07/03/2023. +// + +import Foundation +import AppIntents +import SwiftUI + +@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) +struct StartTimerIntent: AppIntent, CustomIntentMigratedAppIntent { + + static let intentClassName = "StartTimerIntent" + static var title: LocalizedStringResource = "Launch Timer" + static var description = IntentDescription("Launch timers and stopwatches") + + @Parameter(title: "Timer") + var timer: TimerIdentifierAppEntity? + + static var parameterSummary: some ParameterSummary { + Summary() + } + + func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView { + + let timerIdentifier: TimerIdentifierAppEntity + if let timer { + timerIdentifier = timer + } else { + timerIdentifier = try await $timer.requestDisambiguation(among: timerEntities()) + } + + if let abstractTimer = IntentDataProvider.main.timer(id: timerIdentifier.id) { + do { + let _ = try await TimerRouter.performAction(timer: abstractTimer) + print("perform() success !") + return .result(dialog: "Timer \(abstractTimer.displayName) started") + } catch { + Logger.error(error) + let dialog: IntentDialog = "\(error.localizedDescription)" + return .result(dialog: dialog) + } + } + return .result(dialog: "The timer has not been found in the app") + } + + func timerEntities() -> [TimerIdentifierAppEntity] { + do { + let timers: [AbstractTimer] = try IntentDataProvider.main.timers() + return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } + } catch { + return [] + } + } + +} + +//fileprivate extension IntentDialog { +// static func timerParameterPrompt(timer: TimerIdentifierAppEntity) -> Self { +// "This is the mandatory prompt for your \(timer)" +// } +// static func responseSuccess(timer: TimerIdentifierAppEntity) -> Self { +// "\(timer) has been launched" +// } +//} + diff --git a/LeCountdown/Intent/TimerIdentifierAppEntity.swift b/LeCountdown/Intent/TimerIdentifierAppEntity.swift new file mode 100644 index 0000000..c4ddcb4 --- /dev/null +++ b/LeCountdown/Intent/TimerIdentifierAppEntity.swift @@ -0,0 +1,44 @@ +// +// TimerIdentifierAppEntity.swift +// LeCountdown +// +// Created by Laurent Morvillier on 07/03/2023. +// + +import Foundation +import AppIntents + +@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) +struct TimerIdentifierAppEntity: AppEntity { + static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Timer Identifier") + + struct TimerIdentifierAppEntityQuery: EntityQuery { + func entities(for identifiers: [TimerIdentifierAppEntity.ID]) async throws -> [TimerIdentifierAppEntity] { + + let timers = identifiers.compactMap { IntentDataProvider.main.timer(id: $0) } + return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } + // TODO: return TimerIdentifierAppEntity entities with the specified identifiers here. + } + + func suggestedEntities() async throws -> [TimerIdentifierAppEntity] { + // TODO: return likely TimerIdentifierAppEntity entities here. + // This method is optional; the default implementation returns an empty array. + let timers = try IntentDataProvider.main.timers() + return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } + } + } + + static var defaultQuery = TimerIdentifierAppEntityQuery() + + var id: String // if your identifier is not a String, conform the entity to EntityIdentifierConvertible. + var displayString: String + var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(displayString)") + } + + init(id: String, displayString: String) { + self.id = id + self.displayString = displayString + } +} + diff --git a/LeCountdown/Intent/TimerShortcuts.swift b/LeCountdown/Intent/TimerShortcuts.swift new file mode 100644 index 0000000..4ad21ca --- /dev/null +++ b/LeCountdown/Intent/TimerShortcuts.swift @@ -0,0 +1,21 @@ +// +// TimerShortcuts.swift +// LeCountdown +// +// Created by Laurent Morvillier on 07/03/2023. +// + +import AppIntents + +struct TimerShortcuts: AppShortcutsProvider { + + static var appShortcuts: [AppShortcut] { + AppShortcut(intent: StartTimerIntent(), phrases: [ + "Start \(.applicationName)", + "Launch \(.applicationName)", + "Start \(\.$timer) with \(.applicationName)", + "Launch \(\.$timer) with \(.applicationName)", + "\(.applicationName) \(\.$timer)"]) + } + +} diff --git a/LeCountdown/LeCountdownApp.swift b/LeCountdown/LeCountdownApp.swift index 2070e07..1d7dff2 100644 --- a/LeCountdown/LeCountdownApp.swift +++ b/LeCountdown/LeCountdownApp.swift @@ -9,6 +9,7 @@ import SwiftUI import CoreData import BackgroundTasks import AVFoundation +import Combine @main struct LeCountdownApp: App { @@ -16,7 +17,7 @@ struct LeCountdownApp: App { let persistenceController = PersistenceController.shared @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - + init() { UITabBar.appearance().backgroundColor = UIColor(white: 0.96, alpha: 1.0) @@ -24,6 +25,7 @@ struct LeCountdownApp: App { UIPageControl.appearance().pageIndicatorTintColor = UIColor(white: 0.7, alpha: 1.0) self._registerBackgroundRefreshes() + } @Environment(\.scenePhase) var scenePhase @@ -68,10 +70,6 @@ struct LeCountdownApp: App { } -// fileprivate func _willEnterForegroundNotification() { -// Conductor.maestro.cleanup() -// } - fileprivate func _onAppear() { Logger.log("preferredLanguages = \(String(describing: Locale.preferredLanguages))") diff --git a/LeCountdown/Utils/Preferences.swift b/LeCountdown/Utils/Preferences.swift index 031f063..cbabe78 100644 --- a/LeCountdown/Utils/Preferences.swift +++ b/LeCountdown/Utils/Preferences.swift @@ -14,6 +14,7 @@ enum PreferenceKey: String { case soundDurations case lastSoundPlayed case tips + case timerSiriTips } class Preferences { @@ -22,6 +23,7 @@ class Preferences { @UserDefault(PreferenceKey.soundDurations.rawValue, defaultValue: [:]) static var soundDurations: [Int : TimeInterval] @UserDefault(PreferenceKey.lastSoundPlayed.rawValue, defaultValue: [:]) static var lastSelectedSound: [String : Int] @UserDefault(PreferenceKey.tips.rawValue, defaultValue: nil) static var lastShownTip: Int? + @UserDefault(PreferenceKey.timerSiriTips.rawValue, defaultValue: []) static var timerSiriTips: Set static var hideSilentModeAlerts: Bool { return UserDefaults.standard.bool(forKey: PreferenceKey.showSilentModeAlert.rawValue) diff --git a/LeCountdown/Utils/Tip.swift b/LeCountdown/Utils/Tip.swift index 139998b..739081c 100644 --- a/LeCountdown/Utils/Tip.swift +++ b/LeCountdown/Utils/Tip.swift @@ -8,7 +8,7 @@ import Foundation enum Tip: Int, CaseIterable, Identifiable { - case siri +// case siri case widget var id: Int { self.rawValue } @@ -16,13 +16,13 @@ enum Tip: Int, CaseIterable, Identifiable { var localizedString: String { switch self { case .widget: return NSLocalizedString("You can add widget for your timers and countdowns by modifying your home or lock screen", comment: "") - case .siri: return NSLocalizedString("You can ask Siri to create and launch countdowns and stopwatches", comment: "") +// case .siri: return NSLocalizedString("You can ask Siri to create and launch countdowns and stopwatches", comment: "") } } var pictoName: String { switch self { - case .siri: return "wave.3.right" //"dot.radiowaves.right" +// case .siri: return "wave.3.right" //"dot.radiowaves.right" case .widget: return "app.badge" } } diff --git a/LeCountdown/Views/ContentView.swift b/LeCountdown/Views/ContentView.swift index 824e0fb..108473e 100644 --- a/LeCountdown/Views/ContentView.swift +++ b/LeCountdown/Views/ContentView.swift @@ -8,6 +8,7 @@ import SwiftUI import CoreData import Combine +import _AppIntents_SwiftUI class BoringContext : ObservableObject { @@ -57,6 +58,8 @@ struct ContentView: View { @State private var tipsShown: Bool = false + @State private var siriTipShown: Bool = false + fileprivate let itemSpacing: CGFloat = 10.0 var body: some View { @@ -75,9 +78,11 @@ struct ContentView: View { spacing: itemSpacing ) { - ReorderableForEach(items: Array(timers)) { timer in + ReorderableForEach(items: Array(self.timers)) { timer in - DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width) + DialView(timer: timer, isEditingBinding: self.$isEditing, frameSize: width, handler: { id in + self._handleSiriTips(timerId: id) + }) .environment(\.managedObjectContext, viewContext) .environmentObject(Conductor.maestro) .environmentObject(boringContext) @@ -88,11 +93,14 @@ struct ContentView: View { } }.padding(.horizontal, itemSpacing) - if !self.tipsShown, let tip = Preferences.tipToShow { - TipView(tip: tip) { - self._hideTip(tip) - }.padding() - } +// if !self.tipsShown, let tip = Preferences.tipToShow { +// TipView(tip: tip) { +// self._hideTip(tip) +// }.padding() +// } + + SiriTipView(intent: StartTimerIntent(), isVisible: self.$siriTipShown) + .siriTipViewStyle(SiriTipViewStyle.dark).padding() if !conductor.liveTimers.isEmpty { LiveTimerListView() @@ -188,6 +196,13 @@ struct ContentView: View { } } + fileprivate func _handleSiriTips(timerId: String) { + if !Preferences.timerSiriTips.contains(timerId) { + self.siriTipShown = true + Preferences.timerSiriTips.insert(timerId) + } + } + fileprivate func _performActionIfPossible(url: URL) { // hide new window if launching a timer diff --git a/LeCountdown/Views/DialView.swift b/LeCountdown/Views/DialView.swift index 022c710..d27094d 100644 --- a/LeCountdown/Views/DialView.swift +++ b/LeCountdown/Views/DialView.swift @@ -21,10 +21,14 @@ struct DialView: View { var frameSize: CGFloat + var handler: ((String) -> ())? = nil + var body: some View { ZStack { - Image(self.timer.imageName).resizable().saturation(self.isEditingBinding.wrappedValue ? 0.0 : 1.0) + Image(self.timer.imageName) + .resizable() + .saturation(self.isEditingBinding.wrappedValue ? 0.0 : 1.0) switch self.isEditingBinding.wrappedValue { case false: @@ -76,7 +80,8 @@ struct DialView: View { .frame(width: frameSize, height: 80.0) .cornerRadius(20.0) .alert("Make sure your device is not on silent mode", isPresented: $showSilentModeAlert) { - Button("OK", role: .cancel) { self._launchTimer() + Button("OK", role: .cancel) { + self._launchTimer() } Button("Don't show this again", role: .destructive) { self._launchTimer() @@ -126,6 +131,8 @@ struct DialView: View { fileprivate func _launchTimer() { + self.handler?(self.timer.stringId) + TimerRouter.performAction(timer: self.timer) { result in switch result { case .success: diff --git a/LeCountdown/Views/HomeView.swift b/LeCountdown/Views/HomeView.swift index bbe927e..af26729 100644 --- a/LeCountdown/Views/HomeView.swift +++ b/LeCountdown/Views/HomeView.swift @@ -7,6 +7,7 @@ import SwiftUI import CoreSpotlight +import Combine struct CompactHomeView: View { diff --git a/LeCountdown/en.lproj/AppShortcuts.strings b/LeCountdown/en.lproj/AppShortcuts.strings new file mode 100644 index 0000000..48325fd --- /dev/null +++ b/LeCountdown/en.lproj/AppShortcuts.strings @@ -0,0 +1,12 @@ +/* + AppShortcuts.strings + LeCountdown + + Created by Laurent Morvillier on 07/03/2023. + +*/ +"Start ${applicationName}" = "Start ${applicationName}"; +"Launch ${applicationName}" = "Launch ${applicationName}"; +"Start ${timer} with ${applicationName}" = "Start ${timer} with ${applicationName}"; +"Launch ${timer} with ${applicationName}" = "Launch ${timer} with ${applicationName}"; +"${applicationName} ${timer}" = "${applicationName} ${timer}"; diff --git a/LeCountdown/en.lproj/SiriIntents.strings b/LeCountdown/en.lproj/SiriIntents.strings deleted file mode 100644 index e69de29..0000000 diff --git a/LeCountdown/fr.lproj/AppShortcuts.strings b/LeCountdown/fr.lproj/AppShortcuts.strings new file mode 100644 index 0000000..c62ca4d --- /dev/null +++ b/LeCountdown/fr.lproj/AppShortcuts.strings @@ -0,0 +1,12 @@ +/* + AppShortcuts.strings + LeCountdown + + Created by Laurent Morvillier on 07/03/2023. + +*/ +"Start ${applicationName}" = "Démarre ${applicationName}"; +"Launch ${applicationName}" = "Lance ${applicationName}"; +"Start ${timer} with ${applicationName}" = "Démarre ${timer} avec ${applicationName}"; +"Launch ${timer} with ${applicationName}" = "Lance ${timer} avec ${applicationName}"; +"${applicationName} ${timer}" = "${applicationName} ${timer}"; diff --git a/LeCountdown/fr.lproj/Localizable.strings b/LeCountdown/fr.lproj/Localizable.strings index ae9c0c7..1e475df 100644 --- a/LeCountdown/fr.lproj/Localizable.strings +++ b/LeCountdown/fr.lproj/Localizable.strings @@ -92,7 +92,7 @@ "Hard boiled eggs" = "Oeufs dur"; /* No comment provided by engineer. */ -"Home" = "Kikai"; +"Home" = "Home"; /* No comment provided by engineer. */ "It's time!" = "C'est l'heure !"; @@ -243,3 +243,6 @@ "Launch" = "Démarrer"; "Launch %@" = "Lance %@"; + +"Timer %@ started" = "Le minuteur %@ a démarré"; +"The timer has not been found in the app" = "Le minuteur n'a pas été trouvé dans l'app"; diff --git a/LeCountdown/fr.lproj/SiriIntents.strings b/LeCountdown/fr.lproj/SiriIntents.strings deleted file mode 100644 index 623b095..0000000 --- a/LeCountdown/fr.lproj/SiriIntents.strings +++ /dev/null @@ -1,7 +0,0 @@ - -"Launch Timer" = "Démarrer un compteur"; -"Launch timers and stopwatches" = "Démarre des minuteurs et chronos"; -"Timer" = "Compteur"; -"Starts immediately" = "Démarre immédiatement"; -"Launch ${timer}" = "Démarre ${timer}"; -"${timer} has been launched" = "${timer} a démarré";