@use-voltra/ios-client 2.2.0 → 2.3.1
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
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import ActivityKit
|
|
2
|
+
import Foundation
|
|
3
|
+
import SwiftUI
|
|
4
|
+
import WidgetKit
|
|
5
|
+
|
|
6
|
+
/// Renders generated Dynamic Live Activity configurations through their bundled
|
|
7
|
+
/// JavaScript definitions. Failures intentionally become `EmptyView`: a remote
|
|
8
|
+
/// ActivityKit start may already exist and a later update can recover it.
|
|
9
|
+
public enum VoltraDynamicLiveActivityRenderer {
|
|
10
|
+
public static func lockScreen<Attributes: VoltraDynamicLiveActivityDefinition>(
|
|
11
|
+
definitionId: String,
|
|
12
|
+
context: ActivityViewContext<Attributes>
|
|
13
|
+
) -> some View {
|
|
14
|
+
if #available(iOS 18.0, *) {
|
|
15
|
+
return VoltraDynamicLiveActivityAdaptiveLockScreenView(definitionId: definitionId, context: context)
|
|
16
|
+
}
|
|
17
|
+
return VoltraDynamicLiveActivityLockScreenView(definitionId: definitionId, context: context)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static func dynamicIsland<Attributes: VoltraDynamicLiveActivityDefinition>(
|
|
21
|
+
definitionId: String,
|
|
22
|
+
context: ActivityViewContext<Attributes>
|
|
23
|
+
) -> DynamicIsland {
|
|
24
|
+
DynamicIsland {
|
|
25
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
26
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
27
|
+
definitionId: definitionId,
|
|
28
|
+
context: context,
|
|
29
|
+
region: .islandExpandedLeading
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
33
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
34
|
+
definitionId: definitionId,
|
|
35
|
+
context: context,
|
|
36
|
+
region: .islandExpandedTrailing
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
DynamicIslandExpandedRegion(.center) {
|
|
40
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
41
|
+
definitionId: definitionId,
|
|
42
|
+
context: context,
|
|
43
|
+
region: .islandExpandedCenter
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
47
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
48
|
+
definitionId: definitionId,
|
|
49
|
+
context: context,
|
|
50
|
+
region: .islandExpandedBottom
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
} compactLeading: {
|
|
54
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
55
|
+
definitionId: definitionId,
|
|
56
|
+
context: context,
|
|
57
|
+
region: .islandCompactLeading
|
|
58
|
+
)
|
|
59
|
+
} compactTrailing: {
|
|
60
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
61
|
+
definitionId: definitionId,
|
|
62
|
+
context: context,
|
|
63
|
+
region: .islandCompactTrailing
|
|
64
|
+
)
|
|
65
|
+
} minimal: {
|
|
66
|
+
VoltraDynamicLiveActivityDynamicIslandRegionView(
|
|
67
|
+
definitionId: definitionId,
|
|
68
|
+
context: context,
|
|
69
|
+
region: .islandMinimal
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// DynamicIsland itself is not a View and cannot read SwiftUI Environment.
|
|
74
|
+
// Do not execute a definition against fabricated values solely to obtain a
|
|
75
|
+
// keyline tint; its region views below read the actual environment.
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fileprivate static func resolve<Attributes: VoltraDynamicLiveActivityDefinition>(
|
|
79
|
+
definitionId: String,
|
|
80
|
+
context: ActivityViewContext<Attributes>,
|
|
81
|
+
activityFamily: String?,
|
|
82
|
+
colorScheme: ColorScheme? = nil,
|
|
83
|
+
locale: Locale = .current,
|
|
84
|
+
widgetRenderingMode: WidgetRenderingMode = .fullColor
|
|
85
|
+
) -> VoltraDynamicLiveActivityResolvedContent {
|
|
86
|
+
guard VoltraDynamicLiveActivityRegistry.shared.contains(definitionId) else {
|
|
87
|
+
logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Definition is missing from the installed catalog")
|
|
88
|
+
return .empty
|
|
89
|
+
}
|
|
90
|
+
guard let propsJSON = DynamicLiveActivityPropsCodec.encode(context.state.props) else {
|
|
91
|
+
logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Could not encode content-state props")
|
|
92
|
+
return .empty
|
|
93
|
+
}
|
|
94
|
+
// WidgetKit asks each Dynamic Island region (and each presentation) to render
|
|
95
|
+
// independently, all with identical state/environment. Cache the resolved
|
|
96
|
+
// variants shape so those siblings share one bundle load, one JS evaluation,
|
|
97
|
+
// and one failure diagnostic. `revision` changes on every hot reload, so a
|
|
98
|
+
// DEBUG reload busts the cache; in release it is constant.
|
|
99
|
+
let cacheKey = VoltraDynamicLiveActivityRenderCache.Key(
|
|
100
|
+
activityId: context.activityID,
|
|
101
|
+
definitionId: definitionId,
|
|
102
|
+
revision: VoltraDynamicLiveActivityBundleSource.revision(definitionId: definitionId),
|
|
103
|
+
props: propsJSON,
|
|
104
|
+
activityFamily: activityFamily,
|
|
105
|
+
colorScheme: String(describing: colorScheme ?? .light),
|
|
106
|
+
locale: locale.identifier,
|
|
107
|
+
widgetRenderingMode: String(describing: widgetRenderingMode),
|
|
108
|
+
isStale: context.isStale
|
|
109
|
+
)
|
|
110
|
+
return VoltraDynamicLiveActivityRenderCache.shared.resolve(cacheKey) {
|
|
111
|
+
resolveUncached(
|
|
112
|
+
definitionId: definitionId,
|
|
113
|
+
context: context,
|
|
114
|
+
propsJSON: propsJSON,
|
|
115
|
+
activityFamily: activityFamily,
|
|
116
|
+
colorScheme: colorScheme,
|
|
117
|
+
locale: locale,
|
|
118
|
+
widgetRenderingMode: widgetRenderingMode
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private static func resolveUncached<Attributes: VoltraDynamicLiveActivityDefinition>(
|
|
124
|
+
definitionId: String,
|
|
125
|
+
context: ActivityViewContext<Attributes>,
|
|
126
|
+
propsJSON: String,
|
|
127
|
+
activityFamily: String?,
|
|
128
|
+
colorScheme: ColorScheme?,
|
|
129
|
+
locale: Locale,
|
|
130
|
+
widgetRenderingMode: WidgetRenderingMode
|
|
131
|
+
) -> VoltraDynamicLiveActivityResolvedContent {
|
|
132
|
+
let environmentJSON = VoltraDynamicLiveActivityEnvironmentBuilder.build(
|
|
133
|
+
date: Date(),
|
|
134
|
+
colorScheme: colorScheme,
|
|
135
|
+
locale: locale,
|
|
136
|
+
widgetRenderingMode: widgetRenderingMode,
|
|
137
|
+
isStale: context.isStale,
|
|
138
|
+
activityFamily: activityFamily
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
do {
|
|
142
|
+
let source = try VoltraDynamicLiveActivityBundleSource.load(definitionId: definitionId)
|
|
143
|
+
guard VoltraJSRenderer.ensureLiveActivityEvaluated(definitionId: definitionId, source: source) else {
|
|
144
|
+
logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Could not evaluate definition bundle")
|
|
145
|
+
return .empty
|
|
146
|
+
}
|
|
147
|
+
guard let renderedJSON = VoltraJSRenderer.renderLiveActivity(
|
|
148
|
+
definitionId: definitionId,
|
|
149
|
+
propsJSON: propsJSON,
|
|
150
|
+
envJSON: environmentJSON
|
|
151
|
+
) else {
|
|
152
|
+
logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Definition render failed")
|
|
153
|
+
return .empty
|
|
154
|
+
}
|
|
155
|
+
let payload = try VoltraLiveActivityPayload(jsonString: renderedJSON)
|
|
156
|
+
return VoltraDynamicLiveActivityResolvedContent(payload: payload)
|
|
157
|
+
} catch {
|
|
158
|
+
logFailure(definitionId: definitionId, activityName: context.attributes.name, message: error.localizedDescription)
|
|
159
|
+
return .empty
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fileprivate static func logFailure(definitionId: String, activityName: String, message: String) {
|
|
164
|
+
VoltraDynamicLiveActivityRenderFailureReporter.record(
|
|
165
|
+
activityName: activityName,
|
|
166
|
+
definitionId: definitionId,
|
|
167
|
+
message: message
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private struct VoltraDynamicLiveActivityDynamicIslandRegionView<Attributes: VoltraDynamicLiveActivityDefinition>: View {
|
|
173
|
+
let definitionId: String
|
|
174
|
+
let context: ActivityViewContext<Attributes>
|
|
175
|
+
let region: VoltraRegion
|
|
176
|
+
|
|
177
|
+
@Environment(\.colorScheme) private var colorScheme
|
|
178
|
+
@Environment(\.locale) private var locale
|
|
179
|
+
@Environment(\.widgetRenderingMode) private var widgetRenderingMode
|
|
180
|
+
|
|
181
|
+
var body: some View {
|
|
182
|
+
VoltraDynamicLiveActivityRenderer.resolve(
|
|
183
|
+
definitionId: definitionId,
|
|
184
|
+
context: context,
|
|
185
|
+
activityFamily: nil,
|
|
186
|
+
colorScheme: colorScheme,
|
|
187
|
+
locale: locale,
|
|
188
|
+
widgetRenderingMode: widgetRenderingMode
|
|
189
|
+
)
|
|
190
|
+
.view(for: region, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private struct VoltraDynamicLiveActivityLockScreenView<Attributes: VoltraDynamicLiveActivityDefinition>: View {
|
|
195
|
+
let definitionId: String
|
|
196
|
+
let context: ActivityViewContext<Attributes>
|
|
197
|
+
|
|
198
|
+
@Environment(\.colorScheme) private var colorScheme
|
|
199
|
+
@Environment(\.locale) private var locale
|
|
200
|
+
@Environment(\.widgetRenderingMode) private var widgetRenderingMode
|
|
201
|
+
|
|
202
|
+
var body: some View {
|
|
203
|
+
let content = VoltraDynamicLiveActivityRenderer.resolve(
|
|
204
|
+
definitionId: definitionId,
|
|
205
|
+
context: context,
|
|
206
|
+
activityFamily: nil,
|
|
207
|
+
colorScheme: colorScheme,
|
|
208
|
+
locale: locale,
|
|
209
|
+
widgetRenderingMode: widgetRenderingMode
|
|
210
|
+
)
|
|
211
|
+
if let tint = content.payload?.activityBackgroundTint, let color = JSColorParser.parse(tint) {
|
|
212
|
+
content.view(for: .lockScreen, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
|
|
213
|
+
.activityBackgroundTint(color)
|
|
214
|
+
} else {
|
|
215
|
+
content.view(for: .lockScreen, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@available(iOS 18.0, *)
|
|
221
|
+
private struct VoltraDynamicLiveActivityAdaptiveLockScreenView<Attributes: VoltraDynamicLiveActivityDefinition>: View {
|
|
222
|
+
let definitionId: String
|
|
223
|
+
let context: ActivityViewContext<Attributes>
|
|
224
|
+
|
|
225
|
+
@Environment(\.activityFamily) private var activityFamily
|
|
226
|
+
@Environment(\.colorScheme) private var colorScheme
|
|
227
|
+
@Environment(\.locale) private var locale
|
|
228
|
+
@Environment(\.widgetRenderingMode) private var widgetRenderingMode
|
|
229
|
+
|
|
230
|
+
var body: some View {
|
|
231
|
+
let content = VoltraDynamicLiveActivityRenderer.resolve(
|
|
232
|
+
definitionId: definitionId,
|
|
233
|
+
context: context,
|
|
234
|
+
activityFamily: String(describing: activityFamily),
|
|
235
|
+
colorScheme: colorScheme,
|
|
236
|
+
locale: locale,
|
|
237
|
+
widgetRenderingMode: widgetRenderingMode
|
|
238
|
+
)
|
|
239
|
+
familyContent(content: content)
|
|
240
|
+
.voltraIfLet(content.payload?.activityBackgroundTint.flatMap { JSColorParser.parse($0) }) { view, color in
|
|
241
|
+
view.activityBackgroundTint(color)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@ViewBuilder
|
|
246
|
+
private func familyContent(content: VoltraDynamicLiveActivityResolvedContent) -> some View {
|
|
247
|
+
if activityFamily == .small {
|
|
248
|
+
smallFamilyContent(content: content)
|
|
249
|
+
} else {
|
|
250
|
+
content.view(for: .lockScreen, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@ViewBuilder
|
|
255
|
+
private func smallFamilyContent(content: VoltraDynamicLiveActivityResolvedContent) -> some View {
|
|
256
|
+
if content.hasContent(for: .supplementalActivityFamiliesSmall) {
|
|
257
|
+
content.view(
|
|
258
|
+
for: .supplementalActivityFamiliesSmall,
|
|
259
|
+
activityId: context.activityID,
|
|
260
|
+
deepLink: context.attributes.deepLinkUrl
|
|
261
|
+
)
|
|
262
|
+
} else if content.hasContent(for: .islandCompactLeading) || content.hasContent(for: .islandCompactTrailing) {
|
|
263
|
+
HStack(spacing: 0) {
|
|
264
|
+
content.view(for: .islandCompactLeading, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
|
|
265
|
+
Spacer()
|
|
266
|
+
content.view(for: .islandCompactTrailing, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
|
|
267
|
+
}
|
|
268
|
+
.frame(maxWidth: .infinity)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private struct VoltraDynamicLiveActivityResolvedContent {
|
|
274
|
+
static let empty = VoltraDynamicLiveActivityResolvedContent(payload: nil)
|
|
275
|
+
|
|
276
|
+
let payload: VoltraLiveActivityPayload?
|
|
277
|
+
|
|
278
|
+
func hasContent(for region: VoltraRegion) -> Bool {
|
|
279
|
+
!(payload?.regions[region] ?? []).isEmpty
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@ViewBuilder
|
|
283
|
+
func view(for region: VoltraRegion, activityId: String, deepLink: String?) -> some View {
|
|
284
|
+
if let nodes = payload?.regions[region], !nodes.isEmpty {
|
|
285
|
+
let root: VoltraNode = nodes.count == 1 ? nodes[0] : .array(nodes)
|
|
286
|
+
Voltra(root: root, activityId: activityId)
|
|
287
|
+
.voltraIfLet(deepLink.flatMap(VoltraDeepLinkResolver.resolveUrl)) { view, url in view.widgetURL(url) }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// WidgetKit may independently ask each Dynamic Island region to render. Cache
|
|
293
|
+
/// the complete variants shape per ActivityKit state/environment so all regions
|
|
294
|
+
/// share one JS evaluation and one failure diagnostic. `Key.revision` ties each
|
|
295
|
+
/// entry to the bundle bytes it was rendered from, so a hot reload (which
|
|
296
|
+
/// rewrites the source) never serves a stale render.
|
|
297
|
+
private final class VoltraDynamicLiveActivityRenderCache: @unchecked Sendable {
|
|
298
|
+
struct Key: Hashable {
|
|
299
|
+
let activityId: String
|
|
300
|
+
let definitionId: String
|
|
301
|
+
let revision: String
|
|
302
|
+
let props: String
|
|
303
|
+
let activityFamily: String?
|
|
304
|
+
let colorScheme: String
|
|
305
|
+
let locale: String
|
|
306
|
+
let widgetRenderingMode: String
|
|
307
|
+
let isStale: Bool
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
static let shared = VoltraDynamicLiveActivityRenderCache()
|
|
311
|
+
private let lock = NSLock()
|
|
312
|
+
private var values: [Key: VoltraDynamicLiveActivityResolvedContent] = [:]
|
|
313
|
+
|
|
314
|
+
func resolve(_ key: Key, render: () -> VoltraDynamicLiveActivityResolvedContent) -> VoltraDynamicLiveActivityResolvedContent {
|
|
315
|
+
lock.lock()
|
|
316
|
+
defer { lock.unlock() }
|
|
317
|
+
if let existing = values[key] {
|
|
318
|
+
return existing
|
|
319
|
+
}
|
|
320
|
+
let resolved = render()
|
|
321
|
+
if values.count >= 100 {
|
|
322
|
+
values.removeAll(keepingCapacity: true)
|
|
323
|
+
}
|
|
324
|
+
values[key] = resolved
|
|
325
|
+
return resolved
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private enum VoltraDynamicLiveActivityEnvironmentBuilder {
|
|
330
|
+
static func build(
|
|
331
|
+
date: Date,
|
|
332
|
+
colorScheme: ColorScheme?,
|
|
333
|
+
locale: Locale,
|
|
334
|
+
widgetRenderingMode: WidgetRenderingMode,
|
|
335
|
+
isStale: Bool,
|
|
336
|
+
activityFamily: String?
|
|
337
|
+
) -> String {
|
|
338
|
+
#if DEBUG
|
|
339
|
+
let isDev = true
|
|
340
|
+
let metroURL: String? = VoltraWidgetDefaults.devServerURL() ?? "http://localhost:8081"
|
|
341
|
+
#else
|
|
342
|
+
let isDev = false
|
|
343
|
+
let metroURL: String? = nil
|
|
344
|
+
#endif
|
|
345
|
+
let build: [String: Any] = [
|
|
346
|
+
"isDev": isDev,
|
|
347
|
+
"metroUrl": metroURL as Any,
|
|
348
|
+
"appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown",
|
|
349
|
+
"voltraVersion": VoltraConfig.voltraVersion(),
|
|
350
|
+
]
|
|
351
|
+
var environment: [String: Any] = [
|
|
352
|
+
"date": Int(date.timeIntervalSince1970 * 1000),
|
|
353
|
+
"colorScheme": String(describing: colorScheme ?? .light),
|
|
354
|
+
"locale": locale.identifier,
|
|
355
|
+
"widgetRenderingMode": String(describing: widgetRenderingMode),
|
|
356
|
+
"build": build,
|
|
357
|
+
"isStale": isStale,
|
|
358
|
+
]
|
|
359
|
+
if let activityFamily {
|
|
360
|
+
environment["activityFamily"] = activityFamily
|
|
361
|
+
}
|
|
362
|
+
guard
|
|
363
|
+
let data = try? JSONSerialization.data(withJSONObject: environment),
|
|
364
|
+
let json = String(data: data, encoding: .utf8)
|
|
365
|
+
else {
|
|
366
|
+
return "{}"
|
|
367
|
+
}
|
|
368
|
+
return json
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if os(iOS)
|
|
4
|
+
import ActivityKit
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
/// The JSON-compatible value type used by Dynamic Live Activity props.
|
|
8
|
+
///
|
|
9
|
+
/// This deliberately models only values that can cross the ActivityKit and
|
|
10
|
+
/// JavaScript boundaries without a definition-specific schema.
|
|
11
|
+
public indirect enum VoltraDynamicLiveActivityJSONValue: Codable, Hashable {
|
|
12
|
+
case null
|
|
13
|
+
case bool(Bool)
|
|
14
|
+
case number(Double)
|
|
15
|
+
case string(String)
|
|
16
|
+
case array([VoltraDynamicLiveActivityJSONValue])
|
|
17
|
+
case object([String: VoltraDynamicLiveActivityJSONValue])
|
|
18
|
+
|
|
19
|
+
public init(from decoder: Decoder) throws {
|
|
20
|
+
let container = try decoder.singleValueContainer()
|
|
21
|
+
if container.decodeNil() {
|
|
22
|
+
self = .null
|
|
23
|
+
} else if let value = try? container.decode(Bool.self) {
|
|
24
|
+
self = .bool(value)
|
|
25
|
+
} else if let value = try? container.decode(Double.self) {
|
|
26
|
+
self = .number(value)
|
|
27
|
+
} else if let value = try? container.decode(String.self) {
|
|
28
|
+
self = .string(value)
|
|
29
|
+
} else if let value = try? container.decode([VoltraDynamicLiveActivityJSONValue].self) {
|
|
30
|
+
self = .array(value)
|
|
31
|
+
} else if let value = try? container.decode([String: VoltraDynamicLiveActivityJSONValue].self) {
|
|
32
|
+
self = .object(value)
|
|
33
|
+
} else {
|
|
34
|
+
throw DecodingError.dataCorruptedError(
|
|
35
|
+
in: container,
|
|
36
|
+
debugDescription: "Expected a JSON-compatible value"
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public func encode(to encoder: Encoder) throws {
|
|
42
|
+
var container = encoder.singleValueContainer()
|
|
43
|
+
switch self {
|
|
44
|
+
case .null:
|
|
45
|
+
try container.encodeNil()
|
|
46
|
+
case let .bool(value):
|
|
47
|
+
try container.encode(value)
|
|
48
|
+
case let .number(value):
|
|
49
|
+
try container.encode(value)
|
|
50
|
+
case let .string(value):
|
|
51
|
+
try container.encode(value)
|
|
52
|
+
case let .array(value):
|
|
53
|
+
try container.encode(value)
|
|
54
|
+
case let .object(value):
|
|
55
|
+
try container.encode(value)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// The generic state shared by every generated Dynamic Live Activity type.
|
|
61
|
+
/// Every ActivityKit update replaces the complete props record.
|
|
62
|
+
public struct VoltraDynamicLiveActivityContentState: Codable, Hashable {
|
|
63
|
+
public let props: [String: VoltraDynamicLiveActivityJSONValue]
|
|
64
|
+
|
|
65
|
+
public init(props: [String: VoltraDynamicLiveActivityJSONValue]) {
|
|
66
|
+
self.props = props
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public enum VoltraDynamicLiveActivityError: Error {
|
|
71
|
+
case unknownDefinition(String)
|
|
72
|
+
case payloadTooLarge(size: Int)
|
|
73
|
+
case rendererMismatch
|
|
74
|
+
case resourceUnavailable(Error)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ActivityKit is unavailable to the macOS SwiftPM test target. The JSON props
|
|
78
|
+
// contract above remains independently testable there.
|
|
79
|
+
#if os(iOS)
|
|
80
|
+
/// Metadata and static attributes supplied by each generated definition.
|
|
81
|
+
public protocol VoltraDynamicLiveActivityDefinition: ActivityAttributes where ContentState == VoltraDynamicLiveActivityContentState {
|
|
82
|
+
static var definitionId: String { get }
|
|
83
|
+
var name: String { get }
|
|
84
|
+
var deepLinkUrl: String? { get }
|
|
85
|
+
init(name: String, deepLinkUrl: String?)
|
|
86
|
+
}
|
|
87
|
+
#endif
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Reconciles a persisted cross-engine creation order with ActivityKit's
|
|
4
|
+
/// currently active IDs, pruning ended activities and appending new discoveries.
|
|
5
|
+
public enum VoltraLiveActivityOrder {
|
|
6
|
+
public static func reconcile(previous: [String], active: [String]) -> [String] {
|
|
7
|
+
let activeIds = Set(active)
|
|
8
|
+
var seen = Set<String>()
|
|
9
|
+
var result: [String] = []
|
|
10
|
+
|
|
11
|
+
for id in previous where activeIds.contains(id) && seen.insert(id).inserted {
|
|
12
|
+
result.append(id)
|
|
13
|
+
}
|
|
14
|
+
for id in active where seen.insert(id).inserted {
|
|
15
|
+
result.append(id)
|
|
16
|
+
}
|
|
17
|
+
return result
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -41,6 +41,12 @@ public struct VoltraClientWidgetEntry: TimelineEntry {
|
|
|
41
41
|
/// process, where the provider's process-static JSContext is empty. The View re-evaluates from
|
|
42
42
|
/// this source so `render()` always has the widget's function available in its own process.
|
|
43
43
|
public let bundleSource: String?
|
|
44
|
+
/// How the last server fetch went, as `env.serverUpdate`, for a widget that has a `serverUpdate`
|
|
45
|
+
/// in app.json. `nil` for every other Dynamic Widget, and the reason `env.serverUpdate` is
|
|
46
|
+
/// `undefined` there. Carried on the entry rather than read at render time because the provider
|
|
47
|
+
/// is the thing that knows the fetch outcome, and WidgetKit re-renders archived entries in a
|
|
48
|
+
/// fresh process.
|
|
49
|
+
public let serverUpdateJSON: String?
|
|
44
50
|
|
|
45
51
|
public init(
|
|
46
52
|
date: Date,
|
|
@@ -48,7 +54,8 @@ public struct VoltraClientWidgetEntry: TimelineEntry {
|
|
|
48
54
|
bundleReady: Bool,
|
|
49
55
|
errorMessage: String? = nil,
|
|
50
56
|
configuration: [String: String] = [:],
|
|
51
|
-
bundleSource: String? = nil
|
|
57
|
+
bundleSource: String? = nil,
|
|
58
|
+
serverUpdateJSON: String? = nil
|
|
52
59
|
) {
|
|
53
60
|
self.date = date
|
|
54
61
|
self.widgetId = widgetId
|
|
@@ -56,6 +63,20 @@ public struct VoltraClientWidgetEntry: TimelineEntry {
|
|
|
56
63
|
self.errorMessage = errorMessage
|
|
57
64
|
self.configuration = configuration
|
|
58
65
|
self.bundleSource = bundleSource
|
|
66
|
+
self.serverUpdateJSON = serverUpdateJSON
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// The same entry, told how the server side is doing.
|
|
70
|
+
public func withServerUpdate(_ serverUpdateJSON: String?) -> VoltraClientWidgetEntry {
|
|
71
|
+
VoltraClientWidgetEntry(
|
|
72
|
+
date: date,
|
|
73
|
+
widgetId: widgetId,
|
|
74
|
+
bundleReady: bundleReady,
|
|
75
|
+
errorMessage: errorMessage,
|
|
76
|
+
configuration: configuration,
|
|
77
|
+
bundleSource: bundleSource,
|
|
78
|
+
serverUpdateJSON: serverUpdateJSON
|
|
79
|
+
)
|
|
59
80
|
}
|
|
60
81
|
}
|
|
61
82
|
|
|
@@ -234,10 +255,12 @@ public enum VoltraClientWidgetEnvBuilder {
|
|
|
234
255
|
widgetRenderingMode: WidgetRenderingMode,
|
|
235
256
|
showsWidgetContainerBackground: Bool,
|
|
236
257
|
locale: Locale,
|
|
237
|
-
configuration: [String: String]
|
|
258
|
+
configuration: [String: String],
|
|
259
|
+
serverUpdateJSON: String? = nil
|
|
238
260
|
) -> String {
|
|
239
261
|
let timestampMs = Int(date.timeIntervalSince1970 * 1000)
|
|
240
262
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
|
|
263
|
+
let voltraVersion = VoltraConfig.voltraVersion()
|
|
241
264
|
|
|
242
265
|
#if DEBUG
|
|
243
266
|
let isDev = true
|
|
@@ -253,7 +276,7 @@ public enum VoltraClientWidgetEnvBuilder {
|
|
|
253
276
|
"isDev": \(isDev),
|
|
254
277
|
"metroUrl": \(metroUrlLiteral),
|
|
255
278
|
"appVersion": \(jsonString(appVersion)),
|
|
256
|
-
"voltraVersion": \(jsonString(
|
|
279
|
+
"voltraVersion": \(jsonString(voltraVersion))
|
|
257
280
|
}
|
|
258
281
|
"""
|
|
259
282
|
|
|
@@ -267,6 +290,15 @@ public enum VoltraClientWidgetEnvBuilder {
|
|
|
267
290
|
configurationJSON = "{ \(entries) }"
|
|
268
291
|
}
|
|
269
292
|
|
|
293
|
+
// Only a server-driven widget gets env.serverUpdate; leaving the key out entirely is what
|
|
294
|
+
// makes it `undefined` for every other Dynamic Widget.
|
|
295
|
+
let serverUpdateEntry = serverUpdateJSON.map { ",\n \"serverUpdate\": \($0)" } ?? ""
|
|
296
|
+
|
|
297
|
+
// The instance key (ADR 0007): the hash of this placement's merged configuration, or absent
|
|
298
|
+
// for a widget with no configuration parameters at all — matching what the request builder
|
|
299
|
+
// sends and what WidgetScope.of(widgetId, configuration:) resolves to.
|
|
300
|
+
let instanceEntry = WidgetCanonicalConfiguration.key(configuration).map { ",\n \"instance\": \(jsonString($0))" } ?? ""
|
|
301
|
+
|
|
270
302
|
return """
|
|
271
303
|
{
|
|
272
304
|
"date": \(timestampMs),
|
|
@@ -276,7 +308,7 @@ public enum VoltraClientWidgetEnvBuilder {
|
|
|
276
308
|
"widgetRenderingMode": \(jsonString(renderingModeString(widgetRenderingMode))),
|
|
277
309
|
"showsWidgetContainerBackground": \(showsWidgetContainerBackground),
|
|
278
310
|
"configuration": \(configurationJSON),
|
|
279
|
-
"build": \(buildJSON)
|
|
311
|
+
"build": \(buildJSON)\(serverUpdateEntry)\(instanceEntry)
|
|
280
312
|
}
|
|
281
313
|
"""
|
|
282
314
|
}
|
|
@@ -349,11 +381,17 @@ public struct VoltraClientWidgetContentView: View {
|
|
|
349
381
|
widgetRenderingMode: widgetRenderingMode,
|
|
350
382
|
showsWidgetContainerBackground: showsWidgetContainerBackground,
|
|
351
383
|
locale: locale,
|
|
352
|
-
configuration: entry.configuration
|
|
384
|
+
configuration: entry.configuration,
|
|
385
|
+
serverUpdateJSON: entry.serverUpdateJSON
|
|
353
386
|
)
|
|
387
|
+
// This placement's scope (ADR 0007): an instance of its merged configuration, or the plain
|
|
388
|
+
// widget scope when it has none — which is also every widget's scope before this ADR.
|
|
389
|
+
let scope = WidgetScope.of(entry.widgetId, configuration: entry.configuration)
|
|
354
390
|
let dynamicWidgetPropsStore = DynamicWidgetPropsStore()
|
|
355
391
|
let dynamicWidgetRenderCoordinator = DynamicWidgetRenderCoordinator(
|
|
356
|
-
|
|
392
|
+
// Reads the instance slot for `scope`'s key, falling back to the widget slot when the
|
|
393
|
+
// placement has not fetched yet or has no configuration.
|
|
394
|
+
dynamicWidgetPropsProvider: { _ in dynamicWidgetPropsStore.dynamicWidgetProps(for: scope) },
|
|
357
395
|
dynamicWidgetRuntimeBoundary: { dynamicWidgetID, dynamicWidgetPropsJSON, dynamicWidgetEnvironmentJSON in
|
|
358
396
|
VoltraJSRenderer.render(
|
|
359
397
|
widgetId: dynamicWidgetID,
|