expo-invoke 2.6.15 → 2.8.0
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.
- package/android/src/main/java/expo/modules/invoke/ExpoInvokeModule.kt +35 -0
- package/android/src/main/java/expo/modules/invoke/InvokeSliceProvider.kt +9 -0
- package/build/plugin/android/withAndroidInvoke.d.ts +1 -0
- package/build/plugin/android/withAndroidInvoke.js +1 -0
- package/build/plugin/android/withAndroidInvoke.js.map +1 -1
- package/build/plugin/codegen/generateSwiftIntents.js +22 -0
- package/build/plugin/codegen/generateSwiftIntents.js.map +1 -1
- package/build/plugin/codegen/generateSwiftLiveActivities.d.ts +17 -0
- package/build/plugin/codegen/generateSwiftLiveActivities.js +165 -0
- package/build/plugin/codegen/generateSwiftLiveActivities.js.map +1 -0
- package/build/plugin/codegen/generateSwiftWidget.d.ts +5 -0
- package/build/plugin/codegen/generateSwiftWidget.js.map +1 -1
- package/build/plugin/ios/widgets/withAppGroupEntitlements.d.ts +6 -0
- package/build/plugin/ios/widgets/withAppGroupEntitlements.js +27 -0
- package/build/plugin/ios/widgets/withAppGroupEntitlements.js.map +1 -0
- package/build/plugin/ios/widgets/withAppInfoPlist.d.ts +7 -0
- package/build/plugin/ios/widgets/withAppInfoPlist.js +12 -0
- package/build/plugin/ios/widgets/withAppInfoPlist.js.map +1 -0
- package/build/plugin/ios/widgets/withEasConfig.d.ts +8 -0
- package/build/plugin/ios/widgets/withEasConfig.js +52 -0
- package/build/plugin/ios/widgets/withEasConfig.js.map +1 -0
- package/build/plugin/ios/widgets/withIosWarning.d.ts +7 -0
- package/build/plugin/ios/widgets/withIosWarning.js +10 -0
- package/build/plugin/ios/widgets/withIosWarning.js.map +1 -0
- package/build/plugin/ios/widgets/withIosWidgets.d.ts +11 -0
- package/build/plugin/ios/widgets/withIosWidgets.js +83 -0
- package/build/plugin/ios/widgets/withIosWidgets.js.map +1 -0
- package/build/plugin/ios/widgets/withPodsLinking.d.ts +6 -0
- package/build/plugin/ios/widgets/withPodsLinking.js +102 -0
- package/build/plugin/ios/widgets/withPodsLinking.js.map +1 -0
- package/build/plugin/ios/widgets/withPushNotifications.d.ts +6 -0
- package/build/plugin/ios/widgets/withPushNotifications.js +12 -0
- package/build/plugin/ios/widgets/withPushNotifications.js.map +1 -0
- package/build/plugin/ios/widgets/withWidgetSourceFiles.d.ts +10 -0
- package/build/plugin/ios/widgets/withWidgetSourceFiles.js +379 -0
- package/build/plugin/ios/widgets/withWidgetSourceFiles.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addBuildPhases.d.ts +12 -0
- package/build/plugin/ios/widgets/xcode/addBuildPhases.js +41 -0
- package/build/plugin/ios/widgets/xcode/addBuildPhases.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addPbxGroup.d.ts +5 -0
- package/build/plugin/ios/widgets/xcode/addPbxGroup.js +20 -0
- package/build/plugin/ios/widgets/xcode/addPbxGroup.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addProductFile.d.ts +7 -0
- package/build/plugin/ios/widgets/xcode/addProductFile.js +41 -0
- package/build/plugin/ios/widgets/xcode/addProductFile.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addTargetDependency.d.ts +4 -0
- package/build/plugin/ios/widgets/xcode/addTargetDependency.js +17 -0
- package/build/plugin/ios/widgets/xcode/addTargetDependency.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addToPbxNativeTargetSection.d.ts +14 -0
- package/build/plugin/ios/widgets/xcode/addToPbxNativeTargetSection.js +29 -0
- package/build/plugin/ios/widgets/xcode/addToPbxNativeTargetSection.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addToPbxProjectSection.d.ts +4 -0
- package/build/plugin/ios/widgets/xcode/addToPbxProjectSection.js +20 -0
- package/build/plugin/ios/widgets/xcode/addToPbxProjectSection.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/addXCConfigurationList.d.ts +11 -0
- package/build/plugin/ios/widgets/xcode/addXCConfigurationList.js +37 -0
- package/build/plugin/ios/widgets/xcode/addXCConfigurationList.js.map +1 -0
- package/build/plugin/ios/widgets/xcode/withTargetXcodeProject.d.ts +10 -0
- package/build/plugin/ios/widgets/xcode/withTargetXcodeProject.js +86 -0
- package/build/plugin/ios/widgets/xcode/withTargetXcodeProject.js.map +1 -0
- package/build/plugin/ios/withIOSInvoke.d.ts +6 -15
- package/build/plugin/ios/withIOSInvoke.js +57 -26
- package/build/plugin/ios/withIOSInvoke.js.map +1 -1
- package/build/plugin/ios/withWidgetExtension.d.ts +5 -0
- package/build/plugin/ios/withWidgetExtension.js +5 -0
- package/build/plugin/ios/withWidgetExtension.js.map +1 -1
- package/build/plugin/src/withInvoke.d.ts +34 -23
- package/build/plugin/src/withInvoke.js +16 -27
- package/build/plugin/src/withInvoke.js.map +1 -1
- package/build/plugin/types/WidgetConfig.type.d.ts +54 -0
- package/build/plugin/types/WidgetConfig.type.js +3 -0
- package/build/plugin/types/WidgetConfig.type.js.map +1 -0
- package/build/plugin/types/WidgetFamily.type.d.ts +10 -0
- package/build/plugin/types/WidgetFamily.type.js +13 -0
- package/build/plugin/types/WidgetFamily.type.js.map +1 -0
- package/build/src/hooks/useLiveActivity.d.ts +20 -10
- package/build/src/hooks/useLiveActivity.d.ts.map +1 -1
- package/build/src/hooks/useLiveActivity.js +20 -10
- package/build/src/hooks/useLiveActivity.js.map +1 -1
- package/build/src/index.d.ts +2 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +5 -0
- package/build/src/index.js.map +1 -1
- package/build/src/module.d.ts +4 -0
- package/build/src/module.d.ts.map +1 -1
- package/build/src/module.js +8 -0
- package/build/src/module.js.map +1 -1
- package/build/src/web/index.d.ts +10 -1
- package/build/src/web/index.d.ts.map +1 -1
- package/build/src/web/index.js +4 -0
- package/build/src/web/index.js.map +1 -1
- package/build/src/widgets/ExpoWidgets.d.ts +12 -0
- package/build/src/widgets/ExpoWidgets.d.ts.map +1 -0
- package/build/src/widgets/ExpoWidgets.ios.d.ts +12 -0
- package/build/src/widgets/ExpoWidgets.ios.d.ts.map +1 -0
- package/build/src/widgets/ExpoWidgets.ios.js +3 -0
- package/build/src/widgets/ExpoWidgets.ios.js.map +1 -0
- package/build/src/widgets/ExpoWidgets.js +42 -0
- package/build/src/widgets/ExpoWidgets.js.map +1 -0
- package/build/src/widgets/Widgets.d.ts +119 -0
- package/build/src/widgets/Widgets.d.ts.map +1 -0
- package/build/src/widgets/Widgets.js +158 -0
- package/build/src/widgets/Widgets.js.map +1 -0
- package/build/src/widgets/Widgets.types.d.ts +263 -0
- package/build/src/widgets/Widgets.types.d.ts.map +1 -0
- package/build/src/widgets/Widgets.types.js +2 -0
- package/build/src/widgets/Widgets.types.js.map +1 -0
- package/build/src/widgets/index.d.ts +3 -0
- package/build/src/widgets/index.d.ts.map +1 -0
- package/build/src/widgets/index.js +2 -0
- package/build/src/widgets/index.js.map +1 -0
- package/bundle/__tests__/decorator.test.ts +89 -0
- package/bundle/decorator.ts +61 -0
- package/bundle/expo-modules-core-stub.ts +1 -0
- package/bundle/expo-stub.ts +15 -0
- package/bundle/index.ts +69 -0
- package/bundle/jsx-runtime-stub.ts +71 -0
- package/bundle/react-native-stub.ts +3 -0
- package/bundle/react-stub.ts +13 -0
- package/expo-module.config.json +1 -1
- package/ios/LiveActivity/LiveActivityManager.swift +15 -14
- package/ios/Module/ExpoInvokeModule.swift +124 -0
- package/ios/Widget/WidgetManager.swift +25 -8
- package/ios/Widgets/LiveActivity.swift +81 -0
- package/ios/Widgets/LiveActivityFactory.swift +49 -0
- package/ios/Widgets/Widget/AppIntent.swift +130 -0
- package/ios/Widgets/Widget/Buttons.swift +62 -0
- package/ios/Widgets/Widget/DynamicView.swift +161 -0
- package/ios/Widgets/Widget/EntryView.swift +36 -0
- package/ios/Widgets/Widget/Fragment.swift +13 -0
- package/ios/Widgets/Widget/LiveActivityBanner.swift +19 -0
- package/ios/Widgets/Widget/RedBoxView.swift +83 -0
- package/ios/Widgets/Widget/TimelineEntry.swift +8 -0
- package/ios/Widgets/Widget/TimelineProvider.swift +45 -0
- package/ios/Widgets/Widget/Type.swift +6 -0
- package/ios/Widgets/Widget/Utils.swift +116 -0
- package/ios/Widgets/Widget/WidgetLiveActivity.swift +104 -0
- package/ios/Widgets/Widget/WidgetsJSRuntime.swift +148 -0
- package/ios/Widgets/WidgetObject.swift +31 -0
- package/ios/Widgets/WidgetsContext.swift +7 -0
- package/ios/Widgets/WidgetsEvents.swift +16 -0
- package/ios/Widgets/WidgetsExceptions.swift +25 -0
- package/ios/Widgets/WidgetsModule.swift +149 -0
- package/ios/Widgets/WidgetsRecords.swift +22 -0
- package/ios/Widgets/WidgetsStorage.swift +58 -0
- package/metro.widget.config.js +76 -0
- package/package.json +12 -2
- package/scripts/autolinking.rb +34 -0
- package/scripts/build-bundle.mjs +69 -0
- package/scripts/with-node.sh +44 -0
- package/scripts/xcode-build-bundle.sh +23 -0
- package/spm.config.json +40 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import ActivityKit
|
|
3
|
+
|
|
4
|
+
final class LiveActivityFactory: SharedObject {
|
|
5
|
+
let name: String
|
|
6
|
+
|
|
7
|
+
static var pushNotificationsEnabled: Bool {
|
|
8
|
+
Bundle.main.object(forInfoDictionaryKey: pushNotificationsEnabledKey) as? Bool ?? false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
init(name: String, layout: String) {
|
|
12
|
+
self.name = name
|
|
13
|
+
WidgetsStorage.set(layout, forKey: "__expo_widgets_live_activity_\(name)_layout")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func start(props: String?, url: URL?) throws -> LiveActivity {
|
|
17
|
+
guard #available(iOS 16.2, *) else { throw LiveActivitiesNotSupportedException() }
|
|
18
|
+
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
|
|
19
|
+
throw LiveActivitiesNotSupportedException()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if let url {
|
|
23
|
+
WidgetsStorage.set(url.absoluteString, forKey: "__expo_widgets_live_activity_\(name)_url")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
do {
|
|
27
|
+
let initialState = LiveActivityAttributes.ContentState(name: name, props: props)
|
|
28
|
+
let activity = try Activity.request(
|
|
29
|
+
attributes: LiveActivityAttributes(),
|
|
30
|
+
content: .init(state: initialState, staleDate: nil),
|
|
31
|
+
pushType: LiveActivityFactory.pushNotificationsEnabled ? .token : nil
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
let instance = LiveActivity(id: activity.id, name: name)
|
|
35
|
+
instance.observePushTokenUpdates(for: activity, pushNotificationsEnabled: LiveActivityFactory.pushNotificationsEnabled)
|
|
36
|
+
return instance
|
|
37
|
+
} catch {
|
|
38
|
+
throw StartLiveActivityException(error.localizedDescription)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func getInstances() throws -> [LiveActivity] {
|
|
43
|
+
guard #available(iOS 16.1, *) else { throw LiveActivitiesNotSupportedException() }
|
|
44
|
+
|
|
45
|
+
return Activity<LiveActivityAttributes>.activities.map { activity in
|
|
46
|
+
LiveActivity(id: activity.id, name: name)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import AppIntents
|
|
2
|
+
import WidgetKit
|
|
3
|
+
|
|
4
|
+
struct WidgetReload: AppIntent {
|
|
5
|
+
// title is not used for non-discoverable intents, but it is required
|
|
6
|
+
static var title: LocalizedStringResource = "Reload widget"
|
|
7
|
+
static var isDiscoverable: Bool = false
|
|
8
|
+
@Parameter(title: "source")
|
|
9
|
+
var source: String?
|
|
10
|
+
|
|
11
|
+
init() {}
|
|
12
|
+
init(source: String?) {
|
|
13
|
+
self.source = source
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func perform() async throws -> some IntentResult {
|
|
17
|
+
guard let source else {
|
|
18
|
+
return .result()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
WidgetCenter.shared.reloadTimelines(ofKind: source)
|
|
22
|
+
return .result()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@available(iOS 16.0, *)
|
|
27
|
+
struct WidgetUserInteraction: AppIntent {
|
|
28
|
+
// title is not used for non-discoverable intents, but it is required
|
|
29
|
+
static var title: LocalizedStringResource = "User Interaction"
|
|
30
|
+
static var isDiscoverable: Bool = false
|
|
31
|
+
@Parameter(title: "source")
|
|
32
|
+
var source: String?
|
|
33
|
+
|
|
34
|
+
@Parameter(title: "target")
|
|
35
|
+
var target: String?
|
|
36
|
+
|
|
37
|
+
@Parameter(title: "entryIndex")
|
|
38
|
+
var entryIndex: Int?
|
|
39
|
+
|
|
40
|
+
@Parameter(title: "environmentString")
|
|
41
|
+
var environmentString: String?
|
|
42
|
+
|
|
43
|
+
init() {}
|
|
44
|
+
init(source: String?, target: String?, entryIndex: Int?, environmentString: String?) {
|
|
45
|
+
self.source = source
|
|
46
|
+
self.target = target
|
|
47
|
+
self.entryIndex = entryIndex
|
|
48
|
+
self.environmentString = environmentString
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func perform() async throws -> some IntentResult {
|
|
52
|
+
guard let source else {
|
|
53
|
+
return .result()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let layout = WidgetsStorage.getString(forKey: "__expo_widgets_\(source)_layout") ?? ""
|
|
57
|
+
let timeline = WidgetsStorage.getArray(forKey: "__expo_widgets_\(source)_timeline")
|
|
58
|
+
|
|
59
|
+
guard let timeline,
|
|
60
|
+
let entryIndex,
|
|
61
|
+
timeline.indices.contains(entryIndex),
|
|
62
|
+
let entry = timeline[entryIndex] as? [String: Any],
|
|
63
|
+
let props = entry["props"] as? [String: Any],
|
|
64
|
+
let environmentData = environmentString?.data(using: .utf8),
|
|
65
|
+
var environment = try? JSONSerialization.jsonObject(with: environmentData) as? [String: Any] else {
|
|
66
|
+
return .result()
|
|
67
|
+
}
|
|
68
|
+
environment["target"] = target
|
|
69
|
+
|
|
70
|
+
let newProps: [String: Any]?
|
|
71
|
+
switch evaluateWidgetButtonPress(layout: layout, props: props, environment: environment) {
|
|
72
|
+
case .success(let result):
|
|
73
|
+
newProps = result
|
|
74
|
+
case .failure(let error):
|
|
75
|
+
print("[ExpoWidgets] Button press evaluation failed: \(error.message)")
|
|
76
|
+
newProps = nil
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if let newProps {
|
|
80
|
+
var newEntry = entry
|
|
81
|
+
if let originalProps = entry["props"] as? [String: Any] {
|
|
82
|
+
newEntry["props"] = originalProps.merging(newProps) { _, new in new }
|
|
83
|
+
}
|
|
84
|
+
guard var newTimeline = timeline as? [[String: Any]] else {
|
|
85
|
+
return .result()
|
|
86
|
+
}
|
|
87
|
+
newTimeline[entryIndex] = newEntry
|
|
88
|
+
WidgetsStorage.set(newTimeline, forKey: "__expo_widgets_\(source)_timeline")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
WidgetsEvents.shared.sendNotification(type: .userEvent, data: [
|
|
92
|
+
"source": source as Any,
|
|
93
|
+
"target": target as Any,
|
|
94
|
+
"timestamp": Int(Date().timeIntervalSince1970 * 1000)
|
|
95
|
+
])
|
|
96
|
+
|
|
97
|
+
WidgetCenter.shared.reloadTimelines(ofKind: source)
|
|
98
|
+
|
|
99
|
+
return .result()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@available(iOS 16.0, *)
|
|
104
|
+
struct LiveActivityUserInteraction: LiveActivityIntent {
|
|
105
|
+
// title is not used for non-discoverable intents, but it is required
|
|
106
|
+
static var title: LocalizedStringResource = "User Interaction"
|
|
107
|
+
static var isDiscoverable: Bool = false
|
|
108
|
+
|
|
109
|
+
@Parameter(title: "source")
|
|
110
|
+
var source: String?
|
|
111
|
+
|
|
112
|
+
@Parameter(title: "target")
|
|
113
|
+
var target: String?
|
|
114
|
+
|
|
115
|
+
init() {}
|
|
116
|
+
init(source: String?, target: String?) {
|
|
117
|
+
self.source = source
|
|
118
|
+
self.target = target
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func perform() async throws -> some IntentResult {
|
|
122
|
+
WidgetsEvents.shared.sendNotification(type: .userEvent, data: [
|
|
123
|
+
"source": source as Any,
|
|
124
|
+
"target": target as Any,
|
|
125
|
+
"timestamp": Int(Date().timeIntervalSince1970 * 1000)
|
|
126
|
+
])
|
|
127
|
+
|
|
128
|
+
return .result()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
import ExpoUI
|
|
4
|
+
|
|
5
|
+
final class ButtonProps: ExpoUI.ButtonProps {
|
|
6
|
+
@Field var source: String?
|
|
7
|
+
@Field var target: String?
|
|
8
|
+
@Field var entryIndex: Int?
|
|
9
|
+
@Field var environmentString: String?
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@available(iOS 17.0, *)
|
|
13
|
+
struct WidgetButtonView: ExpoSwiftUI.View {
|
|
14
|
+
@ObservedObject var props: ButtonProps
|
|
15
|
+
|
|
16
|
+
var body: some View {
|
|
17
|
+
SwiftUI.Button(
|
|
18
|
+
role: props.role?.toNativeRole(),
|
|
19
|
+
intent: WidgetUserInteraction(
|
|
20
|
+
source: props.source,
|
|
21
|
+
target: props.target,
|
|
22
|
+
entryIndex: props.entryIndex,
|
|
23
|
+
environmentString: props.environmentString
|
|
24
|
+
)
|
|
25
|
+
) {
|
|
26
|
+
if let label = props.label {
|
|
27
|
+
if let systemImage = props.systemImage {
|
|
28
|
+
Label(title: { Text(label) }, icon: { Image(systemName: systemImage) })
|
|
29
|
+
} else {
|
|
30
|
+
Text(label)
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
Children()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@available(iOS 17.0, *)
|
|
40
|
+
struct LiveActivityButtonView: ExpoSwiftUI.View {
|
|
41
|
+
@ObservedObject var props: ButtonProps
|
|
42
|
+
|
|
43
|
+
var body: some View {
|
|
44
|
+
SwiftUI.Button(
|
|
45
|
+
role: props.role?.toNativeRole(),
|
|
46
|
+
intent: LiveActivityUserInteraction(
|
|
47
|
+
source: props.source,
|
|
48
|
+
target: props.target
|
|
49
|
+
)
|
|
50
|
+
) {
|
|
51
|
+
if let label = props.label {
|
|
52
|
+
if let systemImage = props.systemImage {
|
|
53
|
+
Label(title: { Text(label) }, icon: { Image(systemName: systemImage) })
|
|
54
|
+
} else {
|
|
55
|
+
Text(label)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
Children()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
import ExpoUI
|
|
4
|
+
|
|
5
|
+
// TODO(@jakex7): Hack to satisfy ExpoSwiftUI.AnyChild with random UUID value
|
|
6
|
+
class NodeIdentityWrapper {
|
|
7
|
+
let id: UUID
|
|
8
|
+
init(id: UUID) {
|
|
9
|
+
self.id = id
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
extension ObjectIdentifier: @retroactive Encodable {
|
|
13
|
+
public func encode(to encoder: Encoder) throws {
|
|
14
|
+
var container = encoder.singleValueContainer()
|
|
15
|
+
try container.encode(String(describing: self))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public struct WidgetsDynamicView: View, ExpoSwiftUI.AnyChild {
|
|
20
|
+
let node: [String: Any]
|
|
21
|
+
let name: String
|
|
22
|
+
let kind: WidgetsKind
|
|
23
|
+
let entryIndex: Int?
|
|
24
|
+
let environmentString: String?
|
|
25
|
+
|
|
26
|
+
let uuid = NodeIdentityWrapper(id: UUID())
|
|
27
|
+
public var id: ObjectIdentifier {
|
|
28
|
+
ObjectIdentifier(uuid)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public init(name: String, kind: WidgetsKind, node: [String: Any]) {
|
|
32
|
+
self.name = name
|
|
33
|
+
self.kind = kind
|
|
34
|
+
self.node = node
|
|
35
|
+
self.entryIndex = nil
|
|
36
|
+
self.environmentString = nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public init(name: String, kind: WidgetsKind, node: [String: Any], entryIndex: Int?, environmentString: String?) {
|
|
40
|
+
self.name = name
|
|
41
|
+
self.kind = kind
|
|
42
|
+
self.node = node
|
|
43
|
+
self.entryIndex = entryIndex
|
|
44
|
+
self.environmentString = environmentString
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@ViewBuilder
|
|
48
|
+
public var body: some View {
|
|
49
|
+
switch node["type"] as? String {
|
|
50
|
+
case "TextView":
|
|
51
|
+
render(TextView.self, TextViewProps.self, updateProps: updateChildren)
|
|
52
|
+
case "HStackView":
|
|
53
|
+
render(HStackView.self, HStackViewProps.self, updateProps: updateChildren)
|
|
54
|
+
case "VStackView":
|
|
55
|
+
render(VStackView.self, VStackViewProps.self, updateProps: updateChildren)
|
|
56
|
+
case "ZStackView":
|
|
57
|
+
render(ZStackView.self, ZStackViewProps.self, updateProps: updateChildren)
|
|
58
|
+
case "RectangleView":
|
|
59
|
+
render(RectangleView.self, RectangleViewProps.self)
|
|
60
|
+
case "RoundedRectangleView":
|
|
61
|
+
render(RoundedRectangleView.self, RoundedRectangleViewProps.self)
|
|
62
|
+
case "CapsuleView":
|
|
63
|
+
render(CapsuleView.self, CapsuleViewProps.self)
|
|
64
|
+
case "CircleView":
|
|
65
|
+
render(CircleView.self, CircleViewProps.self)
|
|
66
|
+
case "ImageView":
|
|
67
|
+
render(ImageView.self, ImageViewProps.self)
|
|
68
|
+
case "AccessoryWidgetBackgroundView":
|
|
69
|
+
render(AccessoryWidgetBackgroundView.self, AccessoryWidgetBackgroundProps.self)
|
|
70
|
+
case "DividerView":
|
|
71
|
+
render(DividerView.self, DividerProps.self)
|
|
72
|
+
case "EllipseView":
|
|
73
|
+
render(EllipseView.self, EllipseViewProps.self)
|
|
74
|
+
case "LabelView":
|
|
75
|
+
render(LabelView.self, LabelViewProps.self)
|
|
76
|
+
case "ProgressView":
|
|
77
|
+
render(ProgressView.self, ProgressViewProps.self)
|
|
78
|
+
case "SpacerView":
|
|
79
|
+
render(SpacerView.self, SpacerViewProps.self)
|
|
80
|
+
case "UnevenRoundedRectangleView":
|
|
81
|
+
render(UnevenRoundedRectangleView.self, UnevenRoundedRectangleViewProps.self)
|
|
82
|
+
case "GaugeView":
|
|
83
|
+
render(GaugeView.self, GaugeProps.self)
|
|
84
|
+
case "ChartView":
|
|
85
|
+
render(ChartView.self, ChartProps.self)
|
|
86
|
+
case "Button":
|
|
87
|
+
if #available(iOS 17.0, *) {
|
|
88
|
+
switch kind {
|
|
89
|
+
case .widget:
|
|
90
|
+
render(WidgetButtonView.self, ButtonProps.self) { buttonProps in
|
|
91
|
+
try updateChildren(buttonProps)
|
|
92
|
+
buttonProps.source = name
|
|
93
|
+
buttonProps.entryIndex = entryIndex
|
|
94
|
+
buttonProps.environmentString = environmentString
|
|
95
|
+
}
|
|
96
|
+
case .liveActivity:
|
|
97
|
+
render(LiveActivityButtonView.self, ButtonProps.self) { buttonProps in
|
|
98
|
+
try updateChildren(buttonProps)
|
|
99
|
+
buttonProps.source = name
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
render(ExpoUI.Button.self, ExpoUI.ButtonProps.self, updateProps: updateChildren)
|
|
104
|
+
}
|
|
105
|
+
case "react.fragment":
|
|
106
|
+
render(FragmentView.self, FragmentProps.self, updateProps: updateChildren)
|
|
107
|
+
case "LinkView":
|
|
108
|
+
render(LinkView.self, LinkViewProps.self, updateProps: updateChildren)
|
|
109
|
+
#if DEBUG
|
|
110
|
+
case "RedBoxView":
|
|
111
|
+
render(RedBoxView.self, RedBoxViewProps.self) { redBoxProps in
|
|
112
|
+
redBoxProps.source = name
|
|
113
|
+
redBoxProps.kind = kind
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
ZStack {
|
|
117
|
+
Color.red.opacity(0.5)
|
|
118
|
+
Text("Unable to get the view for: \(node["type"] as? String ?? "undefined")")
|
|
119
|
+
}
|
|
120
|
+
#else
|
|
121
|
+
default:
|
|
122
|
+
EmptyView()
|
|
123
|
+
#endif
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// MARK: - Render Method
|
|
128
|
+
|
|
129
|
+
@ViewBuilder
|
|
130
|
+
private func render<P, V>(_ viewType: V.Type, _ propsType: P.Type, updateProps: ((_ initialProps: P) throws -> Void)? = nil) -> some View
|
|
131
|
+
where P: UIBaseViewProps, V: ExpoSwiftUI.View, V.Props == P {
|
|
132
|
+
// immediately invoked closure {}() here because we can't use 'do-catch' inside @ViewBuilder
|
|
133
|
+
{
|
|
134
|
+
do {
|
|
135
|
+
if let rawProps = node["props"] as? [String: Any] {
|
|
136
|
+
let props = try propsType.init(rawProps: rawProps, context: WidgetsContext.shared.context)
|
|
137
|
+
try updateProps?(props)
|
|
138
|
+
// TODO(@jakex7): Prevent unwanted transition when view is updated with new props - we want to have the same view instance recreated with new props instead of creating a new view instance and transitioning to it
|
|
139
|
+
return AnyView(UIBaseView<P, V>(props: props).transition(.identity))
|
|
140
|
+
}
|
|
141
|
+
return AnyView(EmptyView())
|
|
142
|
+
} catch {
|
|
143
|
+
return AnyView(EmptyView())
|
|
144
|
+
}
|
|
145
|
+
}()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// MARK: - Function that sets children as DynamicView
|
|
149
|
+
|
|
150
|
+
private func updateChildren<P>(_ initialProps: P) throws
|
|
151
|
+
where P: UIBaseViewProps {
|
|
152
|
+
if let props = node["props"] as? [String: Any] {
|
|
153
|
+
if let children = props["children"] as? [Any] {
|
|
154
|
+
let validChildren = children.compactMap { $0 as? [String: Any] }
|
|
155
|
+
initialProps.children = validChildren.map { WidgetsDynamicView(name: name, kind: kind, node: $0, entryIndex: entryIndex, environmentString: environmentString) }
|
|
156
|
+
} else if let child = props["children"] as? [String: Any] {
|
|
157
|
+
initialProps.children = [WidgetsDynamicView(name: name, kind: kind, node: child, entryIndex: entryIndex, environmentString: environmentString)]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
import WidgetKit
|
|
4
|
+
|
|
5
|
+
public struct WidgetsEntryView: View {
|
|
6
|
+
@Environment(\.self) var environment
|
|
7
|
+
var entry: WidgetsTimelineProvider.Entry
|
|
8
|
+
|
|
9
|
+
public init(entry: WidgetsTimelineProvider.Entry) {
|
|
10
|
+
self.entry = entry
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private var widgetEnvironment: [String: Any] {
|
|
14
|
+
var env: [String: Any] = getWidgetEnvironment(environment: environment)
|
|
15
|
+
env["timestamp"] = Int(entry.date.timeIntervalSince1970 * 1000)
|
|
16
|
+
return env
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private var widgetEnvironmentString: String? {
|
|
20
|
+
guard let data = try? JSONSerialization.data(withJSONObject: widgetEnvironment),
|
|
21
|
+
let jsonString = String(data: data, encoding: .utf8) else {
|
|
22
|
+
return nil
|
|
23
|
+
}
|
|
24
|
+
return jsonString
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public var body: some View {
|
|
28
|
+
if let layout = WidgetsStorage.getString(forKey: "__expo_widgets_\(entry.name)_layout"),
|
|
29
|
+
!layout.isEmpty {
|
|
30
|
+
let node = evaluateLayout(layout: layout, props: entry.props, environment: widgetEnvironment)
|
|
31
|
+
WidgetsDynamicView(name: entry.name, kind: .widget, node: node, entryIndex: entry.entryIndex, environmentString: widgetEnvironmentString)
|
|
32
|
+
} else {
|
|
33
|
+
WidgetsDynamicView(name: entry.name, kind: .widget, node: createRedBox(message: "No layout found for \(WidgetsStorage.appGroupIdentifier ?? "")::\(entry.name)"), entryIndex: entry.entryIndex, environmentString: widgetEnvironmentString)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import WidgetKit
|
|
3
|
+
|
|
4
|
+
@available(iOS 18.0, *)
|
|
5
|
+
struct LiveActivityBanner: View {
|
|
6
|
+
@Environment(\.activityFamily) var activityFamily
|
|
7
|
+
var context: ActivityViewContext<LiveActivityAttributes>
|
|
8
|
+
var nodes: [String: Any]?
|
|
9
|
+
|
|
10
|
+
var body: some View {
|
|
11
|
+
if activityFamily == .small, let node = nodes?["bannerSmall"] as? [String: Any] {
|
|
12
|
+
WidgetsDynamicView(name: context.activityID, kind: .liveActivity, node: node)
|
|
13
|
+
} else if let node = nodes?["banner"] as? [String: Any] {
|
|
14
|
+
WidgetsDynamicView(name: context.activityID, kind: .liveActivity, node: node)
|
|
15
|
+
} else {
|
|
16
|
+
EmptyView()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import SwiftUI
|
|
3
|
+
import ExpoUI
|
|
4
|
+
|
|
5
|
+
public final class RedBoxViewProps: UIBaseViewProps {
|
|
6
|
+
@Field var message: String
|
|
7
|
+
@Field var source: String?
|
|
8
|
+
@Field var stack: String?
|
|
9
|
+
var kind: WidgetsKind = .widget
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public struct RedBoxView: ExpoSwiftUI.View {
|
|
13
|
+
@ObservedObject public var props: RedBoxViewProps
|
|
14
|
+
|
|
15
|
+
public init(props: RedBoxViewProps) {
|
|
16
|
+
self.props = props
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public var body: some View {
|
|
20
|
+
FullSizeZStack(kind: props.kind) {
|
|
21
|
+
Color.red
|
|
22
|
+
VStack(alignment: .leading, spacing: 2) {
|
|
23
|
+
HStack(spacing: 6) {
|
|
24
|
+
Image(systemName: "exclamationmark.triangle.fill")
|
|
25
|
+
Text("Error").font(.headline)
|
|
26
|
+
Spacer()
|
|
27
|
+
if #available(iOS 17.0, *) {
|
|
28
|
+
Button(intent: WidgetReload(source: props.source)) {
|
|
29
|
+
Image(systemName: "arrow.clockwise")
|
|
30
|
+
}
|
|
31
|
+
.buttonStyle(.plain)
|
|
32
|
+
.padding(4)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
.foregroundStyle(.white)
|
|
36
|
+
|
|
37
|
+
Text(props.message)
|
|
38
|
+
.font(.system(size: 14).bold().monospaced())
|
|
39
|
+
.foregroundStyle(.white.opacity(0.9))
|
|
40
|
+
.modifier(RedBoxBody())
|
|
41
|
+
|
|
42
|
+
if let stack = props.stack, !stack.isEmpty {
|
|
43
|
+
Text(stack)
|
|
44
|
+
.font(.system(size: 11).monospaced())
|
|
45
|
+
.foregroundStyle(.white.opacity(0.75))
|
|
46
|
+
.modifier(RedBoxBody())
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
.padding(12)
|
|
50
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private struct RedBoxBody: ViewModifier {
|
|
56
|
+
@ViewBuilder
|
|
57
|
+
func body(content: Content) -> some View {
|
|
58
|
+
let base = content.lineLimit(nil).fixedSize(horizontal: false, vertical: true)
|
|
59
|
+
if #available(iOS 26.0, *) {
|
|
60
|
+
base.lineHeight(.tight)
|
|
61
|
+
} else {
|
|
62
|
+
base
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public struct FullSizeZStack<Content: View>: View {
|
|
68
|
+
let kind: WidgetsKind
|
|
69
|
+
let content: Content
|
|
70
|
+
|
|
71
|
+
init(kind: WidgetsKind = .widget, @ViewBuilder _ content: () -> Content) {
|
|
72
|
+
self.kind = kind
|
|
73
|
+
self.content = content()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public var body: some View {
|
|
77
|
+
if #available(iOS 17.0, *), kind == .widget {
|
|
78
|
+
ZStack { content }.containerRelativeFrame([.horizontal, .vertical])
|
|
79
|
+
} else {
|
|
80
|
+
ZStack { content }.frame(maxWidth: .infinity)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import WidgetKit
|
|
2
|
+
|
|
3
|
+
public struct WidgetsTimelineProvider: TimelineProvider {
|
|
4
|
+
public func placeholder(in context: Context) -> WidgetsTimelineEntry {
|
|
5
|
+
WidgetsTimelineEntry(date: Date(), name: name, props: nil, entryIndex: nil)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
public func getSnapshot(
|
|
9
|
+
in context: Context, completion: @escaping @Sendable (WidgetsTimelineEntry) -> Void
|
|
10
|
+
) {
|
|
11
|
+
let groupIdentifier =
|
|
12
|
+
Bundle.main.object(forInfoDictionaryKey: "ExpoWidgetsAppGroupIdentifier") as? String
|
|
13
|
+
guard let groupIdentifier else {
|
|
14
|
+
completion(WidgetsTimelineEntry(date: Date(), name: name, props: nil, entryIndex: nil))
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let entries = parseTimeline(identifier: groupIdentifier, name: name, family: context.family)
|
|
19
|
+
completion(entries.first ?? WidgetsTimelineEntry(date: Date(), name: name, props: nil, entryIndex: nil))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public func getTimeline(
|
|
23
|
+
in context: Context,
|
|
24
|
+
completion: @escaping @Sendable (Timeline<WidgetsTimelineEntry>) -> Void
|
|
25
|
+
) {
|
|
26
|
+
let groupIdentifier =
|
|
27
|
+
Bundle.main.object(forInfoDictionaryKey: "ExpoWidgetsAppGroupIdentifier") as? String
|
|
28
|
+
guard let groupIdentifier else {
|
|
29
|
+
fatalError("Could not get the app group identifier from Info.plist")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let entries = parseTimeline(identifier: groupIdentifier, name: name, family: context.family)
|
|
33
|
+
|
|
34
|
+
let timeline = Timeline<WidgetsTimelineEntry>(entries: entries, policy: .atEnd)
|
|
35
|
+
completion(timeline)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public typealias Entry = WidgetsTimelineEntry
|
|
39
|
+
|
|
40
|
+
let name: String
|
|
41
|
+
|
|
42
|
+
public init(name: String) {
|
|
43
|
+
self.name = name
|
|
44
|
+
}
|
|
45
|
+
}
|