Skip to content

Commit 3a709db

Browse files
authored
Best effort to reflect state in custom widget after interaction (#3415)
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> Since an entity not always reflect its state right away after being toggled and at the same time refreshing the widget through push notification is not something reliable at the moment in iOS, this PR does the best effort to reflect the state in the widget after an interaction by adding a delay. "Haptic" was also added. ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. -->
1 parent dac0272 commit 3a709db

File tree

10 files changed

+36
-12
lines changed

10 files changed

+36
-12
lines changed

HomeAssistant.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,8 @@
604604
422F88192D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */; };
605605
422F881A2D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */; };
606606
422F951F2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */; };
607+
423179802D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */; };
608+
423179812D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */; };
607609
42333ADB2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; };
608610
42333ADC2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; };
609611
42333ADD2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; };
@@ -1928,6 +1930,7 @@
19281930
422F88152D428E8300706A0A /* CustomWidgetActivateAppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomWidgetActivateAppIntent.swift; sourceTree = "<group>"; };
19291931
422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomWidgetPressButtonAppIntent.swift; sourceTree = "<group>"; };
19301932
422F951E2CFDF7C5003B7514 /* HAApplicationShortcutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAApplicationShortcutItem.swift; sourceTree = "<group>"; };
1933+
4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentHaptics.swift; sourceTree = "<group>"; };
19311934
42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityRegistryListForDisplay.swift; sourceTree = "<group>"; };
19321935
4235075C2CDB756800A19902 /* HAServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAServices.swift; sourceTree = "<group>"; };
19331936
4239D1802C4FFB75003497FC /* WatchUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchUserDefaults.swift; sourceTree = "<group>"; };
@@ -3868,6 +3871,7 @@
38683871
422F88182D42989E00706A0A /* CustomWidgetPressButtonAppIntent.swift */,
38693872
42EF0ACC2D4CDC0C0088C91E /* ResetAllCustomWidgetConfirmationAppIntent.swift */,
38703873
42EF0ACF2D4CDFF30088C91E /* UpdateWidgetItemConfirmationStateAppIntent.swift */,
3874+
4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */,
38713875
);
38723876
path = AppIntents;
38733877
sourceTree = "<group>";
@@ -6917,6 +6921,7 @@
69176921
4223688B2D40F9B7005911E4 /* WidgetCustom.swift in Sources */,
69186922
429BA2AF2C800CAB00A50996 /* SFSymbolEntity.swift in Sources */,
69196923
420461692C8F29440062E89F /* ControlLight.swift in Sources */,
6924+
423179812D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */,
69206925
110E694624E771AB004AA96D /* Color+Hex.swift in Sources */,
69216926
);
69226927
runOnlyForDeploymentPostprocessing = 0;
@@ -7180,6 +7185,7 @@
71807185
42D3E4B72C5D2C2700444BE6 /* WidgetScriptsAppIntent.swift in Sources */,
71817186
4251AA9B2C6B9DBE004CCC9D /* MagicItemCustomizationViewModel.swift in Sources */,
71827187
422F88142D4282CD00706A0A /* CustomWidgetToggleAppIntent.swift in Sources */,
7188+
423179802D54FADD0037A8A4 /* AppIntentHaptics.swift in Sources */,
71837189
4273C48F2C885FB00065A5B4 /* SFSymbolEntity.swift in Sources */,
71847190
116D3A442724EFFB00EF5D21 /* OnboardingAuthTokenExchange.swift in Sources */,
71857191
429821142CD0DD85005ECD39 /* BluetoothPermissionViewModel.swift in Sources */,

Sources/Extensions/AppIntents/Action/PerformAction.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import AppIntents
2-
import AudioToolbox
32
import Foundation
43
import PromiseKit
54
import Shared
@@ -46,9 +45,7 @@ struct PerformAction: AppIntent, CustomIntentMigratedAppIntent, PredictableInten
4645
}
4746

4847
if hapticConfirmation {
49-
// Unfortunately this is the only 'haptics' that works with widgets
50-
// ideally in the future this should use CoreHaptics for a better experience
51-
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
48+
AppIntentHaptics.notify()
5249
}
5350

5451
try await withCheckedThrowingContinuation { continuation in

Sources/Extensions/AppIntents/Script/ScriptAppIntent.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import AppIntents
2-
import AudioToolbox
32
import Foundation
43
import PromiseKit
54
import SFSafeSymbols
@@ -37,9 +36,7 @@ final class ScriptAppIntent: AppIntent {
3736

3837
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
3938
if hapticConfirmation {
40-
// Unfortunately this is the only 'haptics' that work with widgets
41-
// ideally in the future this should use CoreHaptics for a better experience
42-
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
39+
AppIntentHaptics.notify()
4340
}
4441

4542
let success: Bool = try await withCheckedThrowingContinuation { continuation in
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import AudioToolbox
2+
import Foundation
3+
4+
enum AppIntentHaptics {
5+
static func notify() {
6+
// Unfortunately this is the only 'haptics' that work with widgets
7+
// ideally in the future this should use CoreHaptics for a better experience
8+
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
9+
}
10+
}

Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetActivateAppIntent.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct CustomWidgetActivateAppIntent: AppIntent {
1010
static var title: LocalizedStringResource = "Activate"
1111
static var isDiscoverable: Bool = false
1212

13+
// No translation needed below, this is not a discoverable intent
1314
@Parameter(title: "Server")
1415
var serverId: String?
1516
@Parameter(title: "Domain")
@@ -27,6 +28,7 @@ struct CustomWidgetActivateAppIntent: AppIntent {
2728
}), let connection = Current.api(for: server)?.connection else {
2829
return .result()
2930
}
31+
AppIntentHaptics.notify()
3032

3133
guard let request: HATypedRequest<HAResponseVoid> = {
3234
switch domain {

Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetPressButtonAppIntent.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct CustomWidgetPressButtonAppIntent: AppIntent {
88
static var title: LocalizedStringResource = "Toggle"
99
static var isDiscoverable: Bool = false
1010

11+
// No translation needed below, this is not a discoverable intent
1112
@Parameter(title: "Server")
1213
var serverId: String?
1314
@Parameter(title: "Domain")
@@ -25,6 +26,7 @@ struct CustomWidgetPressButtonAppIntent: AppIntent {
2526
}), let connection = Current.api(for: server)?.connection else {
2627
return .result()
2728
}
29+
AppIntentHaptics.notify()
2830
await withCheckedContinuation { continuation in
2931
connection.send(.pressButton(domain: domain, entityId: entityId)).promise.pipe { result in
3032
switch result {

Sources/Extensions/Widgets/Custom/AppIntents/CustomWidgetToggleAppIntent.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,28 @@ struct CustomWidgetToggleAppIntent: AppIntent {
99
static var title: LocalizedStringResource = "Toggle"
1010
static var isDiscoverable: Bool = false
1111

12+
// No translation needed below, this is not a discoverable intent
1213
@Parameter(title: "Server")
1314
var serverId: String?
1415
@Parameter(title: "Domain")
1516
var domain: String?
1617
@Parameter(title: "Entity ID")
1718
var entityId: String?
19+
@Parameter(title: "Is widget showing states?")
20+
var widgetShowingStates: Bool?
1821

1922
func perform() async throws -> some IntentResult {
2023
guard let serverId,
2124
let domainString = domain,
2225
let domain = Domain(rawValue: domainString),
2326
let entityId,
27+
let widgetShowingStates,
2428
let server = Current.servers.all.first(where: { server in
2529
server.identifier.rawValue == serverId
2630
}), let connection = Current.api(for: server)?.connection else {
2731
return .result()
2832
}
33+
AppIntentHaptics.notify()
2934
await withCheckedContinuation { continuation in
3035
connection.send(.toggleDomain(domain: domain, entityId: entityId)).promise.pipe { result in
3136
switch result {
@@ -47,6 +52,12 @@ struct CustomWidgetToggleAppIntent: AppIntent {
4752
}
4853
}
4954
_ = try await ResetAllCustomWidgetConfirmationAppIntent().perform()
55+
if widgetShowingStates {
56+
/* Since when you toggle an entity not always it reflects the new state right away
57+
and at the same time push notifications to update widgets are currently not working reliably
58+
in iOS, this delay is out best effort for the user to see the correct state after finishing the interaction */
59+
try await Task.sleep(nanoseconds: 1_000_000_000)
60+
}
5061
return .result()
5162
}
5263
}

Sources/Extensions/Widgets/Custom/AppIntents/ResetAllCustomWidgetConfirmationAppIntent.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SwiftUI
55

66
@available(iOS 16.4, *)
77
struct ResetAllCustomWidgetConfirmationAppIntent: AppIntent {
8+
// No translation needed below, this is not a discoverable intent
89
static var title: LocalizedStringResource = "Reset custom widget confirmation states"
910
static var isDiscoverable: Bool = false
1011

Sources/Extensions/Widgets/Custom/AppIntents/UpdateWidgetItemConfirmationStateAppIntent.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct UpdateWidgetItemConfirmationStateAppIntent: AppIntent {
99
static var title: LocalizedStringResource = "Update custom widget confirmation"
1010
static var isDiscoverable: Bool = false
1111

12+
// No translation needed below, this is not a discoverable intent
1213
@Parameter(title: "Widget Id")
1314
var widgetId: String?
1415

Sources/Extensions/Widgets/Scene/SceneAppIntent.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import AppIntents
2-
import AudioToolbox
32
import Foundation
43
import PromiseKit
54
import Shared
@@ -36,9 +35,7 @@ final class SceneAppIntent: AppIntent {
3635

3736
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
3837
if hapticConfirmation {
39-
// Unfortunately this is the only 'haptics' that work with widgets
40-
// ideally in the future this should use CoreHaptics for a better experience
41-
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
38+
AppIntentHaptics.notify()
4239
}
4340

4441
let success: Bool = try await withCheckedThrowingContinuation { continuation in

0 commit comments

Comments
 (0)