forked from home-assistant/iOS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNotificationsCommandManager.swift
More file actions
166 lines (145 loc) · 6.02 KB
/
NotificationsCommandManager.swift
File metadata and controls
166 lines (145 loc) · 6.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import Communicator
import PromiseKit
import UserNotifications
import WidgetKit
public protocol NotificationCommandHandler {
func handle(_ payload: [String: Any]) -> Promise<Void>
}
public class NotificationCommandManager {
public static var didUpdateComplicationsNotification: Notification.Name {
.init(rawValue: "didUpdateComplicationsNotification")
}
public enum CommandError: Error {
case notCommand
case unknownCommand
}
public init() {
register(command: "request_location_update", handler: HandlerLocationUpdate())
register(command: "clear_notification", handler: HandlerClearNotification())
#if os(iOS)
register(command: "update_complications", handler: HandlerUpdateComplications())
#if !targetEnvironment(macCatalyst)
if #available(iOS 17.2, *) {
register(command: "live_activity", handler: HandlerStartOrUpdateLiveActivity())
register(command: "end_live_activity", handler: HandlerEndLiveActivity())
}
#endif
#endif
#if os(iOS) || os(macOS)
register(command: "update_widgets", handler: HandlerUpdateWidgets())
#endif
}
private var commands = [String: NotificationCommandHandler]()
public func register(command: String, handler: NotificationCommandHandler) {
commands[command] = handler
}
public func handle(_ payload: [AnyHashable: Any]) -> Promise<Void> {
guard let hadict = payload["homeassistant"] as? [String: Any] else {
return .init(error: CommandError.notCommand)
}
// Support data.live_update: true — the same field Android uses for Live Updates.
// A single YAML automation can target both platforms with no platform-specific keys.
#if os(iOS) && !targetEnvironment(macCatalyst)
if #available(iOS 17.2, *), hadict["live_update"] as? Bool == true,
let handler = commands["live_activity"] {
return handler.handle(hadict)
}
#endif
guard let command = hadict["command"] as? String else {
return .init(error: CommandError.notCommand)
}
if let handler = commands[command] {
return handler.handle(hadict)
} else {
return .init(error: CommandError.unknownCommand)
}
}
public func updateComplications() -> Promise<Void> {
#if os(iOS)
HandlerUpdateComplications().handle([:])
#else
return .value(())
#endif
}
}
private struct HandlerLocationUpdate: NotificationCommandHandler {
private enum LocationUpdateError: Error {
case notEnabled
}
func handle(_ payload: [String: Any]) -> Promise<Void> {
guard Current.settingsStore.locationSources.pushNotifications else {
Current.Log.info("ignoring request, location source of notifications is disabled")
return .init(error: LocationUpdateError.notEnabled)
}
Current.Log.verbose("Received remote request to provide a location update")
return Current.backgroundTask(withName: BackgroundTask.pushLocationRequest.rawValue) { remaining in
firstly {
Current.location.oneShotLocation(.PushNotification, remaining)
}.then { location in
when(fulfilled: Current.apis.map { api in
api.SubmitLocation(updateType: .PushNotification, location: location, zone: nil)
})
}
}
}
}
private struct HandlerClearNotification: NotificationCommandHandler {
func handle(_ payload: [String: Any]) -> Promise<Void> {
Current.Log.verbose("clearing notification for \(payload)")
let keys = ["tag", "collapseId"].compactMap { payload[$0] as? String }
if !keys.isEmpty {
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: keys)
}
// Also end any Live Activity whose tag matches — same YAML works on both iOS and Android.
// Bridged into the returned Promise so the background fetch window stays open until
// the activity is actually dismissed (prevents the OS suspending mid-dismiss).
// ActivityKit is unavailable in the PushProvider extension and Mac Catalyst, so guard accordingly.
#if os(iOS) && !targetEnvironment(macCatalyst)
if #available(iOS 17.2, *), !Current.isAppExtension, let tag = payload["tag"] as? String {
return Promise<Void> { seal in
Task {
await Current.liveActivityRegistry?.end(tag: tag, dismissalPolicy: .immediate)
// https://stackoverflow.com/a/56657888/6324550
DispatchQueue.main.async { seal.fulfill(()) }
}
}
}
#endif
// https://stackoverflow.com/a/56657888/6324550
return Promise<Void> { seal in
DispatchQueue.main.async {
seal.fulfill(())
}
}
}
}
#if os(iOS)
private struct HandlerUpdateComplications: NotificationCommandHandler {
func handle(_ payload: [String: Any]) -> Promise<Void> {
Promise<Void> { seal in
Communicator.shared.transfer(ComplicationInfo(content: [:])) { result in
switch result {
case .success: seal.fulfill(())
case let .failure(error): seal.reject(error)
}
}
}.get {
NotificationCenter.default.post(
name: NotificationCommandManager.didUpdateComplicationsNotification,
object: nil
)
}
}
}
private struct HandlerUpdateWidgets: NotificationCommandHandler {
func handle(_ payload: [String: Any]) -> Promise<Void> {
Current.Log.verbose("Reloading widgets triggered by notification command")
Current.clientEventStore.addEvent(ClientEvent(
text: "Notification command triggered widget update",
type: .notification
))
DataWidgetsUpdater.update()
return Promise.value(())
}
}
#endif