@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,143 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+ import ActivityKit
4
+
5
+ @available(iOS 16.2, *)
6
+ struct DeliveryActivityView: Widget {
7
+ var body: some WidgetConfiguration {
8
+ ActivityConfiguration(for: DeliveryAttributes.self) { context in
9
+ // Lock screen presentation
10
+ DeliveryLockScreenView(context: context)
11
+ .padding()
12
+ } dynamicIsland: { context in
13
+ DynamicIsland {
14
+ DynamicIslandExpandedRegion(.leading) {
15
+ VStack(alignment: .leading, spacing: 2) {
16
+ Text(context.attributes.startLabel)
17
+ .font(.caption2)
18
+ .foregroundStyle(.secondary)
19
+ if let driver = context.state.driverName {
20
+ Text(driver)
21
+ .font(.caption)
22
+ .bold()
23
+ }
24
+ }
25
+ }
26
+
27
+ DynamicIslandExpandedRegion(.trailing) {
28
+ VStack(alignment: .trailing, spacing: 2) {
29
+ Text(context.attributes.endLabel)
30
+ .font(.caption2)
31
+ .foregroundStyle(.secondary)
32
+ if let etaMs = context.state.eta {
33
+ Text(Date(timeIntervalSince1970: etaMs / 1000), style: .relative)
34
+ .font(.caption)
35
+ .bold()
36
+ }
37
+ }
38
+ }
39
+
40
+ DynamicIslandExpandedRegion(.center) {
41
+ Text(context.state.status)
42
+ .font(.headline)
43
+ .lineLimit(1)
44
+ }
45
+
46
+ DynamicIslandExpandedRegion(.bottom) {
47
+ ProgressView(value: context.state.progress)
48
+ .tint(accentColorFrom(context.attributes.accentColor))
49
+ .padding(.top, 4)
50
+ }
51
+ } compactLeading: {
52
+ Image(systemName: context.attributes.icon ?? "car.fill")
53
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
54
+ } compactTrailing: {
55
+ if let etaMs = context.state.eta {
56
+ Text(Date(timeIntervalSince1970: etaMs / 1000), style: .relative)
57
+ .font(.caption)
58
+ .monospacedDigit()
59
+ } else {
60
+ Text("\(Int(context.state.progress * 100))%")
61
+ .font(.caption)
62
+ .monospacedDigit()
63
+ }
64
+ } minimal: {
65
+ Image(systemName: context.attributes.icon ?? "car.fill")
66
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ @available(iOS 16.2, *)
73
+ private struct DeliveryLockScreenView: View {
74
+ let context: ActivityViewContext<DeliveryAttributes>
75
+
76
+ var body: some View {
77
+ VStack(spacing: 12) {
78
+ HStack {
79
+ VStack(alignment: .leading, spacing: 2) {
80
+ Text(context.state.status)
81
+ .font(.headline)
82
+ if let subtitle = context.state.subtitle {
83
+ Text(subtitle)
84
+ .font(.subheadline)
85
+ .foregroundStyle(.secondary)
86
+ }
87
+ }
88
+ Spacer()
89
+ if let etaMs = context.state.eta {
90
+ VStack(alignment: .trailing, spacing: 2) {
91
+ Text("ETA")
92
+ .font(.caption2)
93
+ .foregroundStyle(.secondary)
94
+ Text(Date(timeIntervalSince1970: etaMs / 1000), style: .relative)
95
+ .font(.subheadline)
96
+ .bold()
97
+ .monospacedDigit()
98
+ }
99
+ }
100
+ }
101
+
102
+ VStack(spacing: 4) {
103
+ ProgressView(value: context.state.progress)
104
+ .tint(accentColorFrom(context.attributes.accentColor))
105
+
106
+ HStack {
107
+ Text(context.attributes.startLabel)
108
+ .font(.caption2)
109
+ .foregroundStyle(.secondary)
110
+ Spacer()
111
+ Text(context.attributes.endLabel)
112
+ .font(.caption2)
113
+ .foregroundStyle(.secondary)
114
+ }
115
+ }
116
+ }
117
+ .padding()
118
+ .background(.ultraThinMaterial)
119
+ }
120
+ }
121
+
122
+ // MARK: - Helpers
123
+
124
+ private func accentColorFrom(_ hex: String?) -> Color {
125
+ guard let hex = hex else { return .blue }
126
+ return Color(hex: hex) ?? .blue
127
+ }
128
+
129
+ extension Color {
130
+ init?(hex: String) {
131
+ var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
132
+ hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
133
+
134
+ var rgb: UInt64 = 0
135
+ guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
136
+
137
+ let r = Double((rgb & 0xFF0000) >> 16) / 255.0
138
+ let g = Double((rgb & 0x00FF00) >> 8) / 255.0
139
+ let b = Double(rgb & 0x0000FF) / 255.0
140
+
141
+ self.init(red: r, green: g, blue: b)
142
+ }
143
+ }
@@ -0,0 +1,18 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+
4
+ /// Widget bundle that registers all Idealyst pre-built Live Activity templates.
5
+ /// This is used as the @main entry point for the Widget Extension target.
6
+ ///
7
+ /// When using the CLI generator to scaffold a custom Live Activity,
8
+ /// add your custom widget to this bundle.
9
+ @available(iOS 16.2, *)
10
+ @main
11
+ struct IdealystActivityBundle: WidgetBundle {
12
+ var body: some Widget {
13
+ DeliveryActivityView()
14
+ TimerActivityView()
15
+ MediaActivityView()
16
+ ProgressActivityView()
17
+ }
18
+ }
@@ -0,0 +1,124 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+ import ActivityKit
4
+
5
+ @available(iOS 16.2, *)
6
+ struct MediaActivityView: Widget {
7
+ var body: some WidgetConfiguration {
8
+ ActivityConfiguration(for: MediaAttributes.self) { context in
9
+ // Lock screen presentation
10
+ MediaLockScreenView(context: context)
11
+ .padding()
12
+ } dynamicIsland: { context in
13
+ DynamicIsland {
14
+ DynamicIslandExpandedRegion(.leading) {
15
+ if let artworkUri = context.attributes.artworkUri {
16
+ AsyncImage(url: URL(string: artworkUri)) { image in
17
+ image.resizable().aspectRatio(contentMode: .fill)
18
+ } placeholder: {
19
+ Image(systemName: "music.note")
20
+ }
21
+ .frame(width: 48, height: 48)
22
+ .clipShape(RoundedRectangle(cornerRadius: 8))
23
+ } else {
24
+ Image(systemName: "music.note")
25
+ .font(.title2)
26
+ }
27
+ }
28
+
29
+ DynamicIslandExpandedRegion(.trailing) {
30
+ Image(systemName: context.state.isPlaying ? "pause.fill" : "play.fill")
31
+ .font(.title2)
32
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
33
+ }
34
+
35
+ DynamicIslandExpandedRegion(.center) {
36
+ VStack(spacing: 2) {
37
+ Text(context.state.trackTitle)
38
+ .font(.headline)
39
+ .lineLimit(1)
40
+ if let artist = context.state.artist {
41
+ Text(artist)
42
+ .font(.caption)
43
+ .foregroundStyle(.secondary)
44
+ .lineLimit(1)
45
+ }
46
+ }
47
+ }
48
+
49
+ DynamicIslandExpandedRegion(.bottom) {
50
+ if let progress = context.state.progress {
51
+ ProgressView(value: progress)
52
+ .tint(accentColorFrom(context.attributes.accentColor))
53
+ .padding(.top, 4)
54
+ }
55
+ }
56
+ } compactLeading: {
57
+ Image(systemName: context.state.isPlaying ? "music.note" : "pause.fill")
58
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
59
+ } compactTrailing: {
60
+ Text(context.state.trackTitle)
61
+ .font(.caption)
62
+ .lineLimit(1)
63
+ } minimal: {
64
+ Image(systemName: context.state.isPlaying ? "music.note" : "pause.fill")
65
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ @available(iOS 16.2, *)
72
+ private struct MediaLockScreenView: View {
73
+ let context: ActivityViewContext<MediaAttributes>
74
+
75
+ var body: some View {
76
+ HStack(spacing: 12) {
77
+ if let artworkUri = context.attributes.artworkUri {
78
+ AsyncImage(url: URL(string: artworkUri)) { image in
79
+ image.resizable().aspectRatio(contentMode: .fill)
80
+ } placeholder: {
81
+ RoundedRectangle(cornerRadius: 8)
82
+ .fill(.quaternary)
83
+ .overlay(
84
+ Image(systemName: "music.note")
85
+ .foregroundStyle(.secondary)
86
+ )
87
+ }
88
+ .frame(width: 56, height: 56)
89
+ .clipShape(RoundedRectangle(cornerRadius: 8))
90
+ }
91
+
92
+ VStack(alignment: .leading, spacing: 4) {
93
+ Text(context.state.trackTitle)
94
+ .font(.headline)
95
+ .lineLimit(1)
96
+
97
+ if let artist = context.state.artist {
98
+ Text(artist)
99
+ .font(.subheadline)
100
+ .foregroundStyle(.secondary)
101
+ .lineLimit(1)
102
+ }
103
+
104
+ if let progress = context.state.progress {
105
+ ProgressView(value: progress)
106
+ .tint(accentColorFrom(context.attributes.accentColor))
107
+ }
108
+ }
109
+
110
+ Spacer()
111
+
112
+ Image(systemName: context.state.isPlaying ? "pause.circle.fill" : "play.circle.fill")
113
+ .font(.largeTitle)
114
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
115
+ }
116
+ .padding()
117
+ .background(.ultraThinMaterial)
118
+ }
119
+ }
120
+
121
+ private func accentColorFrom(_ hex: String?) -> Color {
122
+ guard let hex = hex else { return .blue }
123
+ return Color(hex: hex) ?? .blue
124
+ }
@@ -0,0 +1,164 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+ import ActivityKit
4
+
5
+ @available(iOS 16.2, *)
6
+ struct ProgressActivityView: Widget {
7
+ var body: some WidgetConfiguration {
8
+ ActivityConfiguration(for: ProgressAttributes.self) { context in
9
+ // Lock screen presentation
10
+ ProgressLockScreenView(context: context)
11
+ .padding()
12
+ } dynamicIsland: { context in
13
+ DynamicIsland {
14
+ DynamicIslandExpandedRegion(.leading) {
15
+ Image(systemName: context.attributes.icon ?? "arrow.down.circle")
16
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
17
+ .font(.title2)
18
+ }
19
+
20
+ DynamicIslandExpandedRegion(.trailing) {
21
+ if context.state.indeterminate == true {
22
+ ProgressView()
23
+ .progressViewStyle(.circular)
24
+ } else {
25
+ Text("\(Int(context.state.progress * 100))%")
26
+ .font(.title3)
27
+ .bold()
28
+ .monospacedDigit()
29
+ }
30
+ }
31
+
32
+ DynamicIslandExpandedRegion(.center) {
33
+ Text(context.state.status)
34
+ .font(.headline)
35
+ .lineLimit(1)
36
+ }
37
+
38
+ DynamicIslandExpandedRegion(.bottom) {
39
+ VStack(spacing: 4) {
40
+ if context.state.indeterminate == true {
41
+ ProgressView()
42
+ .progressViewStyle(.linear)
43
+ .tint(accentColorFrom(context.attributes.accentColor))
44
+ } else {
45
+ ProgressView(value: context.state.progress)
46
+ .tint(accentColorFrom(context.attributes.accentColor))
47
+ }
48
+
49
+ if let subtitle = context.state.subtitle {
50
+ Text(subtitle)
51
+ .font(.caption2)
52
+ .foregroundStyle(.secondary)
53
+ }
54
+ }
55
+ .padding(.top, 4)
56
+ }
57
+ } compactLeading: {
58
+ ProgressRing(
59
+ progress: context.state.progress,
60
+ color: accentColorFrom(context.attributes.accentColor),
61
+ isIndeterminate: context.state.indeterminate == true
62
+ )
63
+ .frame(width: 20, height: 20)
64
+ } compactTrailing: {
65
+ if context.state.indeterminate == true {
66
+ ProgressView()
67
+ .progressViewStyle(.circular)
68
+ .scaleEffect(0.5)
69
+ } else {
70
+ Text("\(Int(context.state.progress * 100))%")
71
+ .font(.caption)
72
+ .monospacedDigit()
73
+ }
74
+ } minimal: {
75
+ ProgressRing(
76
+ progress: context.state.progress,
77
+ color: accentColorFrom(context.attributes.accentColor),
78
+ isIndeterminate: context.state.indeterminate == true
79
+ )
80
+ .frame(width: 20, height: 20)
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ @available(iOS 16.2, *)
87
+ private struct ProgressLockScreenView: View {
88
+ let context: ActivityViewContext<ProgressAttributes>
89
+
90
+ var body: some View {
91
+ VStack(spacing: 12) {
92
+ HStack {
93
+ Image(systemName: context.attributes.icon ?? "arrow.down.circle")
94
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
95
+ .font(.title3)
96
+
97
+ VStack(alignment: .leading, spacing: 2) {
98
+ Text(context.attributes.title)
99
+ .font(.headline)
100
+ Text(context.state.status)
101
+ .font(.subheadline)
102
+ .foregroundStyle(.secondary)
103
+ }
104
+
105
+ Spacer()
106
+
107
+ if context.state.indeterminate != true {
108
+ Text("\(Int(context.state.progress * 100))%")
109
+ .font(.title2)
110
+ .bold()
111
+ .monospacedDigit()
112
+ }
113
+ }
114
+
115
+ if context.state.indeterminate == true {
116
+ ProgressView()
117
+ .progressViewStyle(.linear)
118
+ .tint(accentColorFrom(context.attributes.accentColor))
119
+ } else {
120
+ ProgressView(value: context.state.progress)
121
+ .tint(accentColorFrom(context.attributes.accentColor))
122
+ }
123
+
124
+ if let subtitle = context.state.subtitle {
125
+ Text(subtitle)
126
+ .font(.caption)
127
+ .foregroundStyle(.secondary)
128
+ .frame(maxWidth: .infinity, alignment: .leading)
129
+ }
130
+ }
131
+ .padding()
132
+ .background(.ultraThinMaterial)
133
+ }
134
+ }
135
+
136
+ // MARK: - Progress Ring (for compact/minimal Dynamic Island)
137
+
138
+ private struct ProgressRing: View {
139
+ let progress: Double
140
+ let color: Color
141
+ let isIndeterminate: Bool
142
+
143
+ var body: some View {
144
+ ZStack {
145
+ Circle()
146
+ .stroke(color.opacity(0.2), lineWidth: 2)
147
+ if isIndeterminate {
148
+ Circle()
149
+ .trim(from: 0, to: 0.3)
150
+ .stroke(color, style: StrokeStyle(lineWidth: 2, lineCap: .round))
151
+ } else {
152
+ Circle()
153
+ .trim(from: 0, to: progress)
154
+ .stroke(color, style: StrokeStyle(lineWidth: 2, lineCap: .round))
155
+ .rotationEffect(.degrees(-90))
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ private func accentColorFrom(_ hex: String?) -> Color {
162
+ guard let hex = hex else { return .blue }
163
+ return Color(hex: hex) ?? .blue
164
+ }
@@ -0,0 +1,110 @@
1
+ import SwiftUI
2
+ import WidgetKit
3
+ import ActivityKit
4
+
5
+ @available(iOS 16.2, *)
6
+ struct TimerActivityView: Widget {
7
+ var body: some WidgetConfiguration {
8
+ ActivityConfiguration(for: TimerAttributes.self) { context in
9
+ // Lock screen presentation
10
+ TimerLockScreenView(context: context)
11
+ .padding()
12
+ } dynamicIsland: { context in
13
+ DynamicIsland {
14
+ DynamicIslandExpandedRegion(.leading) {
15
+ Image(systemName: context.attributes.icon ?? "timer")
16
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
17
+ .font(.title2)
18
+ }
19
+
20
+ DynamicIslandExpandedRegion(.trailing) {
21
+ timerText(for: context)
22
+ .font(.title2)
23
+ .bold()
24
+ .monospacedDigit()
25
+ }
26
+
27
+ DynamicIslandExpandedRegion(.center) {
28
+ Text(context.attributes.title)
29
+ .font(.headline)
30
+ .lineLimit(1)
31
+ }
32
+
33
+ DynamicIslandExpandedRegion(.bottom) {
34
+ if let subtitle = context.state.subtitle {
35
+ Text(subtitle)
36
+ .font(.caption)
37
+ .foregroundStyle(.secondary)
38
+ }
39
+ }
40
+ } compactLeading: {
41
+ Image(systemName: context.attributes.icon ?? "timer")
42
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
43
+ } compactTrailing: {
44
+ timerText(for: context)
45
+ .font(.caption)
46
+ .monospacedDigit()
47
+ } minimal: {
48
+ Image(systemName: context.attributes.icon ?? "timer")
49
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
50
+ }
51
+ }
52
+ }
53
+
54
+ @ViewBuilder
55
+ private func timerText(for context: ActivityViewContext<TimerAttributes>) -> some View {
56
+ let endDate = Date(timeIntervalSince1970: context.state.endTime / 1000)
57
+ if context.state.isPaused == true {
58
+ Text(endDate, style: .relative)
59
+ } else {
60
+ Text(timerInterval: Date.now...endDate, countsDown: !(context.attributes.showElapsed ?? false))
61
+ }
62
+ }
63
+ }
64
+
65
+ @available(iOS 16.2, *)
66
+ private struct TimerLockScreenView: View {
67
+ let context: ActivityViewContext<TimerAttributes>
68
+
69
+ var body: some View {
70
+ VStack(spacing: 12) {
71
+ HStack {
72
+ Image(systemName: context.attributes.icon ?? "timer")
73
+ .foregroundColor(accentColorFrom(context.attributes.accentColor))
74
+ .font(.title2)
75
+
76
+ Text(context.attributes.title)
77
+ .font(.headline)
78
+
79
+ Spacer()
80
+
81
+ let endDate = Date(timeIntervalSince1970: context.state.endTime / 1000)
82
+ if context.state.isPaused == true {
83
+ Text(endDate, style: .relative)
84
+ .font(.title)
85
+ .bold()
86
+ .monospacedDigit()
87
+ } else {
88
+ Text(timerInterval: Date.now...endDate, countsDown: !(context.attributes.showElapsed ?? false))
89
+ .font(.title)
90
+ .bold()
91
+ .monospacedDigit()
92
+ }
93
+ }
94
+
95
+ if let subtitle = context.state.subtitle {
96
+ Text(subtitle)
97
+ .font(.subheadline)
98
+ .foregroundStyle(.secondary)
99
+ .frame(maxWidth: .infinity, alignment: .leading)
100
+ }
101
+ }
102
+ .padding()
103
+ .background(.ultraThinMaterial)
104
+ }
105
+ }
106
+
107
+ private func accentColorFrom(_ hex: String?) -> Color {
108
+ guard let hex = hex else { return .blue }
109
+ return Color(hex: hex) ?? .blue
110
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@idealyst/live-activity",
3
+ "version": "1.2.114",
4
+ "description": "Cross-platform Live Activities for React Native (iOS ActivityKit + Android Live Updates)",
5
+ "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/live-activity#readme",
6
+ "readme": "README.md",
7
+ "main": "src/index.ts",
8
+ "module": "src/index.ts",
9
+ "types": "src/index.ts",
10
+ "react-native": "src/index.native.ts",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/IdealystIO/idealyst-framework.git",
14
+ "directory": "packages/live-activity"
15
+ },
16
+ "author": "Idealyst <contact@idealyst.io>",
17
+ "license": "MIT",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "react-native": "./src/index.native.ts",
24
+ "browser": {
25
+ "types": "./src/index.web.ts",
26
+ "import": "./src/index.web.ts",
27
+ "require": "./src/index.web.ts"
28
+ },
29
+ "default": {
30
+ "types": "./src/index.ts",
31
+ "import": "./src/index.ts",
32
+ "require": "./src/index.ts"
33
+ }
34
+ }
35
+ },
36
+ "scripts": {
37
+ "prepublishOnly": "echo 'Publishing TypeScript source directly'",
38
+ "publish:npm": "npm publish"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18.0.0",
42
+ "react-native": ">=0.76.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "react-native": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "^19.1.0",
51
+ "typescript": "^5.0.0"
52
+ },
53
+ "codegenConfig": {
54
+ "name": "IdealystLiveActivitySpec",
55
+ "type": "modules",
56
+ "jsSrcsDir": "src",
57
+ "android": {
58
+ "javaPackageName": "io.idealyst.liveactivity"
59
+ }
60
+ },
61
+ "files": [
62
+ "src",
63
+ "ios",
64
+ "android",
65
+ "*.podspec",
66
+ "README.md"
67
+ ],
68
+ "keywords": [
69
+ "react",
70
+ "react-native",
71
+ "live-activity",
72
+ "activitykit",
73
+ "dynamic-island",
74
+ "live-updates",
75
+ "notifications",
76
+ "ios",
77
+ "android",
78
+ "cross-platform"
79
+ ]
80
+ }
@@ -0,0 +1,49 @@
1
+ import type { TurboModule } from 'react-native';
2
+ import { TurboModuleRegistry } from 'react-native';
3
+
4
+ export interface Spec extends TurboModule {
5
+ // Availability
6
+ isSupported(): boolean;
7
+ isEnabled(): Promise<boolean>;
8
+
9
+ // Lifecycle
10
+ startActivity(
11
+ templateType: string,
12
+ attributesJson: string,
13
+ contentStateJson: string,
14
+ optionsJson: string,
15
+ ): Promise<string>;
16
+
17
+ updateActivity(
18
+ activityId: string,
19
+ contentStateJson: string,
20
+ alertConfigJson: string | null,
21
+ ): Promise<void>;
22
+
23
+ endActivity(
24
+ activityId: string,
25
+ finalContentStateJson: string | null,
26
+ dismissalPolicy: string,
27
+ dismissAfter: number,
28
+ ): Promise<void>;
29
+
30
+ endAllActivities(
31
+ dismissalPolicy: string,
32
+ dismissAfter: number,
33
+ ): Promise<void>;
34
+
35
+ // Queries
36
+ getActivity(activityId: string): Promise<string | null>;
37
+ listActivities(): Promise<string>;
38
+
39
+ // Push tokens
40
+ getPushToken(activityId: string): Promise<string | null>;
41
+
42
+ // Events
43
+ addListener(eventName: string): void;
44
+ removeListeners(count: number): void;
45
+ }
46
+
47
+ export default TurboModuleRegistry.getEnforcing<Spec>(
48
+ 'IdealystLiveActivity',
49
+ );