diff --git a/HomeAssistant.xcodeproj/project.pbxproj b/HomeAssistant.xcodeproj/project.pbxproj index f84918ab3..93eb9efc3 100644 --- a/HomeAssistant.xcodeproj/project.pbxproj +++ b/HomeAssistant.xcodeproj/project.pbxproj @@ -604,6 +604,8 @@ 422F88192D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */; }; 422F881A2D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */; }; 422F951F2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */; }; + 423179802D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */; }; + 423179812D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */; }; 42333ADB2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; }; 42333ADC2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; }; 42333ADD2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; }; @@ -1928,6 +1930,7 @@ 422F88152D428E8300706A0A /* CustomWidgetActivateAppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomWidgetActivateAppIntent.swift; sourceTree = ""; }; 422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomWidgetPressButtonAppIntent.swift; sourceTree = ""; }; 422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAApplicationShortcutItem.swift; sourceTree = ""; }; + 4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentHaptics.swift; sourceTree = ""; }; 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityRegistryListForDisplay.swift; sourceTree = ""; }; 4235075C2CDB756800A19902 /* HAServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAServices.swift; sourceTree = ""; }; 4239D1802C4FFB75003497FC /* WatchUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchUserDefaults.swift; sourceTree = ""; }; @@ -3868,6 +3871,7 @@ 422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */, 42EF0ACC2D4CDC0C0088C91E /* ResetAllCustomWidgetConfirmationAppIntent.swift */, 42EF0ACF2D4CDFF30088C91E /* UpdateWidgetItemConfirmationStateAppIntent.swift */, + 4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */, ); path = AppIntents; sourceTree = ""; @@ -6917,6 +6921,7 @@ 4223688B2D40F9B7005911E4 /* WidgetCustom.swift in Sources */, 429BA2AF2C800CAB00A50996 /* SFSymbolEntity.swift in Sources */, 420461692C8F29440062E89F /* ControlLight.swift in Sources */, + 423179812D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */, 110E694624E771AB004AA96D /* Color+Hex.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7180,6 +7185,7 @@ 42D3E4B72C5D2C2700444BE6 /* WidgetScriptsAppIntent.swift in Sources */, 4251AA9B2C6B9DBE004CCC9D /* MagicItemCustomizationViewModel.swift in Sources */, 422F88142D4282CD00706A0A /* CustomWidgetToggleAppIntent.swift in Sources */, + 423179802D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */, 4273C48F2C885FB00065A5B4 /* SFSymbolEntity.swift in Sources */, 116D3A442724EFFB00EF5D21 /* OnboardingAuthTokenExchange.swift in Sources */, 429821142CD0DD85005ECD39 /* BluetoothPermissionViewModel.swift in Sources */, diff --git a/Sources/Extensions/AppIntents/Action/PerformAction.swift b/Sources/Extensions/AppIntents/Action/PerformAction.swift index cdcd0d8e4..346cb0567 100644 --- a/Sources/Extensions/AppIntents/Action/PerformAction.swift +++ b/Sources/Extensions/AppIntents/Action/PerformAction.swift @@ -1,5 +1,4 @@ import AppIntents -import AudioToolbox import Foundation import PromiseKit import Shared @@ -46,9 +45,7 @@ struct PerformAction: AppIntent, CustomIntentMigratedAppIntent, PredictableInten } if hapticConfirmation { - // Unfortunately this is the only 'haptics' that works with widgets - // ideally in the future this should use CoreHaptics for a better experience - AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) + AppIntentHaptics.notify() } try await withCheckedThrowingContinuation { continuation in diff --git a/Sources/Extensions/AppIntents/Script/ScriptAppIntent.swift b/Sources/Extensions/AppIntents/Script/ScriptAppIntent.swift index f13d84f3a..e7e0119b0 100644 --- a/Sources/Extensions/AppIntents/Script/ScriptAppIntent.swift +++ b/Sources/Extensions/AppIntents/Script/ScriptAppIntent.swift @@ -1,5 +1,4 @@ import AppIntents -import AudioToolbox import Foundation import PromiseKit import SFSafeSymbols @@ -37,9 +36,7 @@ final class ScriptAppIntent: AppIntent { func perform() async throws -> some IntentResult & ReturnsValue { if hapticConfirmation { - // Unfortunately this is the only 'haptics' that work with widgets - // ideally in the future this should use CoreHaptics for a better experience - AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) + AppIntentHaptics.notify() } let success: Bool = try await withCheckedThrowingContinuation { continuation in diff --git a/Sources/Extensions/Widgets/Custom/AppIntents/AppIntentHaptics.swift b/Sources/Extensions/Widgets/Custom/AppIntents/AppIntentHaptics.swift new file mode 100644 index 000000000..e0962c17a --- /dev/null +++ b/Sources/Extensions/Widgets/Custom/AppIntents/AppIntentHaptics.swift @@ -0,0 +1,10 @@ +import AudioToolbox +import Foundation + +enum AppIntentHaptics { + static func notify() { + // Unfortunately this is the only 'haptics' that work with widgets + // ideally in the future this should use CoreHaptics for a better experience + AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) + } +} diff --git a/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetActivateAppIntent.swift b/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetActivateAppIntent.swift index caac1dca9..c5ad1dd7a 100644 --- a/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetActivateAppIntent.swift +++ b/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetActivateAppIntent.swift @@ -10,6 +10,7 @@ struct CustomWidgetActivateAppIntent: AppIntent { static var title: LocalizedStringResource = "Activate" static var isDiscoverable: Bool = false + // No translation needed below, this is not a discoverable intent @Parameter(title: "Server") var serverId: String? @Parameter(title: "Domain") @@ -27,6 +28,7 @@ struct CustomWidgetActivateAppIntent: AppIntent { }), let connection = Current.api(for: server)?.connection else { return .result() } + AppIntentHaptics.notify() guard let request: HATypedRequest = { switch domain { diff --git a/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetPressButtonAppIntent.swift b/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetPressButtonAppIntent.swift index ae8ecd563..ab3026d94 100644 --- a/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetPressButtonAppIntent.swift +++ b/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetPressButtonAppIntent.swift @@ -8,6 +8,7 @@ struct CustomWidgetPressButtonAppIntent: AppIntent { static var title: LocalizedStringResource = "Toggle" static var isDiscoverable: Bool = false + // No translation needed below, this is not a discoverable intent @Parameter(title: "Server") var serverId: String? @Parameter(title: "Domain") @@ -25,6 +26,7 @@ struct CustomWidgetPressButtonAppIntent: AppIntent { }), let connection = Current.api(for: server)?.connection else { return .result() } + AppIntentHaptics.notify() await withCheckedContinuation { continuation in connection.send(.pressButton(domain: domain, entityId: entityId)).promise.pipe { result in switch result { diff --git a/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetToggleAppIntent.swift b/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetToggleAppIntent.swift index 052e699bd..ac8dcad7f 100644 --- a/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetToggleAppIntent.swift +++ b/Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetToggleAppIntent.swift @@ -9,23 +9,28 @@ struct CustomWidgetToggleAppIntent: AppIntent { static var title: LocalizedStringResource = "Toggle" static var isDiscoverable: Bool = false + // No translation needed below, this is not a discoverable intent @Parameter(title: "Server") var serverId: String? @Parameter(title: "Domain") var domain: String? @Parameter(title: "Entity ID") var entityId: String? + @Parameter(title: "Is widget showing states?") + var widgetShowingStates: Bool? func perform() async throws -> some IntentResult { guard let serverId, let domainString = domain, let domain = Domain(rawValue: domainString), let entityId, + let widgetShowingStates, let server = Current.servers.all.first(where: { server in server.identifier.rawValue == serverId }), let connection = Current.api(for: server)?.connection else { return .result() } + AppIntentHaptics.notify() await withCheckedContinuation { continuation in connection.send(.toggleDomain(domain: domain, entityId: entityId)).promise.pipe { result in switch result { @@ -47,6 +52,12 @@ struct CustomWidgetToggleAppIntent: AppIntent { } } _ = try await ResetAllCustomWidgetConfirmationAppIntent().perform() + if widgetShowingStates { + /* Since when you toggle an entity not always it reflects the new state right away + and at the same time push notifications to update widgets are currently not working reliably + in iOS, this delay is out best effort for the user to see the correct state after finishing the interaction */ + try await Task.sleep(nanoseconds: 1_000_000_000) + } return .result() } } diff --git a/Sources/Extensions/Widgets/Custom/AppIntents/ResetAllCustomWidgetConfirmationAppIntent.swift b/Sources/Extensions/Widgets/Custom/AppIntents/ResetAllCustomWidgetConfirmationAppIntent.swift index 11183db84..419679cc5 100644 --- a/Sources/Extensions/Widgets/Custom/AppIntents/ResetAllCustomWidgetConfirmationAppIntent.swift +++ b/Sources/Extensions/Widgets/Custom/AppIntents/ResetAllCustomWidgetConfirmationAppIntent.swift @@ -5,6 +5,7 @@ import SwiftUI @available(iOS 16.4, *) struct ResetAllCustomWidgetConfirmationAppIntent: AppIntent { + // No translation needed below, this is not a discoverable intent static var title: LocalizedStringResource = "Reset custom widget confirmation states" static var isDiscoverable: Bool = false diff --git a/Sources/Extensions/Widgets/Custom/AppIntents/UpdateWidgetItemConfirmationStateAppIntent.swift b/Sources/Extensions/Widgets/Custom/AppIntents/UpdateWidgetItemConfirmationStateAppIntent.swift index e6834eeff..384c3bc7b 100644 --- a/Sources/Extensions/Widgets/Custom/AppIntents/UpdateWidgetItemConfirmationStateAppIntent.swift +++ b/Sources/Extensions/Widgets/Custom/AppIntents/UpdateWidgetItemConfirmationStateAppIntent.swift @@ -9,6 +9,7 @@ struct UpdateWidgetItemConfirmationStateAppIntent: AppIntent { static var title: LocalizedStringResource = "Update custom widget confirmation" static var isDiscoverable: Bool = false + // No translation needed below, this is not a discoverable intent @Parameter(title: "Widget Id") var widgetId: String? diff --git a/Sources/Extensions/Widgets/Scene/SceneAppIntent.swift b/Sources/Extensions/Widgets/Scene/SceneAppIntent.swift index 265c51d92..2bb50cee0 100644 --- a/Sources/Extensions/Widgets/Scene/SceneAppIntent.swift +++ b/Sources/Extensions/Widgets/Scene/SceneAppIntent.swift @@ -1,5 +1,4 @@ import AppIntents -import AudioToolbox import Foundation import PromiseKit import Shared @@ -36,9 +35,7 @@ final class SceneAppIntent: AppIntent { func perform() async throws -> some IntentResult & ReturnsValue { if hapticConfirmation { - // Unfortunately this is the only 'haptics' that work with widgets - // ideally in the future this should use CoreHaptics for a better experience - AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate)) + AppIntentHaptics.notify() } let success: Bool = try await withCheckedThrowingContinuation { continuation in