expo-invoke 2.6.14 → 2.7.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/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 +54 -25
- package/build/plugin/ios/withIOSInvoke.js.map +1 -1
- package/build/plugin/ios/withWidgetExtension.d.ts +5 -0
- package/build/plugin/ios/withWidgetExtension.js +36 -6
- 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 +11 -1
- 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,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
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import WidgetKit
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
func parseTimeline(identifier: String, name: String, family: WidgetFamily) -> [WidgetsTimelineEntry] {
|
|
6
|
+
guard let timeline = WidgetsStorage.getArray(forKey: "__expo_widgets_\(name)_timeline") else {
|
|
7
|
+
return [WidgetsTimelineEntry(date: Date(), name: name, props: nil, entryIndex: nil)]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let entries: [WidgetsTimelineEntry?] = timeline.enumerated().map { index, entry in
|
|
11
|
+
if let entry = entry as? [String: Any], let timestamp = entry["timestamp"] as? Int, let props = entry["props"] as? [String: Any] {
|
|
12
|
+
return WidgetsTimelineEntry(
|
|
13
|
+
date: Date(timeIntervalSince1970: Double(timestamp) / 1000),
|
|
14
|
+
name: name,
|
|
15
|
+
props: props,
|
|
16
|
+
entryIndex: index
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
return nil
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return entries.compactMap(\.self)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public func createRedBox(message: String, stack: String? = nil) -> [String: Any] {
|
|
26
|
+
var props: [String: Any] = ["message": message]
|
|
27
|
+
if let stack {
|
|
28
|
+
props["stack"] = stack
|
|
29
|
+
}
|
|
30
|
+
return ["type": "RedBoxView", "props": props]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public func evaluateLayout(
|
|
34
|
+
layout: String,
|
|
35
|
+
props: [String: Any]?,
|
|
36
|
+
environment: [String: Any]
|
|
37
|
+
) -> [String: Any] {
|
|
38
|
+
switch evaluateWidgetLayout(layout: layout, props: props, environment: environment) {
|
|
39
|
+
case .success(let result):
|
|
40
|
+
return result
|
|
41
|
+
case .failure(let error):
|
|
42
|
+
print("[ExpoWidgets] Layout evaluation failed: \(error.message)")
|
|
43
|
+
return createRedBox(message: error.message)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func getLiveActivityNodes(forName name: String, props: String? = nil, environment: [String: Any]) -> [String: Any] {
|
|
48
|
+
let layout = WidgetsStorage.getString(forKey: "__expo_widgets_live_activity_\(name)_layout") ?? ""
|
|
49
|
+
let propsDict = props.flatMap { props in
|
|
50
|
+
props.data(using: .utf8).flatMap {
|
|
51
|
+
try? JSONSerialization.jsonObject(with: $0, options: []) as? [String: Any]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
switch evaluateWidgetLayout(layout: layout, props: propsDict, environment: environment) {
|
|
56
|
+
case .success(let result):
|
|
57
|
+
return result
|
|
58
|
+
case .failure(let error):
|
|
59
|
+
print("[ExpoWidgets] Layout evaluation failed: \(error.message)")
|
|
60
|
+
return ["banner": createRedBox(message: error.message)]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func getLiveActivityUrl(forName name: String) -> URL? {
|
|
65
|
+
guard let urlString = WidgetsStorage.getString(forKey: "__expo_widgets_live_activity_\(name)_url") else {
|
|
66
|
+
return nil
|
|
67
|
+
}
|
|
68
|
+
return URL(string: urlString)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public func getWidgetEnvironment(environment: EnvironmentValues) -> [String: Any] {
|
|
72
|
+
var env: [String: Any] = [
|
|
73
|
+
"showsContainerBackground": environment.showsWidgetContainerBackground,
|
|
74
|
+
"widgetFamily": environment.widgetFamily.description,
|
|
75
|
+
"colorScheme": "\(environment.colorScheme)"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
if #available(iOS 16.0, *) {
|
|
79
|
+
env["isLuminanceReduced"] = environment.isLuminanceReduced
|
|
80
|
+
env["widgetRenderingMode"] = environment.widgetRenderingMode.description
|
|
81
|
+
env["showsWidgetLabel"] = environment.showsWidgetLabel
|
|
82
|
+
}
|
|
83
|
+
if #available(iOS 17.0, *) {
|
|
84
|
+
env["widgetContentMargins"] = [
|
|
85
|
+
"top": environment.widgetContentMargins.top,
|
|
86
|
+
"bottom": environment.widgetContentMargins.bottom,
|
|
87
|
+
"leading": environment.widgetContentMargins.leading,
|
|
88
|
+
"trailing": environment.widgetContentMargins.trailing,
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
if #available(iOS 26.0, *) {
|
|
92
|
+
env["levelOfDetail"] = environment.levelOfDetail == .simplified ? "simplified" : environment.levelOfDetail == .default ? "default" : nil
|
|
93
|
+
}
|
|
94
|
+
return env
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func getLiveActivityEnvironment(environment: EnvironmentValues) -> [String: Any] {
|
|
98
|
+
var env: [String: Any] = [
|
|
99
|
+
"colorScheme": "\(environment.colorScheme)"
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
if #available(iOS 16.0, *) {
|
|
103
|
+
env["isLuminanceReduced"] = environment.isLuminanceReduced
|
|
104
|
+
}
|
|
105
|
+
if #available(iOS 16.1, *) {
|
|
106
|
+
env["isActivityFullscreen"] = environment.isActivityFullscreen
|
|
107
|
+
}
|
|
108
|
+
if #available(iOS 18.0, *) {
|
|
109
|
+
env["isActivityUpdateReduced"] = environment.isActivityUpdateReduced
|
|
110
|
+
env["activityFamily"] = "\(environment.activityFamily)"
|
|
111
|
+
}
|
|
112
|
+
if #available(iOS 26.0, *) {
|
|
113
|
+
env["levelOfDetail"] = environment.levelOfDetail == .simplified ? "simplified" : environment.levelOfDetail == .default ? "default" : nil
|
|
114
|
+
}
|
|
115
|
+
return env
|
|
116
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import WidgetKit
|
|
3
|
+
import ExpoModulesCore
|
|
4
|
+
import ActivityKit
|
|
5
|
+
|
|
6
|
+
struct LiveActivityAttributes: ActivityAttributes {
|
|
7
|
+
public struct ContentState: Codable, Hashable {
|
|
8
|
+
var name: String
|
|
9
|
+
var props: String?
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@available(iOS 16.1, *)
|
|
14
|
+
public struct WidgetLiveActivity: Widget {
|
|
15
|
+
@Environment(\.self) var env
|
|
16
|
+
|
|
17
|
+
let widgetContext: AppContext = AppContext()
|
|
18
|
+
|
|
19
|
+
var environment: [String: Any] {
|
|
20
|
+
return getLiveActivityEnvironment(environment: env)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public init() {}
|
|
24
|
+
|
|
25
|
+
public var body: some WidgetConfiguration {
|
|
26
|
+
ActivityConfiguration(for: LiveActivityAttributes.self) { context in
|
|
27
|
+
let nodes = getLiveActivityNodes(
|
|
28
|
+
forName: context.state.name,
|
|
29
|
+
props: context.state.props,
|
|
30
|
+
environment: environment
|
|
31
|
+
)
|
|
32
|
+
LiveActivityBannerView(context: context, nodes: nodes)
|
|
33
|
+
} dynamicIsland: { context in
|
|
34
|
+
let nodes = getLiveActivityNodes(
|
|
35
|
+
forName: context.state.name,
|
|
36
|
+
props: context.state.props,
|
|
37
|
+
environment: environment
|
|
38
|
+
)
|
|
39
|
+
return DynamicIsland {
|
|
40
|
+
DynamicIslandExpandedRegion(.center) {
|
|
41
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "expandedCenter")
|
|
42
|
+
}
|
|
43
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
44
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "expandedLeading")
|
|
45
|
+
}
|
|
46
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
47
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "expandedTrailing")
|
|
48
|
+
}
|
|
49
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
50
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "expandedBottom")
|
|
51
|
+
}
|
|
52
|
+
} compactLeading: {
|
|
53
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "compactLeading")
|
|
54
|
+
} compactTrailing: {
|
|
55
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "compactTrailing")
|
|
56
|
+
} minimal: {
|
|
57
|
+
LiveActivitySectionView(context: context, nodes: nodes, sectionName: "minimal")
|
|
58
|
+
}
|
|
59
|
+
.widgetURL(getLiveActivityUrl(forName: context.state.name))
|
|
60
|
+
}
|
|
61
|
+
.supplementalActivityFamiliesIfAvailable()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@available(iOS 16.1, *)
|
|
66
|
+
private struct LiveActivitySectionView: View {
|
|
67
|
+
let context: ActivityViewContext<LiveActivityAttributes>
|
|
68
|
+
let nodes: [String: Any]
|
|
69
|
+
let sectionName: String
|
|
70
|
+
|
|
71
|
+
var body: some View {
|
|
72
|
+
if let node = nodes[sectionName] as? [String: Any] {
|
|
73
|
+
WidgetsDynamicView(name: context.activityID, kind: .liveActivity, node: node)
|
|
74
|
+
} else {
|
|
75
|
+
EmptyView()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@available(iOS 16.1, *)
|
|
81
|
+
private struct LiveActivityBannerView: View {
|
|
82
|
+
var context: ActivityViewContext<LiveActivityAttributes>
|
|
83
|
+
let nodes: [String: Any]
|
|
84
|
+
|
|
85
|
+
var body: some View {
|
|
86
|
+
if #available(iOS 18.0, *) {
|
|
87
|
+
LiveActivityBanner(context: context, nodes: nodes)
|
|
88
|
+
} else if let node = nodes["banner"] as? [String: Any] {
|
|
89
|
+
WidgetsDynamicView(name: context.activityID, kind: .liveActivity, node: node)
|
|
90
|
+
} else {
|
|
91
|
+
EmptyView()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
extension WidgetConfiguration {
|
|
97
|
+
func supplementalActivityFamiliesIfAvailable() -> some WidgetConfiguration {
|
|
98
|
+
if #available(iOS 18.0, iOSApplicationExtension 18.0, *) {
|
|
99
|
+
return self.supplementalActivityFamilies([.small, .medium])
|
|
100
|
+
} else {
|
|
101
|
+
return self
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|