@use-voltra/ios-client 2.2.0 → 2.3.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/Voltra.podspec +1 -0
- package/build/commonjs/events.js +23 -0
- package/build/commonjs/events.js.map +1 -1
- package/build/commonjs/index.js +59 -0
- package/build/commonjs/index.js.map +1 -1
- package/build/commonjs/live-activity/api.js +3 -1
- package/build/commonjs/live-activity/api.js.map +1 -1
- package/build/commonjs/live-activity/dynamic-api.js +173 -0
- package/build/commonjs/live-activity/dynamic-api.js.map +1 -0
- package/build/commonjs/native/NativeVoltra.js.map +1 -1
- package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js +47 -0
- package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
- package/build/commonjs/utils/enableWidgetHotReload.js +8 -11
- package/build/commonjs/utils/enableWidgetHotReload.js.map +1 -1
- package/build/commonjs/utils/useUpdateOnHMR.js +3 -7
- package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -1
- package/build/commonjs/widgets/server-credentials.js +9 -0
- package/build/commonjs/widgets/server-credentials.js.map +1 -1
- package/build/commonjs/widgets/server-update.js +78 -0
- package/build/commonjs/widgets/server-update.js.map +1 -0
- package/build/module/events.js +23 -0
- package/build/module/events.js.map +1 -1
- package/build/module/index.js +3 -0
- package/build/module/index.js.map +1 -1
- package/build/module/live-activity/api.js +2 -2
- package/build/module/live-activity/api.js.map +1 -1
- package/build/module/live-activity/dynamic-api.js +167 -0
- package/build/module/live-activity/dynamic-api.js.map +1 -0
- package/build/module/native/NativeVoltra.js.map +1 -1
- package/build/module/utils/enableDynamicLiveActivityHotReload.js +43 -0
- package/build/module/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
- package/build/module/utils/enableWidgetHotReload.js +9 -11
- package/build/module/utils/enableWidgetHotReload.js.map +1 -1
- package/build/module/utils/useUpdateOnHMR.js +3 -7
- package/build/module/utils/useUpdateOnHMR.js.map +1 -1
- package/build/module/widgets/server-credentials.js +9 -0
- package/build/module/widgets/server-credentials.js.map +1 -1
- package/build/module/widgets/server-update.js +72 -0
- package/build/module/widgets/server-update.js.map +1 -0
- package/build/typescript/commonjs/events.d.ts +7 -0
- package/build/typescript/commonjs/events.d.ts.map +1 -1
- package/build/typescript/commonjs/index.d.ts +3 -0
- package/build/typescript/commonjs/index.d.ts.map +1 -1
- package/build/typescript/commonjs/live-activity/api.d.ts +7 -0
- package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -1
- package/build/typescript/commonjs/live-activity/dynamic-api.d.ts +54 -0
- package/build/typescript/commonjs/live-activity/dynamic-api.d.ts.map +1 -0
- package/build/typescript/commonjs/native/NativeVoltra.d.ts +20 -0
- package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -1
- package/build/typescript/commonjs/types.d.ts +1 -1
- package/build/typescript/commonjs/types.d.ts.map +1 -1
- package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
- package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
- package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts +5 -7
- package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts.map +1 -1
- package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts +0 -3
- package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -1
- package/build/typescript/commonjs/widgets/server-credentials.d.ts +8 -0
- package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -1
- package/build/typescript/commonjs/widgets/server-update.d.ts +64 -0
- package/build/typescript/commonjs/widgets/server-update.d.ts.map +1 -0
- package/build/typescript/module/events.d.ts +7 -0
- package/build/typescript/module/events.d.ts.map +1 -1
- package/build/typescript/module/index.d.ts +3 -0
- package/build/typescript/module/index.d.ts.map +1 -1
- package/build/typescript/module/live-activity/api.d.ts +7 -0
- package/build/typescript/module/live-activity/api.d.ts.map +1 -1
- package/build/typescript/module/live-activity/dynamic-api.d.ts +54 -0
- package/build/typescript/module/live-activity/dynamic-api.d.ts.map +1 -0
- package/build/typescript/module/native/NativeVoltra.d.ts +20 -0
- package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -1
- package/build/typescript/module/types.d.ts +1 -1
- package/build/typescript/module/types.d.ts.map +1 -1
- package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
- package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
- package/build/typescript/module/utils/enableWidgetHotReload.d.ts +5 -7
- package/build/typescript/module/utils/enableWidgetHotReload.d.ts.map +1 -1
- package/build/typescript/module/utils/useUpdateOnHMR.d.ts +0 -3
- package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -1
- package/build/typescript/module/widgets/server-credentials.d.ts +8 -0
- package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -1
- package/build/typescript/module/widgets/server-update.d.ts +64 -0
- package/build/typescript/module/widgets/server-update.d.ts.map +1 -0
- package/expo-plugin/build/cjs/constants.js +20 -1
- package/expo-plugin/build/cjs/constants.js.map +1 -1
- package/expo-plugin/build/cjs/index.js +6 -0
- package/expo-plugin/build/cjs/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios/index.js +1 -0
- package/expo-plugin/build/cjs/ios/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios/infoPlist.js +28 -5
- package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -1
- package/expo-plugin/build/cjs/ios/serverUpdate.js +25 -0
- package/expo-plugin/build/cjs/ios/serverUpdate.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/clientRendered.js +4 -4
- package/expo-plugin/build/cjs/ios-widget/clientRendered.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js +5 -5
- package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js +130 -0
- package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/index.js +9 -2
- package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +6 -3
- package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/files/manifest.js +24 -0
- package/expo-plugin/build/cjs/ios-widget/files/manifest.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js +7 -0
- package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/swift.js +56 -26
- package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/index.js +9 -3
- package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +34 -22
- package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +51 -11
- package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/xcode/index.js +21 -5
- package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -1
- package/expo-plugin/build/cjs/utils/urlScheme.js +36 -14
- package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -1
- package/expo-plugin/build/cjs/validation.js +54 -0
- package/expo-plugin/build/cjs/validation.js.map +1 -1
- package/expo-plugin/build/esm/constants.js +17 -0
- package/expo-plugin/build/esm/constants.js.map +1 -1
- package/expo-plugin/build/esm/index.js +6 -0
- package/expo-plugin/build/esm/index.js.map +1 -1
- package/expo-plugin/build/esm/ios/index.js +1 -0
- package/expo-plugin/build/esm/ios/index.js.map +1 -1
- package/expo-plugin/build/esm/ios/infoPlist.js +28 -5
- package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -1
- package/expo-plugin/build/esm/ios/serverUpdate.js +21 -0
- package/expo-plugin/build/esm/ios/serverUpdate.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/clientRendered.js +5 -5
- package/expo-plugin/build/esm/ios-widget/clientRendered.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js +6 -6
- package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js +122 -0
- package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/index.js +10 -3
- package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +6 -3
- package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/files/manifest.js +23 -1
- package/expo-plugin/build/esm/ios-widget/files/manifest.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/files/swift-utils.js +4 -0
- package/expo-plugin/build/esm/ios-widget/files/swift-utils.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/swift.js +50 -20
- package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/index.js +9 -3
- package/expo-plugin/build/esm/ios-widget/index.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/widgetPlist.js +34 -22
- package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +51 -11
- package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/xcode/index.js +21 -5
- package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -1
- package/expo-plugin/build/esm/utils/urlScheme.js +34 -14
- package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -1
- package/expo-plugin/build/esm/validation.js +54 -1
- package/expo-plugin/build/esm/validation.js.map +1 -1
- package/expo-plugin/build/types/constants.d.ts +10 -1
- package/expo-plugin/build/types/constants.d.ts.map +1 -1
- package/expo-plugin/build/types/index.d.ts +1 -1
- package/expo-plugin/build/types/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios/index.d.ts +2 -0
- package/expo-plugin/build/types/ios/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios/infoPlist.d.ts +2 -0
- package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -1
- package/expo-plugin/build/types/ios/serverUpdate.d.ts +11 -0
- package/expo-plugin/build/types/ios/serverUpdate.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/clientRendered.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/clientRenderedPrerender.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts +7 -0
- package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/index.d.ts +3 -1
- package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +2 -1
- package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/files/manifest.d.ts +9 -2
- package/expo-plugin/build/types/ios-widget/files/manifest.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts +2 -0
- package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/swift.d.ts +6 -2
- package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/index.d.ts +3 -1
- package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +3 -1
- package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +2 -1
- package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -1
- package/expo-plugin/build/types/types.d.ts +23 -4
- package/expo-plugin/build/types/types.d.ts.map +1 -1
- package/expo-plugin/build/types/utils/urlScheme.d.ts +16 -0
- package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -1
- package/expo-plugin/build/types/validation.d.ts +2 -1
- package/expo-plugin/build/types/validation.d.ts.map +1 -1
- package/expo-plugin/src/constants.ts +24 -1
- package/expo-plugin/src/index.ts +9 -0
- package/expo-plugin/src/ios/index.ts +3 -0
- package/expo-plugin/src/ios/infoPlist.ts +33 -5
- package/expo-plugin/src/ios/serverUpdate.ts +29 -0
- package/expo-plugin/src/ios-widget/clientRendered.ts +9 -5
- package/expo-plugin/src/ios-widget/clientRenderedPrerender.ts +11 -6
- package/expo-plugin/src/ios-widget/dynamic-live-activity/swift.ts +138 -0
- package/expo-plugin/src/ios-widget/files/index.ts +15 -4
- package/expo-plugin/src/ios-widget/files/infoPlist.ts +17 -3
- package/expo-plugin/src/ios-widget/files/manifest.ts +42 -2
- package/expo-plugin/src/ios-widget/files/swift-utils.ts +3 -0
- package/expo-plugin/src/ios-widget/files/swift.ts +69 -22
- package/expo-plugin/src/ios-widget/index.ts +13 -3
- package/expo-plugin/src/ios-widget/widgetPlist.ts +37 -23
- package/expo-plugin/src/ios-widget/xcode/__snapshots__/applyXcodeChanges.node.test.ts.snap +34 -28
- package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +59 -12
- package/expo-plugin/src/ios-widget/xcode/index.ts +23 -6
- package/expo-plugin/src/types.ts +28 -4
- package/expo-plugin/src/utils/urlScheme.ts +47 -17
- package/expo-plugin/src/validation.ts +76 -1
- package/ios/Package.swift +32 -3
- package/ios/VoltraWidget.podspec +1 -0
- package/ios/app/NativeVoltra.mm +107 -7
- package/ios/app/VoltraLiveActivityManager.swift +30 -0
- package/ios/app/VoltraLiveActivityService.swift +77 -5
- package/ios/app/VoltraModule.swift +99 -1
- package/ios/app/VoltraModuleImpl.swift +100 -2
- package/ios/app/VoltraViewComponentView.mm +3 -3
- package/ios/app/VoltraWidgetService.swift +147 -12
- package/ios/app/dynamic-live-activity/VoltraDynamicLiveActivityService.swift +97 -0
- package/ios/app/dynamic-live-activity/VoltraLiveActivityChronology.swift +68 -0
- package/ios/shared/DynamicWidgetPropsStore.swift +73 -6
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerFetchResolver.swift +34 -0
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerProps.swift +55 -0
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerPropsStore.swift +146 -0
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerUpdateRunner.swift +193 -0
- package/ios/shared/ServerWidgetContentResolver.swift +60 -0
- package/ios/shared/ServerWidgetResponseStore.swift +25 -0
- package/ios/shared/ShortNames.swift +6 -0
- package/ios/shared/VoltraConfig.swift +41 -0
- package/ios/shared/VoltraConstants.swift +15 -0
- package/ios/shared/VoltraElement.swift +127 -43
- package/ios/shared/VoltraEventBus.swift +52 -7
- package/ios/shared/VoltraJSRenderer.swift +90 -22
- package/ios/shared/VoltraNode.swift +1 -109
- package/ios/shared/VoltraRefreshIntent.swift +1 -1
- package/ios/shared/VoltraWidgetKind.swift +41 -0
- package/ios/shared/VoltraWidgetServerFetcher.swift +52 -107
- package/ios/shared/WidgetServer/VoltraWidgetAppearance.swift +17 -0
- package/ios/shared/WidgetServer/VoltraWidgetServer.swift +138 -0
- package/ios/shared/WidgetServer/WidgetCanonicalConfiguration.swift +77 -0
- package/ios/shared/WidgetServer/WidgetScope.swift +55 -0
- package/ios/shared/WidgetServer/WidgetServerEtagStore.swift +55 -0
- package/ios/shared/WidgetServer/WidgetServerFetcher.swift +179 -0
- package/ios/shared/WidgetServer/WidgetServerRequestBuilder.swift +114 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsCodec.swift +47 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsResolver.swift +129 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsStore.swift +170 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsValidator.swift +73 -0
- package/ios/shared/WidgetServer/WidgetServerUpdateSettings.swift +123 -0
- package/ios/shared/WidgetServer/WidgetServerUpdateSettingsJson.swift +115 -0
- package/ios/shared/dynamic-live-activity/DynamicLiveActivityPropsCodec.swift +13 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityBundleSource.swift +156 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityObserver.swift +97 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityOperations.swift +105 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityPayloadValidator.swift +55 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRegistry.swift +143 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureQueue.swift +158 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureReporter.swift +120 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderer.swift +370 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityTypes.swift +87 -0
- package/ios/shared/dynamic-live-activity/VoltraLiveActivityOrder.swift +19 -0
- package/ios/target/VoltraClientWidgetRuntime.swift +44 -6
- package/ios/target/VoltraDynamicWidgetServerUpdateProvider.swift +204 -0
- package/ios/target/VoltraHomeWidget.swift +85 -90
- package/ios/ui/Extensions/VoltraNode+View.swift +113 -0
- package/ios/ui/Generated/Parameters/ChartParameters.swift +3 -0
- package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +2 -2
- package/ios/ui/Views/VoltraChart.swift +108 -0
- package/package.json +4 -4
- package/src/events.ts +34 -0
- package/src/index.ts +22 -0
- package/src/live-activity/api.ts +2 -2
- package/src/live-activity/dynamic-api.ts +240 -0
- package/src/native/NativeVoltra.ts +21 -0
- package/src/types.ts +4 -0
- package/src/utils/enableDynamicLiveActivityHotReload.ts +50 -0
- package/src/utils/enableWidgetHotReload.ts +9 -16
- package/src/utils/useUpdateOnHMR.ts +3 -12
- package/src/widgets/server-credentials.ts +8 -0
- package/src/widgets/server-update.ts +84 -0
- /package/ios/shared/{VoltraKeychainHelper.swift → WidgetServer/VoltraKeychainHelper.swift} +0 -0
|
@@ -11,17 +11,20 @@ public struct VoltraElement: Hashable {
|
|
|
11
11
|
/// Child nodes or text content
|
|
12
12
|
public let children: VoltraNode?
|
|
13
13
|
|
|
14
|
-
/// Raw properties (stored as JSONValue for type safety)
|
|
14
|
+
/// Raw properties (stored as JSONValue for type safety).
|
|
15
|
+
///
|
|
16
|
+
/// Stylesheet indices and shared element references are substituted for the values they point
|
|
17
|
+
/// at while parsing, so this holds no handle into a per-payload lookup table.
|
|
15
18
|
private let _props: [String: JSONValue]?
|
|
16
19
|
|
|
17
|
-
///
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/// Optional shared elements for resolving element references
|
|
21
|
-
public let sharedElements: [JSONValue]?
|
|
20
|
+
/// Style dictionary with expanded keys, resolved once while parsing.
|
|
21
|
+
private let _style: [String: JSONValue]?
|
|
22
22
|
|
|
23
23
|
// MARK: - Hashable
|
|
24
24
|
|
|
25
|
+
/// `_props` carries fully resolved values, so it alone distinguishes elements that render
|
|
26
|
+
/// differently. Identity deliberately omits `_style`, which is derived from `_props` and would
|
|
27
|
+
/// only restate it.
|
|
25
28
|
public func hash(into hasher: inout Hasher) {
|
|
26
29
|
hasher.combine(type)
|
|
27
30
|
hasher.combine(id)
|
|
@@ -52,39 +55,15 @@ public struct VoltraElement: Hashable {
|
|
|
52
55
|
|
|
53
56
|
/// Style dictionary with expanded keys
|
|
54
57
|
public var style: [String: JSONValue]? {
|
|
55
|
-
|
|
56
|
-
return nil
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let styleDict: [String: JSONValue]
|
|
60
|
-
|
|
61
|
-
// Handle stylesheet reference (integer index)
|
|
62
|
-
if let index = styleValue.intValue,
|
|
63
|
-
let stylesheet = stylesheet,
|
|
64
|
-
index >= 0, index < stylesheet.count
|
|
65
|
-
{
|
|
66
|
-
styleDict = stylesheet[index]
|
|
67
|
-
}
|
|
68
|
-
// Handle inline style (object)
|
|
69
|
-
else if let objectValue = styleValue.objectValue {
|
|
70
|
-
styleDict = objectValue
|
|
71
|
-
} else {
|
|
72
|
-
return nil
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
var expanded: [String: JSONValue] = [:]
|
|
76
|
-
for (key, value) in styleDict {
|
|
77
|
-
// Use unified ShortNames mapping for style properties
|
|
78
|
-
let expandedKey = ShortNames.expand(key)
|
|
79
|
-
expanded[expandedKey] = value
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return expanded
|
|
58
|
+
_style
|
|
83
59
|
}
|
|
84
60
|
|
|
85
61
|
// MARK: - Initialization
|
|
86
62
|
|
|
87
63
|
/// Initialize from JSONValue (no serialization roundtrip)
|
|
64
|
+
///
|
|
65
|
+
/// The stylesheet and shared elements are consumed here rather than retained: every reference
|
|
66
|
+
/// into them is replaced by the value it names, leaving a self-contained element.
|
|
88
67
|
/// - Parameters:
|
|
89
68
|
/// - json: The JSON value to parse
|
|
90
69
|
/// - stylesheet: Optional shared stylesheet for style deduplication
|
|
@@ -112,25 +91,28 @@ public struct VoltraElement: Hashable {
|
|
|
112
91
|
children = nil
|
|
113
92
|
}
|
|
114
93
|
|
|
115
|
-
// Extract props
|
|
94
|
+
// Extract props, substituting the values behind every stylesheet index and shared element
|
|
95
|
+
// reference so the element stops depending on lookup tables it does not carry.
|
|
96
|
+
let resolvedProps: [String: JSONValue]?
|
|
116
97
|
if let propsValue = dict["p"], case let .object(propsDict) = propsValue {
|
|
117
|
-
|
|
98
|
+
resolvedProps = Self.resolvingReferences(
|
|
99
|
+
inProps: propsDict,
|
|
100
|
+
stylesheet: stylesheet,
|
|
101
|
+
sharedElements: sharedElements
|
|
102
|
+
)
|
|
118
103
|
} else {
|
|
119
|
-
|
|
104
|
+
resolvedProps = nil
|
|
120
105
|
}
|
|
121
106
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// Store shared elements reference
|
|
126
|
-
self.sharedElements = sharedElements
|
|
107
|
+
_props = resolvedProps
|
|
108
|
+
_style = Self.expandedStyle(from: resolvedProps)
|
|
127
109
|
}
|
|
128
110
|
|
|
129
111
|
/// Get component prop by name - handles both single component and array
|
|
130
112
|
public func componentProp(_ propName: String) -> VoltraNode {
|
|
131
113
|
guard let propValue = props?[propName] else { return .empty }
|
|
132
114
|
|
|
133
|
-
return VoltraNode(from: propValue
|
|
115
|
+
return VoltraNode(from: propValue)
|
|
134
116
|
}
|
|
135
117
|
|
|
136
118
|
/// Decode parameters from props
|
|
@@ -150,4 +132,106 @@ public struct VoltraElement: Hashable {
|
|
|
150
132
|
return try! JSONDecoder().decode(T.self, from: "{}".data(using: .utf8)!)
|
|
151
133
|
}
|
|
152
134
|
}
|
|
135
|
+
|
|
136
|
+
// MARK: - Reference Resolution
|
|
137
|
+
|
|
138
|
+
/// Substitutes the stylesheet entry behind a style index, and the shared element behind a
|
|
139
|
+
/// `$r` reference, throughout a props dictionary.
|
|
140
|
+
///
|
|
141
|
+
/// Element-valued props carry serialized nodes of their own, and those nodes reference the
|
|
142
|
+
/// same payload-level tables. Leaving them unresolved would make two elements with equal
|
|
143
|
+
/// props compare equal while rendering differently.
|
|
144
|
+
/// The renderer registers shared elements only after rendering their contents, so generated
|
|
145
|
+
/// references point to completed entries and cannot form cycles.
|
|
146
|
+
private static func resolvingReferences(
|
|
147
|
+
inProps props: [String: JSONValue],
|
|
148
|
+
stylesheet: [[String: JSONValue]]?,
|
|
149
|
+
sharedElements: [JSONValue]?
|
|
150
|
+
) -> [String: JSONValue] {
|
|
151
|
+
guard stylesheet != nil || sharedElements != nil else {
|
|
152
|
+
return props
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var resolved = props
|
|
156
|
+
for (key, value) in props {
|
|
157
|
+
if ShortNames.expand(key) == "style" {
|
|
158
|
+
if let index = value.intValue,
|
|
159
|
+
let stylesheet = stylesheet,
|
|
160
|
+
index >= 0, index < stylesheet.count
|
|
161
|
+
{
|
|
162
|
+
resolved[key] = .object(stylesheet[index])
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
resolved[key] = resolvingReferences(
|
|
166
|
+
in: value,
|
|
167
|
+
stylesheet: stylesheet,
|
|
168
|
+
sharedElements: sharedElements
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return resolved
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Resolves references inside a value that may hold serialized nodes.
|
|
176
|
+
private static func resolvingReferences(
|
|
177
|
+
in value: JSONValue,
|
|
178
|
+
stylesheet: [[String: JSONValue]]?,
|
|
179
|
+
sharedElements: [JSONValue]?
|
|
180
|
+
) -> JSONValue {
|
|
181
|
+
switch value {
|
|
182
|
+
case let .array(items):
|
|
183
|
+
return .array(items.map {
|
|
184
|
+
resolvingReferences(in: $0, stylesheet: stylesheet, sharedElements: sharedElements)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
case let .object(dict):
|
|
188
|
+
// Element reference ($r key) - substitute the shared element it names
|
|
189
|
+
if let refIndex = dict["$r"]?.intValue,
|
|
190
|
+
let sharedElements = sharedElements,
|
|
191
|
+
refIndex >= 0, refIndex < sharedElements.count
|
|
192
|
+
{
|
|
193
|
+
return resolvingReferences(
|
|
194
|
+
in: sharedElements[refIndex],
|
|
195
|
+
stylesheet: stylesheet,
|
|
196
|
+
sharedElements: sharedElements
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
var resolved = dict
|
|
201
|
+
if let propsValue = dict["p"], case let .object(propsDict) = propsValue {
|
|
202
|
+
resolved["p"] = .object(resolvingReferences(
|
|
203
|
+
inProps: propsDict,
|
|
204
|
+
stylesheet: stylesheet,
|
|
205
|
+
sharedElements: sharedElements
|
|
206
|
+
))
|
|
207
|
+
}
|
|
208
|
+
if let childrenValue = dict["c"] {
|
|
209
|
+
resolved["c"] = resolvingReferences(
|
|
210
|
+
in: childrenValue,
|
|
211
|
+
stylesheet: stylesheet,
|
|
212
|
+
sharedElements: sharedElements
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
return .object(resolved)
|
|
216
|
+
|
|
217
|
+
default:
|
|
218
|
+
return value
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Expands the short keys of an already resolved style prop into full property names.
|
|
223
|
+
private static func expandedStyle(from props: [String: JSONValue]?) -> [String: JSONValue]? {
|
|
224
|
+
guard let styleValue = props?["s"] ?? props?["style"],
|
|
225
|
+
let styleDict = styleValue.objectValue
|
|
226
|
+
else {
|
|
227
|
+
return nil
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
var expanded: [String: JSONValue] = [:]
|
|
231
|
+
for (key, value) in styleDict {
|
|
232
|
+
// Use unified ShortNames mapping for style properties
|
|
233
|
+
expanded[ShortNames.expand(key)] = value
|
|
234
|
+
}
|
|
235
|
+
return expanded
|
|
236
|
+
}
|
|
153
237
|
}
|
|
@@ -8,6 +8,9 @@ public class VoltraEventBus {
|
|
|
8
8
|
public static let shared = VoltraEventBus()
|
|
9
9
|
|
|
10
10
|
private var observer: NSObjectProtocol?
|
|
11
|
+
private var renderFailureObserver: UUID?
|
|
12
|
+
private var handler: ((String, [String: Any]) -> Void)?
|
|
13
|
+
private var renderFailureListenerCount = 0
|
|
11
14
|
private let lock = NSLock()
|
|
12
15
|
|
|
13
16
|
private init() {}
|
|
@@ -39,20 +42,18 @@ public class VoltraEventBus {
|
|
|
39
42
|
/// - Parameter handler: A closure that receives the event name and event data dictionary
|
|
40
43
|
public func subscribe(handler: @escaping (String, [String: Any]) -> Void) {
|
|
41
44
|
lock.lock()
|
|
42
|
-
defer { lock.unlock() }
|
|
43
|
-
|
|
44
45
|
if let observer {
|
|
45
46
|
NotificationCenter.default.removeObserver(observer)
|
|
46
47
|
self.observer = nil
|
|
47
48
|
}
|
|
49
|
+
if let renderFailureObserver {
|
|
50
|
+
VoltraDynamicLiveActivityRenderFailureReporter.removeChangeObserver(renderFailureObserver)
|
|
51
|
+
self.renderFailureObserver = nil
|
|
52
|
+
}
|
|
53
|
+
self.handler = handler
|
|
48
54
|
|
|
49
55
|
// 1. Replay persisted events from UserDefaults (interactions from widget)
|
|
50
56
|
let persistedEvents = VoltraPersistentEventQueue.popAll()
|
|
51
|
-
for event in persistedEvents {
|
|
52
|
-
handler(event.name, event.data)
|
|
53
|
-
}
|
|
54
|
-
VoltraLogger.event.info("Replayed \(persistedEvents.count) persisted events")
|
|
55
|
-
|
|
56
57
|
// 2. Listen for all events via NotificationCenter (hot delivery)
|
|
57
58
|
observer = NotificationCenter.default.addObserver(
|
|
58
59
|
forName: .voltraEvent,
|
|
@@ -66,6 +67,44 @@ public class VoltraEventBus {
|
|
|
66
67
|
}
|
|
67
68
|
handler(eventName, userInfo)
|
|
68
69
|
}
|
|
70
|
+
renderFailureObserver = VoltraDynamicLiveActivityRenderFailureReporter.observeChanges { [weak self] in
|
|
71
|
+
self?.drainDynamicLiveActivityRenderFailures()
|
|
72
|
+
}
|
|
73
|
+
lock.unlock()
|
|
74
|
+
|
|
75
|
+
for event in persistedEvents {
|
|
76
|
+
handler(event.name, event.data)
|
|
77
|
+
}
|
|
78
|
+
VoltraLogger.event.info("Replayed \(persistedEvents.count) persisted events")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// JS owns the dedicated emitter subscription count. Persisted failures must
|
|
82
|
+
/// remain queued whenever there is no relevant JavaScript listener.
|
|
83
|
+
public func setDynamicLiveActivityRenderFailureListenerActive(_ active: Bool) {
|
|
84
|
+
lock.lock()
|
|
85
|
+
renderFailureListenerCount = active ? 1 : 0
|
|
86
|
+
lock.unlock()
|
|
87
|
+
if active {
|
|
88
|
+
drainDynamicLiveActivityRenderFailures()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Flush only the dedicated Dynamic Live Activity diagnostic queue. This
|
|
93
|
+
/// cannot read, displace, or clear persistent interaction events.
|
|
94
|
+
public func drainDynamicLiveActivityRenderFailures() {
|
|
95
|
+
lock.lock()
|
|
96
|
+
let handler = handler
|
|
97
|
+
let isReady = renderFailureListenerCount > 0
|
|
98
|
+
lock.unlock()
|
|
99
|
+
guard isReady, let handler else { return }
|
|
100
|
+
|
|
101
|
+
let failures = VoltraDynamicLiveActivityRenderFailureReporter.drain()
|
|
102
|
+
for failure in failures {
|
|
103
|
+
handler(failure.type, failure.dictionary)
|
|
104
|
+
}
|
|
105
|
+
if !failures.isEmpty {
|
|
106
|
+
VoltraLogger.event.info("Replayed \(failures.count) Dynamic Live Activity render failures")
|
|
107
|
+
}
|
|
69
108
|
}
|
|
70
109
|
|
|
71
110
|
/// Unsubscribe from Voltra events
|
|
@@ -77,6 +116,12 @@ public class VoltraEventBus {
|
|
|
77
116
|
NotificationCenter.default.removeObserver(observer)
|
|
78
117
|
self.observer = nil
|
|
79
118
|
}
|
|
119
|
+
if let renderFailureObserver {
|
|
120
|
+
VoltraDynamicLiveActivityRenderFailureReporter.removeChangeObserver(renderFailureObserver)
|
|
121
|
+
self.renderFailureObserver = nil
|
|
122
|
+
}
|
|
123
|
+
handler = nil
|
|
124
|
+
renderFailureListenerCount = 0
|
|
80
125
|
}
|
|
81
126
|
|
|
82
127
|
deinit {
|
|
@@ -15,6 +15,7 @@ import JavaScriptCore
|
|
|
15
15
|
/// indefinitely.
|
|
16
16
|
public enum VoltraJSRenderer {
|
|
17
17
|
private static var _context: JSContext?
|
|
18
|
+
private static var liveActivitySources: [String: String] = [:]
|
|
18
19
|
private static let lock = NSLock()
|
|
19
20
|
private static let TAG = "VoltraJSRenderer"
|
|
20
21
|
|
|
@@ -30,6 +31,28 @@ public enum VoltraJSRenderer {
|
|
|
30
31
|
/// Idempotent: re-evaluating the same widget overwrites the captured exports — used
|
|
31
32
|
/// by dev-mode hot-reload (always-refetch policy).
|
|
32
33
|
public static func evaluateBundle(source: String, widgetId: String) -> Bool {
|
|
34
|
+
evaluateBundle(source: source, id: widgetId, registryName: "__voltraWidgets", kind: "widget")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Evaluate a Dynamic Live Activity bundle in the shared JSContext. Its exports
|
|
38
|
+
/// are captured separately from Dynamic Widgets so both collections may use the
|
|
39
|
+
/// same definition ID without overwriting one another.
|
|
40
|
+
public static func evaluateLiveActivityBundle(source: String, definitionId: String) -> Bool {
|
|
41
|
+
let evaluated = evaluateBundle(
|
|
42
|
+
source: source,
|
|
43
|
+
id: definitionId,
|
|
44
|
+
registryName: "__voltraDynamicLiveActivities",
|
|
45
|
+
kind: "live activity"
|
|
46
|
+
)
|
|
47
|
+
if evaluated {
|
|
48
|
+
lock.lock()
|
|
49
|
+
liveActivitySources[definitionId] = source
|
|
50
|
+
lock.unlock()
|
|
51
|
+
}
|
|
52
|
+
return evaluated
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private static func evaluateBundle(source: String, id: String, registryName: String, kind: String) -> Bool {
|
|
33
56
|
lock.lock()
|
|
34
57
|
defer { lock.unlock() }
|
|
35
58
|
|
|
@@ -38,7 +61,7 @@ public enum VoltraJSRenderer {
|
|
|
38
61
|
return false
|
|
39
62
|
}
|
|
40
63
|
|
|
41
|
-
let escapedId = jsStringLiteral(
|
|
64
|
+
let escapedId = jsStringLiteral(id)
|
|
42
65
|
// Metro emits the bundle's entry invocation as `__r(<entryModuleId>);` near the
|
|
43
66
|
// end of the file (before the sourcemap/sourceURL comments). The entry id is NOT
|
|
44
67
|
// always 0 — when Metro serves multiple widget bundles from the same process, it
|
|
@@ -50,34 +73,34 @@ public enum VoltraJSRenderer {
|
|
|
50
73
|
let wrapped = """
|
|
51
74
|
\(source)
|
|
52
75
|
;(function () {
|
|
53
|
-
if (!globalThis
|
|
54
|
-
globalThis
|
|
76
|
+
if (!globalThis.\(registryName)) { globalThis.\(registryName) = {}; }
|
|
77
|
+
globalThis.\(registryName)[\(escapedId)] = __r(\(entryModuleId));
|
|
55
78
|
})();
|
|
56
79
|
"""
|
|
57
80
|
|
|
58
81
|
ctx.exception = nil
|
|
59
82
|
ctx.evaluateScript(wrapped)
|
|
60
83
|
if let message = exceptionMessage(ctx) {
|
|
61
|
-
VoltraLogger.widget.error("[\(TAG)] Bundle eval failed for
|
|
84
|
+
VoltraLogger.widget.error("[\(TAG)] Bundle eval failed for \(kind) id=\(id): \(message)")
|
|
62
85
|
return false
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
// Verify the bootstrap captured the exports correctly
|
|
66
89
|
guard
|
|
67
|
-
let registry = ctx.objectForKeyedSubscript(
|
|
90
|
+
let registry = ctx.objectForKeyedSubscript(registryName),
|
|
68
91
|
!registry.isUndefined,
|
|
69
|
-
let
|
|
70
|
-
!
|
|
71
|
-
let renderFn =
|
|
92
|
+
let entry = registry.objectForKeyedSubscript(id),
|
|
93
|
+
!entry.isUndefined,
|
|
94
|
+
let renderFn = entry.objectForKeyedSubscript("render"),
|
|
72
95
|
renderFn.isObject,
|
|
73
96
|
renderFn.objectForKeyedSubscript("call") != nil
|
|
74
97
|
else {
|
|
75
|
-
VoltraLogger.widget.error("[\(TAG)] Bundle evaluated but did not expose render() for
|
|
98
|
+
VoltraLogger.widget.error("[\(TAG)] Bundle evaluated but did not expose render() for \(kind) id=\(id)")
|
|
76
99
|
return false
|
|
77
100
|
}
|
|
78
101
|
_ = renderFn
|
|
79
102
|
|
|
80
|
-
VoltraLogger.widget.info("[\(TAG)] Bundle evaluated for
|
|
103
|
+
VoltraLogger.widget.info("[\(TAG)] Bundle evaluated for \(kind) id=\(id) (\(source.count) chars)")
|
|
81
104
|
return true
|
|
82
105
|
}
|
|
83
106
|
|
|
@@ -90,11 +113,31 @@ public enum VoltraJSRenderer {
|
|
|
90
113
|
/// evaluation never ran. The View calls this before `render()` so rendering never depends on
|
|
91
114
|
/// which process evaluated the bundle.
|
|
92
115
|
public static func ensureEvaluated(widgetId: String, source: String) -> Bool {
|
|
116
|
+
ensureEvaluated(source: source, id: widgetId, registryName: "__voltraWidgets", kind: "widget")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public static func ensureLiveActivityEvaluated(definitionId: String, source: String) -> Bool {
|
|
120
|
+
lock.lock()
|
|
121
|
+
let currentSource = liveActivitySources[definitionId]
|
|
122
|
+
let hasRender =
|
|
123
|
+
_context?
|
|
124
|
+
.objectForKeyedSubscript("__voltraDynamicLiveActivities")?
|
|
125
|
+
.objectForKeyedSubscript(definitionId)?
|
|
126
|
+
.objectForKeyedSubscript("render")?
|
|
127
|
+
.isObject ?? false
|
|
128
|
+
lock.unlock()
|
|
129
|
+
if hasRender, currentSource == source {
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
return evaluateLiveActivityBundle(source: source, definitionId: definitionId)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static func ensureEvaluated(source: String, id: String, registryName: String, kind: String) -> Bool {
|
|
93
136
|
lock.lock()
|
|
94
137
|
let alreadyEvaluated =
|
|
95
138
|
_context?
|
|
96
|
-
.objectForKeyedSubscript(
|
|
97
|
-
.objectForKeyedSubscript(
|
|
139
|
+
.objectForKeyedSubscript(registryName)?
|
|
140
|
+
.objectForKeyedSubscript(id)?
|
|
98
141
|
.objectForKeyedSubscript("render")?
|
|
99
142
|
.isObject ?? false
|
|
100
143
|
lock.unlock()
|
|
@@ -102,7 +145,7 @@ public enum VoltraJSRenderer {
|
|
|
102
145
|
if alreadyEvaluated {
|
|
103
146
|
return true
|
|
104
147
|
}
|
|
105
|
-
return evaluateBundle(source: source,
|
|
148
|
+
return evaluateBundle(source: source, id: id, registryName: registryName, kind: kind)
|
|
106
149
|
}
|
|
107
150
|
|
|
108
151
|
/// Invoke the previously-evaluated widget's `render(propsJSON, envJSON)` function and
|
|
@@ -114,39 +157,64 @@ public enum VoltraJSRenderer {
|
|
|
114
157
|
widgetId: String,
|
|
115
158
|
propsJSON: String,
|
|
116
159
|
envJSON: String
|
|
160
|
+
) -> String? {
|
|
161
|
+
render(id: widgetId, propsJSON: propsJSON, envJSON: envJSON, registryName: "__voltraWidgets", kind: "widget")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Render a Dynamic Live Activity from its separately registered definition.
|
|
165
|
+
public static func renderLiveActivity(
|
|
166
|
+
definitionId: String,
|
|
167
|
+
propsJSON: String,
|
|
168
|
+
envJSON: String
|
|
169
|
+
) -> String? {
|
|
170
|
+
render(
|
|
171
|
+
id: definitionId,
|
|
172
|
+
propsJSON: propsJSON,
|
|
173
|
+
envJSON: envJSON,
|
|
174
|
+
registryName: "__voltraDynamicLiveActivities",
|
|
175
|
+
kind: "live activity"
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private static func render(
|
|
180
|
+
id: String,
|
|
181
|
+
propsJSON: String,
|
|
182
|
+
envJSON: String,
|
|
183
|
+
registryName: String,
|
|
184
|
+
kind: String
|
|
117
185
|
) -> String? {
|
|
118
186
|
lock.lock()
|
|
119
187
|
defer { lock.unlock() }
|
|
120
188
|
|
|
121
189
|
guard let ctx = _context else {
|
|
122
|
-
VoltraLogger.widget.error("[\(TAG)] render
|
|
190
|
+
VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id): no JSContext (call evaluateBundle first)")
|
|
123
191
|
return nil
|
|
124
192
|
}
|
|
125
193
|
|
|
126
194
|
guard
|
|
127
|
-
let registry = ctx.objectForKeyedSubscript(
|
|
195
|
+
let registry = ctx.objectForKeyedSubscript(registryName),
|
|
128
196
|
!registry.isUndefined,
|
|
129
|
-
let
|
|
130
|
-
!
|
|
131
|
-
let renderFn =
|
|
197
|
+
let entry = registry.objectForKeyedSubscript(id),
|
|
198
|
+
!entry.isUndefined,
|
|
199
|
+
let renderFn = entry.objectForKeyedSubscript("render"),
|
|
132
200
|
renderFn.isObject
|
|
133
201
|
else {
|
|
134
|
-
VoltraLogger.widget.error("[\(TAG)] render
|
|
202
|
+
VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id): no captured render() — bundle not evaluated?")
|
|
135
203
|
return nil
|
|
136
204
|
}
|
|
137
205
|
|
|
138
206
|
ctx.exception = nil
|
|
139
207
|
guard let result = renderFn.call(withArguments: [propsJSON, envJSON]) else {
|
|
140
|
-
VoltraLogger.widget.error("[\(TAG)] render
|
|
208
|
+
VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id): call returned nil")
|
|
141
209
|
return nil
|
|
142
210
|
}
|
|
143
211
|
if let message = exceptionMessage(ctx) {
|
|
144
|
-
VoltraLogger.widget.error("[\(TAG)] render
|
|
212
|
+
VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id) threw: \(message)")
|
|
145
213
|
return nil
|
|
146
214
|
}
|
|
147
215
|
|
|
148
216
|
guard result.isString else {
|
|
149
|
-
VoltraLogger.widget.error("[\(TAG)] render
|
|
217
|
+
VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id) did not return a string")
|
|
150
218
|
return nil
|
|
151
219
|
}
|
|
152
220
|
return result.toString()
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
-
import SwiftUI
|
|
3
2
|
|
|
4
3
|
/// Represents anything that can be rendered (like ReactNode)
|
|
5
|
-
public indirect enum VoltraNode: Hashable
|
|
4
|
+
public indirect enum VoltraNode: Hashable {
|
|
6
5
|
/// A component element
|
|
7
6
|
case element(VoltraElement)
|
|
8
7
|
|
|
@@ -88,111 +87,4 @@ public indirect enum VoltraNode: Hashable, View {
|
|
|
88
87
|
|
|
89
88
|
return VoltraNode(from: json, stylesheet: stylesheet, sharedElements: sharedElements)
|
|
90
89
|
}
|
|
91
|
-
|
|
92
|
-
// MARK: - View conformance
|
|
93
|
-
|
|
94
|
-
public var body: some View {
|
|
95
|
-
switch self {
|
|
96
|
-
case let .element(element):
|
|
97
|
-
VoltraElementView(element: element)
|
|
98
|
-
case let .array(nodes):
|
|
99
|
-
// Use stable identifiers: prefer element.id, fall back to index
|
|
100
|
-
let items: [(id: String, node: VoltraNode)] = nodes.enumerated().map { offset, node in
|
|
101
|
-
let id: String
|
|
102
|
-
if case let .element(element) = node, let elementId = element.id {
|
|
103
|
-
id = elementId
|
|
104
|
-
} else {
|
|
105
|
-
id = "idx_\(offset)"
|
|
106
|
-
}
|
|
107
|
-
return (id: id, node: node)
|
|
108
|
-
}
|
|
109
|
-
ForEach(items, id: \.id) { item in
|
|
110
|
-
item.node
|
|
111
|
-
}
|
|
112
|
-
case let .text(text):
|
|
113
|
-
Text(text)
|
|
114
|
-
case .empty:
|
|
115
|
-
EmptyView()
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/// View that renders a VoltraElement based on its type
|
|
121
|
-
struct VoltraElementView: View {
|
|
122
|
-
let element: VoltraElement
|
|
123
|
-
|
|
124
|
-
var body: some View {
|
|
125
|
-
switch element.type {
|
|
126
|
-
case "Button":
|
|
127
|
-
VoltraButton(element)
|
|
128
|
-
|
|
129
|
-
case "Link":
|
|
130
|
-
VoltraLink(element)
|
|
131
|
-
|
|
132
|
-
case "VStack":
|
|
133
|
-
VoltraVStack(element)
|
|
134
|
-
|
|
135
|
-
case "HStack":
|
|
136
|
-
VoltraHStack(element)
|
|
137
|
-
|
|
138
|
-
case "View":
|
|
139
|
-
VoltraFlexView(element)
|
|
140
|
-
|
|
141
|
-
case "ZStack":
|
|
142
|
-
VoltraZStack(element)
|
|
143
|
-
|
|
144
|
-
case "Text":
|
|
145
|
-
VoltraText(element)
|
|
146
|
-
|
|
147
|
-
case "Image":
|
|
148
|
-
VoltraImage(element)
|
|
149
|
-
|
|
150
|
-
case "Symbol":
|
|
151
|
-
VoltraSymbol(element)
|
|
152
|
-
|
|
153
|
-
case "Divider":
|
|
154
|
-
VoltraDivider(element)
|
|
155
|
-
|
|
156
|
-
case "Spacer":
|
|
157
|
-
VoltraSpacer(element)
|
|
158
|
-
|
|
159
|
-
case "Label":
|
|
160
|
-
VoltraLabel(element)
|
|
161
|
-
|
|
162
|
-
case "Toggle":
|
|
163
|
-
VoltraToggle(element)
|
|
164
|
-
|
|
165
|
-
case "Gauge":
|
|
166
|
-
VoltraGauge(element)
|
|
167
|
-
|
|
168
|
-
case "LinearProgressView":
|
|
169
|
-
VoltraLinearProgressView(element)
|
|
170
|
-
|
|
171
|
-
case "CircularProgressView":
|
|
172
|
-
VoltraCircularProgressView(element)
|
|
173
|
-
|
|
174
|
-
case "Timer":
|
|
175
|
-
VoltraTimer(element)
|
|
176
|
-
|
|
177
|
-
case "GroupBox":
|
|
178
|
-
VoltraGroupBox(element)
|
|
179
|
-
|
|
180
|
-
case "LinearGradient":
|
|
181
|
-
VoltraLinearGradient(element)
|
|
182
|
-
|
|
183
|
-
case "GlassContainer":
|
|
184
|
-
VoltraGlassContainer(element)
|
|
185
|
-
|
|
186
|
-
case "Mask":
|
|
187
|
-
VoltraMask(element)
|
|
188
|
-
|
|
189
|
-
case "Chart":
|
|
190
|
-
if #available(iOS 16.0, macOS 13.0, *) {
|
|
191
|
-
VoltraChart(element)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
default:
|
|
195
|
-
EmptyView()
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
90
|
}
|
|
@@ -19,7 +19,7 @@ public struct VoltraRefreshIntent: AppIntent {
|
|
|
19
19
|
|
|
20
20
|
public func perform() async throws -> some IntentResult {
|
|
21
21
|
if let widgetId = widgetId {
|
|
22
|
-
let kind =
|
|
22
|
+
let kind = VoltraWidgetKind.kind(for: widgetId)
|
|
23
23
|
WidgetCenter.shared.reloadTimelines(ofKind: kind)
|
|
24
24
|
} else {
|
|
25
25
|
WidgetCenter.shared.reloadAllTimelines()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Maps a widget id to its WidgetKit `kind` and back.
|
|
4
|
+
///
|
|
5
|
+
/// The kind defaults to `Voltra_Widget_<id>`. A widget can pin another kind through the plugin
|
|
6
|
+
/// `kind` option, stored in Info.plist under `Voltra_WidgetKinds` (app and extension), so widgets
|
|
7
|
+
/// placed before a migration to Voltra keep their identity.
|
|
8
|
+
///
|
|
9
|
+
/// This is the only place either half of that mapping is derived; nothing else should reach for
|
|
10
|
+
/// `VoltraStorageKeys.widgetKindPrefix`.
|
|
11
|
+
public enum VoltraWidgetKind {
|
|
12
|
+
/// Info.plist cannot change for the life of the process, so the map is read once.
|
|
13
|
+
private static let mainOverrides = VoltraConfig.widgetKinds()
|
|
14
|
+
|
|
15
|
+
public static func kind(for widgetId: String) -> String {
|
|
16
|
+
kind(for: widgetId, overrides: mainOverrides)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Returns nil when the kind does not belong to a Voltra widget.
|
|
20
|
+
public static func widgetId(for kind: String) -> String? {
|
|
21
|
+
widgetId(for: kind, overrides: mainOverrides)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Pure half of `kind(for:)`, split out so the mapping is covered without building a bundle.
|
|
25
|
+
static func kind(for widgetId: String, overrides: [String: String]) -> String {
|
|
26
|
+
overrides[widgetId] ?? "\(VoltraStorageKeys.widgetKindPrefix)\(widgetId)"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Pure half of `widgetId(for:)`. Overrides win over the prefix so a widget that pins a kind
|
|
30
|
+
/// shaped like another widget's default still resolves to its own id.
|
|
31
|
+
static func widgetId(for kind: String, overrides: [String: String]) -> String? {
|
|
32
|
+
if let match = overrides.first(where: { $0.value == kind }) {
|
|
33
|
+
return match.key
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let prefix = VoltraStorageKeys.widgetKindPrefix
|
|
37
|
+
guard kind.hasPrefix(prefix) else { return nil }
|
|
38
|
+
|
|
39
|
+
return String(kind.dropFirst(prefix.count))
|
|
40
|
+
}
|
|
41
|
+
}
|