@idealyst/live-activity 1.2.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(IdealystLiveActivity, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(isSupported)
7
+
8
+ RCT_EXTERN_METHOD(isEnabled:
9
+ (RCTPromiseResolveBlock)resolve
10
+ rejecter:(RCTPromiseRejectBlock)reject)
11
+
12
+ RCT_EXTERN_METHOD(startActivity:
13
+ (NSString *)templateType
14
+ attributesJson:(NSString *)attributesJson
15
+ contentStateJson:(NSString *)contentStateJson
16
+ optionsJson:(NSString *)optionsJson
17
+ resolve:(RCTPromiseResolveBlock)resolve
18
+ rejecter:(RCTPromiseRejectBlock)reject)
19
+
20
+ RCT_EXTERN_METHOD(updateActivity:
21
+ (NSString *)activityId
22
+ contentStateJson:(NSString *)contentStateJson
23
+ alertConfigJson:(NSString *)alertConfigJson
24
+ resolve:(RCTPromiseResolveBlock)resolve
25
+ rejecter:(RCTPromiseRejectBlock)reject)
26
+
27
+ RCT_EXTERN_METHOD(endActivity:
28
+ (NSString *)activityId
29
+ finalContentStateJson:(NSString *)finalContentStateJson
30
+ dismissalPolicy:(NSString *)dismissalPolicy
31
+ dismissAfter:(double)dismissAfter
32
+ resolve:(RCTPromiseResolveBlock)resolve
33
+ rejecter:(RCTPromiseRejectBlock)reject)
34
+
35
+ RCT_EXTERN_METHOD(endAllActivities:
36
+ (NSString *)dismissalPolicy
37
+ dismissAfter:(double)dismissAfter
38
+ resolve:(RCTPromiseResolveBlock)resolve
39
+ rejecter:(RCTPromiseRejectBlock)reject)
40
+
41
+ RCT_EXTERN_METHOD(getActivity:
42
+ (NSString *)activityId
43
+ resolve:(RCTPromiseResolveBlock)resolve
44
+ rejecter:(RCTPromiseRejectBlock)reject)
45
+
46
+ RCT_EXTERN_METHOD(listActivities:
47
+ (RCTPromiseResolveBlock)resolve
48
+ rejecter:(RCTPromiseRejectBlock)reject)
49
+
50
+ RCT_EXTERN_METHOD(getPushToken:
51
+ (NSString *)activityId
52
+ resolve:(RCTPromiseResolveBlock)resolve
53
+ rejecter:(RCTPromiseRejectBlock)reject)
54
+
55
+ @end
@@ -0,0 +1,571 @@
1
+ import Foundation
2
+ import ActivityKit
3
+ import React
4
+
5
+ @available(iOS 16.2, *)
6
+ @objc(IdealystLiveActivity)
7
+ class IdealystLiveActivity: RCTEventEmitter {
8
+
9
+ private var pushTokenTasks: [String: Task<Void, Never>] = [:]
10
+ private var activityStateTasks: [String: Task<Void, Never>] = [:]
11
+
12
+ // MARK: - RCTEventEmitter
13
+
14
+ override func supportedEvents() -> [String]! {
15
+ return ["IdealystLiveActivityEvent"]
16
+ }
17
+
18
+ override static func requiresMainQueueSetup() -> Bool {
19
+ return false
20
+ }
21
+
22
+ // MARK: - Availability
23
+
24
+ @objc
25
+ func isSupported() -> Bool {
26
+ if #available(iOS 16.2, *) {
27
+ return ActivityAuthorizationInfo().areActivitiesEnabled
28
+ }
29
+ return false
30
+ }
31
+
32
+ @objc
33
+ func isEnabled(
34
+ _ resolve: @escaping RCTPromiseResolveBlock,
35
+ rejecter reject: @escaping RCTPromiseRejectBlock
36
+ ) {
37
+ if #available(iOS 16.2, *) {
38
+ resolve(ActivityAuthorizationInfo().areActivitiesEnabled)
39
+ } else {
40
+ resolve(false)
41
+ }
42
+ }
43
+
44
+ // MARK: - Start Activity
45
+
46
+ @objc
47
+ func startActivity(
48
+ _ templateType: String,
49
+ attributesJson: String,
50
+ contentStateJson: String,
51
+ optionsJson: String,
52
+ resolve: @escaping RCTPromiseResolveBlock,
53
+ rejecter reject: @escaping RCTPromiseRejectBlock
54
+ ) {
55
+ guard #available(iOS 16.2, *) else {
56
+ reject("not_supported", "Live Activities require iOS 16.2 or later", nil)
57
+ return
58
+ }
59
+
60
+ do {
61
+ let optionsData = optionsJson.data(using: .utf8) ?? Data()
62
+ let options = try JSONSerialization.jsonObject(with: optionsData) as? [String: Any] ?? [:]
63
+ let enablePush = options["enablePushUpdates"] as? Bool ?? false
64
+ let iosOptions = options["ios"] as? [String: Any]
65
+ let relevanceScore = iosOptions?["relevanceScore"] as? Double
66
+ let staleDateMs = iosOptions?["staleDate"] as? Double
67
+
68
+ switch templateType {
69
+ case "delivery":
70
+ let result = try startTypedActivity(
71
+ DeliveryAttributes.self,
72
+ attributesJson: attributesJson,
73
+ contentStateJson: contentStateJson,
74
+ enablePush: enablePush,
75
+ relevanceScore: relevanceScore,
76
+ staleDateMs: staleDateMs
77
+ )
78
+ resolve(result)
79
+
80
+ case "timer":
81
+ let result = try startTypedActivity(
82
+ TimerAttributes.self,
83
+ attributesJson: attributesJson,
84
+ contentStateJson: contentStateJson,
85
+ enablePush: enablePush,
86
+ relevanceScore: relevanceScore,
87
+ staleDateMs: staleDateMs
88
+ )
89
+ resolve(result)
90
+
91
+ case "media":
92
+ let result = try startTypedActivity(
93
+ MediaAttributes.self,
94
+ attributesJson: attributesJson,
95
+ contentStateJson: contentStateJson,
96
+ enablePush: enablePush,
97
+ relevanceScore: relevanceScore,
98
+ staleDateMs: staleDateMs
99
+ )
100
+ resolve(result)
101
+
102
+ case "progress":
103
+ let result = try startTypedActivity(
104
+ ProgressAttributes.self,
105
+ attributesJson: attributesJson,
106
+ contentStateJson: contentStateJson,
107
+ enablePush: enablePush,
108
+ relevanceScore: relevanceScore,
109
+ staleDateMs: staleDateMs
110
+ )
111
+ resolve(result)
112
+
113
+ default:
114
+ reject("template_not_found", "Unknown template type: \(templateType)", nil)
115
+ }
116
+ } catch {
117
+ reject("start_failed", error.localizedDescription, error)
118
+ }
119
+ }
120
+
121
+ @available(iOS 16.2, *)
122
+ private func startTypedActivity<T: IdealystActivityAttributes>(
123
+ _ type: T.Type,
124
+ attributesJson: String,
125
+ contentStateJson: String,
126
+ enablePush: Bool,
127
+ relevanceScore: Double?,
128
+ staleDateMs: Double?
129
+ ) throws -> String {
130
+ let decoder = JSONDecoder()
131
+ let attributesData = attributesJson.data(using: .utf8)!
132
+ let contentData = contentStateJson.data(using: .utf8)!
133
+
134
+ let attributes = try decoder.decode(T.self, from: attributesData)
135
+ let contentState = try decoder.decode(T.ContentState.self, from: contentData)
136
+
137
+ let initialContent = ActivityContent(
138
+ state: contentState,
139
+ staleDate: staleDateMs.map { Date(timeIntervalSince1970: $0 / 1000) },
140
+ relevanceScore: relevanceScore ?? 0
141
+ )
142
+
143
+ let activity = try Activity.request(
144
+ attributes: attributes,
145
+ content: initialContent,
146
+ pushType: enablePush ? .token : nil
147
+ )
148
+
149
+ // Observe push token updates
150
+ if enablePush {
151
+ observePushTokenUpdates(for: activity)
152
+ }
153
+
154
+ // Observe activity state changes
155
+ observeActivityState(for: activity)
156
+
157
+ return activityToJson(activity)
158
+ }
159
+
160
+ // MARK: - Update Activity
161
+
162
+ @objc
163
+ func updateActivity(
164
+ _ activityId: String,
165
+ contentStateJson: String,
166
+ alertConfigJson: String?,
167
+ resolve: @escaping RCTPromiseResolveBlock,
168
+ rejecter reject: @escaping RCTPromiseRejectBlock
169
+ ) {
170
+ guard #available(iOS 16.2, *) else {
171
+ reject("not_supported", "Live Activities require iOS 16.2 or later", nil)
172
+ return
173
+ }
174
+
175
+ Task {
176
+ do {
177
+ // Try each template type
178
+ if try await updateTypedActivity(DeliveryAttributes.self, activityId: activityId, contentStateJson: contentStateJson, alertConfigJson: alertConfigJson) {
179
+ resolve(nil)
180
+ return
181
+ }
182
+ if try await updateTypedActivity(TimerAttributes.self, activityId: activityId, contentStateJson: contentStateJson, alertConfigJson: alertConfigJson) {
183
+ resolve(nil)
184
+ return
185
+ }
186
+ if try await updateTypedActivity(MediaAttributes.self, activityId: activityId, contentStateJson: contentStateJson, alertConfigJson: alertConfigJson) {
187
+ resolve(nil)
188
+ return
189
+ }
190
+ if try await updateTypedActivity(ProgressAttributes.self, activityId: activityId, contentStateJson: contentStateJson, alertConfigJson: alertConfigJson) {
191
+ resolve(nil)
192
+ return
193
+ }
194
+
195
+ reject("activity_not_found", "No activity found with id: \(activityId)", nil)
196
+ } catch {
197
+ reject("update_failed", error.localizedDescription, error)
198
+ }
199
+ }
200
+ }
201
+
202
+ @available(iOS 16.2, *)
203
+ private func updateTypedActivity<T: IdealystActivityAttributes>(
204
+ _ type: T.Type,
205
+ activityId: String,
206
+ contentStateJson: String,
207
+ alertConfigJson: String?
208
+ ) async throws -> Bool {
209
+ guard let activity = Activity<T>.activities.first(where: { $0.id == activityId }) else {
210
+ return false
211
+ }
212
+
213
+ let decoder = JSONDecoder()
214
+ let contentData = contentStateJson.data(using: .utf8)!
215
+ let contentState = try decoder.decode(T.ContentState.self, from: contentData)
216
+
217
+ let updatedContent = ActivityContent(state: contentState, staleDate: nil)
218
+
219
+ if #available(iOS 16.2, *) {
220
+ if let alertJson = alertConfigJson,
221
+ let alertData = alertJson.data(using: .utf8),
222
+ let alertDict = try JSONSerialization.jsonObject(with: alertData) as? [String: Any] {
223
+ let alertConfig = AlertConfiguration(
224
+ title: LocalizedStringResource(stringLiteral: alertDict["title"] as? String ?? ""),
225
+ body: LocalizedStringResource(stringLiteral: alertDict["body"] as? String ?? ""),
226
+ sound: (alertDict["sound"] as? Bool ?? false) ? .default : nil
227
+ )
228
+ await activity.update(updatedContent, alertConfiguration: alertConfig)
229
+ } else {
230
+ await activity.update(updatedContent)
231
+ }
232
+ }
233
+
234
+ return true
235
+ }
236
+
237
+ // MARK: - End Activity
238
+
239
+ @objc
240
+ func endActivity(
241
+ _ activityId: String,
242
+ finalContentStateJson: String?,
243
+ dismissalPolicy: String,
244
+ dismissAfter: Double,
245
+ resolve: @escaping RCTPromiseResolveBlock,
246
+ rejecter reject: @escaping RCTPromiseRejectBlock
247
+ ) {
248
+ guard #available(iOS 16.2, *) else {
249
+ reject("not_supported", "Live Activities require iOS 16.2 or later", nil)
250
+ return
251
+ }
252
+
253
+ Task {
254
+ do {
255
+ if try await endTypedActivity(DeliveryAttributes.self, activityId: activityId, finalContentStateJson: finalContentStateJson, dismissalPolicy: dismissalPolicy, dismissAfter: dismissAfter) {
256
+ resolve(nil)
257
+ return
258
+ }
259
+ if try await endTypedActivity(TimerAttributes.self, activityId: activityId, finalContentStateJson: finalContentStateJson, dismissalPolicy: dismissalPolicy, dismissAfter: dismissAfter) {
260
+ resolve(nil)
261
+ return
262
+ }
263
+ if try await endTypedActivity(MediaAttributes.self, activityId: activityId, finalContentStateJson: finalContentStateJson, dismissalPolicy: dismissalPolicy, dismissAfter: dismissAfter) {
264
+ resolve(nil)
265
+ return
266
+ }
267
+ if try await endTypedActivity(ProgressAttributes.self, activityId: activityId, finalContentStateJson: finalContentStateJson, dismissalPolicy: dismissalPolicy, dismissAfter: dismissAfter) {
268
+ resolve(nil)
269
+ return
270
+ }
271
+
272
+ reject("activity_not_found", "No activity found with id: \(activityId)", nil)
273
+ } catch {
274
+ reject("end_failed", error.localizedDescription, error)
275
+ }
276
+ }
277
+ }
278
+
279
+ @available(iOS 16.2, *)
280
+ private func endTypedActivity<T: IdealystActivityAttributes>(
281
+ _ type: T.Type,
282
+ activityId: String,
283
+ finalContentStateJson: String?,
284
+ dismissalPolicy: String,
285
+ dismissAfter: Double
286
+ ) async throws -> Bool {
287
+ guard let activity = Activity<T>.activities.first(where: { $0.id == activityId }) else {
288
+ return false
289
+ }
290
+
291
+ // Cancel observation tasks
292
+ pushTokenTasks[activityId]?.cancel()
293
+ pushTokenTasks.removeValue(forKey: activityId)
294
+ activityStateTasks[activityId]?.cancel()
295
+ activityStateTasks.removeValue(forKey: activityId)
296
+
297
+ let policy = parseDismissalPolicy(dismissalPolicy, dismissAfter: dismissAfter)
298
+
299
+ if let json = finalContentStateJson,
300
+ let data = json.data(using: .utf8) {
301
+ let decoder = JSONDecoder()
302
+ let finalState = try decoder.decode(T.ContentState.self, from: data)
303
+ let finalContent = ActivityContent(state: finalState, staleDate: nil)
304
+ await activity.end(finalContent, dismissalPolicy: policy)
305
+ } else {
306
+ await activity.end(nil, dismissalPolicy: policy)
307
+ }
308
+
309
+ return true
310
+ }
311
+
312
+ @objc
313
+ func endAllActivities(
314
+ _ dismissalPolicy: String,
315
+ dismissAfter: Double,
316
+ resolve: @escaping RCTPromiseResolveBlock,
317
+ rejecter reject: @escaping RCTPromiseRejectBlock
318
+ ) {
319
+ guard #available(iOS 16.2, *) else {
320
+ reject("not_supported", "Live Activities require iOS 16.2 or later", nil)
321
+ return
322
+ }
323
+
324
+ Task {
325
+ let policy = parseDismissalPolicy(dismissalPolicy, dismissAfter: dismissAfter)
326
+
327
+ // Cancel all observation tasks
328
+ for task in pushTokenTasks.values { task.cancel() }
329
+ pushTokenTasks.removeAll()
330
+ for task in activityStateTasks.values { task.cancel() }
331
+ activityStateTasks.removeAll()
332
+
333
+ for activity in Activity<DeliveryAttributes>.activities {
334
+ await activity.end(nil, dismissalPolicy: policy)
335
+ }
336
+ for activity in Activity<TimerAttributes>.activities {
337
+ await activity.end(nil, dismissalPolicy: policy)
338
+ }
339
+ for activity in Activity<MediaAttributes>.activities {
340
+ await activity.end(nil, dismissalPolicy: policy)
341
+ }
342
+ for activity in Activity<ProgressAttributes>.activities {
343
+ await activity.end(nil, dismissalPolicy: policy)
344
+ }
345
+
346
+ resolve(nil)
347
+ }
348
+ }
349
+
350
+ // MARK: - Queries
351
+
352
+ @objc
353
+ func getActivity(
354
+ _ activityId: String,
355
+ resolve: @escaping RCTPromiseResolveBlock,
356
+ rejecter reject: @escaping RCTPromiseRejectBlock
357
+ ) {
358
+ guard #available(iOS 16.2, *) else {
359
+ resolve(nil)
360
+ return
361
+ }
362
+
363
+ if let json = findActivity(DeliveryAttributes.self, id: activityId) ??
364
+ findActivity(TimerAttributes.self, id: activityId) ??
365
+ findActivity(MediaAttributes.self, id: activityId) ??
366
+ findActivity(ProgressAttributes.self, id: activityId) {
367
+ resolve(json)
368
+ } else {
369
+ resolve(nil)
370
+ }
371
+ }
372
+
373
+ @objc
374
+ func listActivities(
375
+ _ resolve: @escaping RCTPromiseResolveBlock,
376
+ rejecter reject: @escaping RCTPromiseRejectBlock
377
+ ) {
378
+ guard #available(iOS 16.2, *) else {
379
+ resolve("[]")
380
+ return
381
+ }
382
+
383
+ var results: [String] = []
384
+ for activity in Activity<DeliveryAttributes>.activities {
385
+ results.append(activityToJson(activity))
386
+ }
387
+ for activity in Activity<TimerAttributes>.activities {
388
+ results.append(activityToJson(activity))
389
+ }
390
+ for activity in Activity<MediaAttributes>.activities {
391
+ results.append(activityToJson(activity))
392
+ }
393
+ for activity in Activity<ProgressAttributes>.activities {
394
+ results.append(activityToJson(activity))
395
+ }
396
+
397
+ resolve("[\(results.joined(separator: ","))]")
398
+ }
399
+
400
+ @objc
401
+ func getPushToken(
402
+ _ activityId: String,
403
+ resolve: @escaping RCTPromiseResolveBlock,
404
+ rejecter reject: @escaping RCTPromiseRejectBlock
405
+ ) {
406
+ guard #available(iOS 16.2, *) else {
407
+ resolve(nil)
408
+ return
409
+ }
410
+
411
+ if let token = findPushToken(DeliveryAttributes.self, id: activityId) ??
412
+ findPushToken(TimerAttributes.self, id: activityId) ??
413
+ findPushToken(MediaAttributes.self, id: activityId) ??
414
+ findPushToken(ProgressAttributes.self, id: activityId) {
415
+ resolve(token)
416
+ } else {
417
+ resolve(nil)
418
+ }
419
+ }
420
+
421
+ // MARK: - Private Helpers
422
+
423
+ @available(iOS 16.2, *)
424
+ private func observePushTokenUpdates<T: ActivityAttributes>(for activity: Activity<T>) {
425
+ let task = Task {
426
+ for await tokenData in activity.pushTokenUpdates {
427
+ let tokenString = tokenData.map { String(format: "%02x", $0) }.joined()
428
+ let event: [String: Any] = [
429
+ "type": "tokenUpdated",
430
+ "activityId": activity.id,
431
+ "timestamp": Date().timeIntervalSince1970 * 1000,
432
+ "payload": [
433
+ "token": [
434
+ "token": tokenString,
435
+ "platform": "ios",
436
+ "activityId": activity.id
437
+ ]
438
+ ]
439
+ ]
440
+ self.sendEvent(withName: "IdealystLiveActivityEvent", body: event)
441
+ }
442
+ }
443
+ pushTokenTasks[activity.id] = task
444
+ }
445
+
446
+ @available(iOS 16.2, *)
447
+ private func observeActivityState<T: ActivityAttributes>(for activity: Activity<T>) {
448
+ let task = Task {
449
+ for await state in activity.activityStateUpdates {
450
+ let stateString: String
451
+ switch state {
452
+ case .active: stateString = "active"
453
+ case .dismissed: stateString = "ended"
454
+ case .ended: stateString = "ended"
455
+ case .stale: stateString = "stale"
456
+ @unknown default: stateString = "unknown"
457
+ }
458
+
459
+ let eventType = state == .stale ? "stale" : (state == .dismissed || state == .ended ? "ended" : "updated")
460
+
461
+ let event: [String: Any] = [
462
+ "type": eventType,
463
+ "activityId": activity.id,
464
+ "timestamp": Date().timeIntervalSince1970 * 1000,
465
+ "payload": [
466
+ "state": stateString
467
+ ]
468
+ ]
469
+ self.sendEvent(withName: "IdealystLiveActivityEvent", body: event)
470
+ }
471
+ }
472
+ activityStateTasks[activity.id] = task
473
+ }
474
+
475
+ @available(iOS 16.2, *)
476
+ private func activityToJson<T: ActivityAttributes>(_ activity: Activity<T>) -> String {
477
+ let encoder = JSONEncoder()
478
+ let stateString: String
479
+ switch activity.activityState {
480
+ case .active: stateString = "active"
481
+ case .dismissed: stateString = "ended"
482
+ case .ended: stateString = "ended"
483
+ case .stale: stateString = "stale"
484
+ @unknown default: stateString = "unknown"
485
+ }
486
+
487
+ var dict: [String: Any] = [
488
+ "id": activity.id,
489
+ "state": stateString,
490
+ "startedAt": Date().timeIntervalSince1970 * 1000,
491
+ ]
492
+
493
+ // Encode attributes and content state
494
+ if let attrData = try? encoder.encode(activity.attributes),
495
+ let attrDict = try? JSONSerialization.jsonObject(with: attrData) {
496
+ dict["attributes"] = attrDict
497
+ }
498
+ if let contentData = try? encoder.encode(activity.content.state),
499
+ let contentDict = try? JSONSerialization.jsonObject(with: contentData) {
500
+ dict["contentState"] = contentDict
501
+ }
502
+
503
+ // Determine template type from attributes type
504
+ dict["templateType"] = templateTypeFor(T.self)
505
+
506
+ if let jsonData = try? JSONSerialization.data(withJSONObject: dict),
507
+ let jsonString = String(data: jsonData, encoding: .utf8) {
508
+ return jsonString
509
+ }
510
+
511
+ return "{}"
512
+ }
513
+
514
+ @available(iOS 16.2, *)
515
+ private func findActivity<T: ActivityAttributes>(_ type: T.Type, id: String) -> String? {
516
+ guard let activity = Activity<T>.activities.first(where: { $0.id == id }) else {
517
+ return nil
518
+ }
519
+ return activityToJson(activity)
520
+ }
521
+
522
+ @available(iOS 16.2, *)
523
+ private func findPushToken<T: ActivityAttributes>(_ type: T.Type, id: String) -> String? {
524
+ guard let activity = Activity<T>.activities.first(where: { $0.id == id }) else {
525
+ return nil
526
+ }
527
+ guard let tokenData = activity.pushToken else {
528
+ return nil
529
+ }
530
+ let tokenString = tokenData.map { String(format: "%02x", $0) }.joined()
531
+ let dict: [String: Any] = [
532
+ "token": tokenString,
533
+ "platform": "ios",
534
+ "activityId": activity.id
535
+ ]
536
+ if let jsonData = try? JSONSerialization.data(withJSONObject: dict),
537
+ let jsonString = String(data: jsonData, encoding: .utf8) {
538
+ return jsonString
539
+ }
540
+ return nil
541
+ }
542
+
543
+ @available(iOS 16.2, *)
544
+ private func parseDismissalPolicy(_ policy: String, dismissAfter: Double) -> ActivityUIDismissalPolicy {
545
+ switch policy {
546
+ case "immediate":
547
+ return .immediate
548
+ case "afterDate":
549
+ if dismissAfter > 0 {
550
+ return .after(Date(timeIntervalSince1970: dismissAfter / 1000))
551
+ }
552
+ return .default
553
+ default:
554
+ return .default
555
+ }
556
+ }
557
+
558
+ private func templateTypeFor<T: ActivityAttributes>(_ type: T.Type) -> String {
559
+ switch String(describing: type) {
560
+ case "DeliveryAttributes": return "delivery"
561
+ case "TimerAttributes": return "timer"
562
+ case "MediaAttributes": return "media"
563
+ case "ProgressAttributes": return "progress"
564
+ default: return "custom"
565
+ }
566
+ }
567
+ }
568
+
569
+ // MARK: - Protocol for template attributes
570
+
571
+ protocol IdealystActivityAttributes: ActivityAttributes where ContentState: Codable {}
@@ -0,0 +1,66 @@
1
+ import ActivityKit
2
+ import Foundation
3
+
4
+ // MARK: - Delivery Activity
5
+
6
+ struct DeliveryAttributes: IdealystActivityAttributes {
7
+ struct ContentState: Codable, Hashable {
8
+ var progress: Double
9
+ var status: String
10
+ var eta: Double?
11
+ var driverName: String?
12
+ var subtitle: String?
13
+ }
14
+
15
+ var startLabel: String
16
+ var endLabel: String
17
+ var icon: String?
18
+ var accentColor: String?
19
+ }
20
+
21
+ // MARK: - Timer Activity
22
+
23
+ struct TimerAttributes: IdealystActivityAttributes {
24
+ struct ContentState: Codable, Hashable {
25
+ var endTime: Double
26
+ var isPaused: Bool?
27
+ var subtitle: String?
28
+ }
29
+
30
+ var title: String
31
+ var icon: String?
32
+ var accentColor: String?
33
+ var showElapsed: Bool?
34
+ }
35
+
36
+ // MARK: - Media Activity
37
+
38
+ struct MediaAttributes: IdealystActivityAttributes {
39
+ struct ContentState: Codable, Hashable {
40
+ var trackTitle: String
41
+ var artist: String?
42
+ var isPlaying: Bool
43
+ var progress: Double?
44
+ var duration: Double?
45
+ var position: Double?
46
+ }
47
+
48
+ var title: String
49
+ var artworkUri: String?
50
+ var accentColor: String?
51
+ }
52
+
53
+ // MARK: - Progress Activity
54
+
55
+ struct ProgressAttributes: IdealystActivityAttributes {
56
+ struct ContentState: Codable, Hashable {
57
+ var progress: Double
58
+ var status: String
59
+ var subtitle: String?
60
+ var indeterminate: Bool?
61
+ }
62
+
63
+ var title: String
64
+ var icon: String?
65
+ var accentColor: String?
66
+ }