@use-voltra/ios-client 1.4.1 → 2.0.0-rc.2
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/README.md +113 -0
- package/Voltra.podspec +28 -0
- package/build/commonjs/VoltraModule.js +13 -0
- package/build/commonjs/VoltraModule.js.map +1 -0
- package/build/commonjs/components/VoltraLiveActivityPreview.js +30 -0
- package/build/commonjs/components/VoltraLiveActivityPreview.js.map +1 -0
- package/build/commonjs/components/VoltraView.js +50 -0
- package/build/commonjs/components/VoltraView.js.map +1 -0
- package/build/commonjs/components/VoltraWidgetPreview.js +58 -0
- package/build/commonjs/components/VoltraWidgetPreview.js.map +1 -0
- package/build/commonjs/events.js +32 -0
- package/build/commonjs/events.js.map +1 -0
- package/build/commonjs/helpers.js +26 -0
- package/build/commonjs/helpers.js.map +1 -0
- package/build/commonjs/index.js +226 -0
- package/build/commonjs/index.js.map +1 -0
- package/build/commonjs/live-activity/api.js +168 -0
- package/build/commonjs/live-activity/api.js.map +1 -0
- package/build/commonjs/logger.js +39 -0
- package/build/commonjs/logger.js.map +1 -0
- package/build/commonjs/native/NativeVoltra.js +15 -0
- package/build/commonjs/native/NativeVoltra.js.map +1 -0
- package/build/commonjs/native/VoltraRNNativeComponent.ts +9 -0
- package/build/commonjs/package.json +1 -0
- package/build/commonjs/preload.js +54 -0
- package/build/commonjs/preload.js.map +1 -0
- package/build/commonjs/types.js +2 -0
- package/build/commonjs/types.js.map +1 -0
- package/build/commonjs/utils/assertRunningOnApple.js +16 -0
- package/build/commonjs/utils/assertRunningOnApple.js.map +1 -0
- package/build/commonjs/utils/helpers.js +19 -0
- package/build/commonjs/utils/helpers.js.map +1 -0
- package/build/commonjs/utils/index.js +28 -0
- package/build/commonjs/utils/index.js.map +1 -0
- package/build/commonjs/utils/useUpdateOnHMR.js +25 -0
- package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -0
- package/build/commonjs/widgets/server-credentials.js +18 -0
- package/build/commonjs/widgets/server-credentials.js.map +1 -0
- package/build/commonjs/widgets/widget-api.js +55 -0
- package/build/commonjs/widgets/widget-api.js.map +1 -0
- package/build/module/VoltraModule.js +4 -0
- package/build/module/VoltraModule.js.map +1 -0
- package/build/module/components/VoltraLiveActivityPreview.js +25 -0
- package/build/module/components/VoltraLiveActivityPreview.js.map +1 -0
- package/build/module/components/VoltraView.js +44 -0
- package/build/module/components/VoltraView.js.map +1 -0
- package/build/module/components/VoltraWidgetPreview.js +53 -0
- package/build/module/components/VoltraWidgetPreview.js.map +1 -0
- package/build/module/events.js +28 -0
- package/build/module/events.js.map +1 -0
- package/build/module/helpers.js +21 -0
- package/build/module/helpers.js.map +1 -0
- package/build/module/index.js +17 -0
- package/build/module/index.js.map +1 -0
- package/build/module/live-activity/api.js +158 -0
- package/build/module/live-activity/api.js.map +1 -0
- package/build/module/logger.js +35 -0
- package/build/module/logger.js.map +1 -0
- package/build/module/native/NativeVoltra.js +11 -0
- package/build/module/native/NativeVoltra.js.map +1 -0
- package/build/module/native/VoltraRNNativeComponent.ts +9 -0
- package/build/module/package.json +1 -0
- package/build/module/preload.js +48 -0
- package/build/module/preload.js.map +1 -0
- package/build/module/types.js +2 -0
- package/build/module/types.js.map +1 -0
- package/build/module/utils/assertRunningOnApple.js +11 -0
- package/build/module/utils/assertRunningOnApple.js.map +1 -0
- package/build/module/utils/helpers.js +4 -0
- package/build/module/utils/helpers.js.map +1 -0
- package/build/module/utils/index.js +5 -0
- package/build/module/utils/index.js.map +1 -0
- package/build/module/utils/useUpdateOnHMR.js +20 -0
- package/build/module/utils/useUpdateOnHMR.js.map +1 -0
- package/build/module/widgets/server-credentials.js +13 -0
- package/build/module/widgets/server-credentials.js.map +1 -0
- package/build/module/widgets/widget-api.js +45 -0
- package/build/module/widgets/widget-api.js.map +1 -0
- package/build/typescript/commonjs/VoltraModule.d.ts +3 -0
- package/build/typescript/commonjs/VoltraModule.d.ts.map +1 -0
- package/build/typescript/commonjs/components/VoltraLiveActivityPreview.d.ts.map +1 -0
- package/build/typescript/commonjs/components/VoltraView.d.ts.map +1 -0
- package/build/typescript/commonjs/components/VoltraWidgetPreview.d.ts.map +1 -0
- package/build/typescript/commonjs/events.d.ts.map +1 -0
- package/build/typescript/commonjs/helpers.d.ts.map +1 -0
- package/build/{types → typescript/commonjs}/index.d.ts +5 -0
- package/build/typescript/commonjs/index.d.ts.map +1 -0
- package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -0
- package/build/typescript/commonjs/logger.d.ts.map +1 -0
- package/build/typescript/commonjs/native/NativeVoltra.d.ts +100 -0
- package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -0
- package/build/typescript/commonjs/native/VoltraRNNativeComponent.d.ts +8 -0
- package/build/typescript/commonjs/native/VoltraRNNativeComponent.d.ts.map +1 -0
- package/build/typescript/commonjs/package.json +1 -0
- package/build/typescript/commonjs/preload.d.ts.map +1 -0
- package/build/typescript/commonjs/types.d.ts.map +1 -0
- package/build/typescript/commonjs/utils/assertRunningOnApple.d.ts.map +1 -0
- package/build/typescript/commonjs/utils/helpers.d.ts.map +1 -0
- package/build/typescript/commonjs/utils/index.d.ts.map +1 -0
- package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -0
- package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -0
- package/build/typescript/commonjs/widgets/widget-api.d.ts.map +1 -0
- package/build/typescript/module/VoltraModule.d.ts +3 -0
- package/build/typescript/module/VoltraModule.d.ts.map +1 -0
- package/build/typescript/module/components/VoltraLiveActivityPreview.d.ts +7 -0
- package/build/typescript/module/components/VoltraLiveActivityPreview.d.ts.map +1 -0
- package/build/typescript/module/components/VoltraView.d.ts +12 -0
- package/build/typescript/module/components/VoltraView.d.ts.map +1 -0
- package/build/typescript/module/components/VoltraWidgetPreview.d.ts +9 -0
- package/build/typescript/module/components/VoltraWidgetPreview.d.ts.map +1 -0
- package/build/typescript/module/events.d.ts +33 -0
- package/build/typescript/module/events.d.ts.map +1 -0
- package/build/typescript/module/helpers.d.ts +3 -0
- package/build/typescript/module/helpers.d.ts.map +1 -0
- package/build/typescript/module/index.d.ts +18 -0
- package/build/typescript/module/index.d.ts.map +1 -0
- package/build/typescript/module/live-activity/api.d.ts +35 -0
- package/build/typescript/module/live-activity/api.d.ts.map +1 -0
- package/build/typescript/module/logger.d.ts +16 -0
- package/build/typescript/module/logger.d.ts.map +1 -0
- package/build/typescript/module/native/NativeVoltra.d.ts +100 -0
- package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -0
- package/build/typescript/module/native/VoltraRNNativeComponent.d.ts +8 -0
- package/build/typescript/module/native/VoltraRNNativeComponent.d.ts.map +1 -0
- package/build/typescript/module/package.json +1 -0
- package/build/typescript/module/preload.d.ts +6 -0
- package/build/typescript/module/preload.d.ts.map +1 -0
- package/build/typescript/module/types.d.ts +2 -0
- package/build/typescript/module/types.d.ts.map +1 -0
- package/build/typescript/module/utils/assertRunningOnApple.d.ts +2 -0
- package/build/typescript/module/utils/assertRunningOnApple.d.ts.map +1 -0
- package/build/{esm/utils/helpers.js → typescript/module/utils/helpers.d.ts} +1 -1
- package/build/typescript/module/utils/helpers.d.ts.map +1 -0
- package/build/{esm/utils/index.js → typescript/module/utils/index.d.ts} +1 -1
- package/build/typescript/module/utils/index.d.ts.map +1 -0
- package/build/typescript/module/utils/useUpdateOnHMR.d.ts +5 -0
- package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -0
- package/build/typescript/module/widgets/server-credentials.d.ts +5 -0
- package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -0
- package/build/typescript/module/widgets/widget-api.d.ts +11 -0
- package/build/typescript/module/widgets/widget-api.d.ts.map +1 -0
- package/expo-plugin/app.plugin.js +3 -0
- package/expo-plugin/build/cjs/constants.js +23 -0
- package/expo-plugin/build/cjs/constants.js.map +1 -0
- package/expo-plugin/build/cjs/index.js +51 -0
- package/expo-plugin/build/cjs/index.js.map +1 -0
- package/expo-plugin/build/cjs/ios/eas.js +28 -0
- package/expo-plugin/build/cjs/ios/eas.js.map +1 -0
- package/expo-plugin/build/cjs/ios/entitlements.js +46 -0
- package/expo-plugin/build/cjs/ios/entitlements.js.map +1 -0
- package/expo-plugin/build/cjs/ios/index.js +39 -0
- package/expo-plugin/build/cjs/ios/index.js.map +1 -0
- package/expo-plugin/build/cjs/ios/infoPlist.js +51 -0
- package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -0
- package/expo-plugin/build/cjs/ios/pushNotifications.js +22 -0
- package/expo-plugin/build/cjs/ios/pushNotifications.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/eas.js +66 -0
- package/expo-plugin/build/cjs/ios-widget/eas.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/assets.js +199 -0
- package/expo-plugin/build/cjs/ios-widget/files/assets.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/entitlements.js +77 -0
- package/expo-plugin/build/cjs/ios-widget/files/entitlements.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/index.js +92 -0
- package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +91 -0
- package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/swift.js +443 -0
- package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/fonts.js +151 -0
- package/expo-plugin/build/cjs/ios-widget/fonts.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/index.js +46 -0
- package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/podfile.js +41 -0
- package/expo-plugin/build/cjs/ios-widget/podfile.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +99 -0
- package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +247 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js +91 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/groups.js +79 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/groups.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/index.js +155 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js +71 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js +74 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/target.js +113 -0
- package/expo-plugin/build/cjs/ios-widget/xcode/target.js.map +1 -0
- package/expo-plugin/build/cjs/utils/fileDiscovery.js +120 -0
- package/expo-plugin/build/cjs/utils/fileDiscovery.js.map +1 -0
- package/expo-plugin/build/cjs/utils/urlScheme.js +40 -0
- package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -0
- package/expo-plugin/build/cjs/validation.js +55 -0
- package/expo-plugin/build/cjs/validation.js.map +1 -0
- package/expo-plugin/build/esm/constants.js +20 -0
- package/expo-plugin/build/esm/constants.js.map +1 -0
- package/expo-plugin/build/esm/index.js +49 -0
- package/expo-plugin/build/esm/index.js.map +1 -0
- package/expo-plugin/build/esm/ios/eas.js +24 -0
- package/expo-plugin/build/esm/ios/eas.js.map +1 -0
- package/expo-plugin/build/esm/ios/entitlements.js +42 -0
- package/expo-plugin/build/esm/ios/entitlements.js.map +1 -0
- package/expo-plugin/build/esm/ios/index.js +34 -0
- package/expo-plugin/build/esm/ios/index.js.map +1 -0
- package/expo-plugin/build/esm/ios/infoPlist.js +47 -0
- package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -0
- package/expo-plugin/build/esm/ios/pushNotifications.js +18 -0
- package/expo-plugin/build/esm/ios/pushNotifications.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/eas.js +62 -0
- package/expo-plugin/build/esm/ios-widget/eas.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/assets.js +163 -0
- package/expo-plugin/build/esm/ios-widget/files/assets.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/entitlements.js +37 -0
- package/expo-plugin/build/esm/ios-widget/files/entitlements.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/index.js +55 -0
- package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +55 -0
- package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/swift.js +403 -0
- package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/fonts.js +111 -0
- package/expo-plugin/build/esm/ios-widget/fonts.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/index.js +42 -0
- package/expo-plugin/build/esm/ios-widget/index.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/podfile.js +37 -0
- package/expo-plugin/build/esm/ios-widget/podfile.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/widgetPlist.js +92 -0
- package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +210 -0
- package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js +87 -0
- package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/groups.js +75 -0
- package/expo-plugin/build/esm/ios-widget/xcode/groups.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/index.js +118 -0
- package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js +68 -0
- package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/productFile.js +70 -0
- package/expo-plugin/build/esm/ios-widget/xcode/productFile.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/xcode/target.js +108 -0
- package/expo-plugin/build/esm/ios-widget/xcode/target.js.map +1 -0
- package/expo-plugin/build/esm/utils/fileDiscovery.js +83 -0
- package/expo-plugin/build/esm/utils/fileDiscovery.js.map +1 -0
- package/expo-plugin/build/esm/utils/urlScheme.js +37 -0
- package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -0
- package/expo-plugin/build/esm/validation.js +51 -0
- package/expo-plugin/build/esm/validation.js.map +1 -0
- package/expo-plugin/build/types/constants.d.ts +13 -0
- package/expo-plugin/build/types/constants.d.ts.map +1 -0
- package/expo-plugin/build/types/index.d.ts +10 -0
- package/expo-plugin/build/types/index.d.ts.map +1 -0
- package/expo-plugin/build/types/ios/eas.d.ts +12 -0
- package/expo-plugin/build/types/ios/eas.d.ts.map +1 -0
- package/expo-plugin/build/types/ios/entitlements.d.ts +14 -0
- package/expo-plugin/build/types/ios/entitlements.d.ts.map +1 -0
- package/expo-plugin/build/types/ios/index.d.ts +19 -0
- package/expo-plugin/build/types/ios/index.d.ts.map +1 -0
- package/expo-plugin/build/types/ios/infoPlist.d.ts +21 -0
- package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -0
- package/expo-plugin/build/types/ios/pushNotifications.d.ts +10 -0
- package/expo-plugin/build/types/ios/pushNotifications.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/eas.d.ts +15 -0
- package/expo-plugin/build/types/ios-widget/eas.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/assets.d.ts +13 -0
- package/expo-plugin/build/types/ios-widget/files/assets.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/entitlements.d.ts +17 -0
- package/expo-plugin/build/types/ios-widget/files/entitlements.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/index.d.ts +24 -0
- package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +10 -0
- package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/swift.d.ts +24 -0
- package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/fonts.d.ts +22 -0
- package/expo-plugin/build/types/ios-widget/fonts.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/index.d.ts +31 -0
- package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/podfile.d.ts +9 -0
- package/expo-plugin/build/types/ios-widget/podfile.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts +20 -0
- package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +25 -0
- package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts +20 -0
- package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts +15 -0
- package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +20 -0
- package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts +15 -0
- package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts +14 -0
- package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/xcode/target.d.ts +42 -0
- package/expo-plugin/build/types/ios-widget/xcode/target.d.ts.map +1 -0
- package/expo-plugin/build/types/types.d.ts +74 -0
- package/expo-plugin/build/types/types.d.ts.map +1 -0
- package/expo-plugin/build/types/utils/fileDiscovery.d.ts +15 -0
- package/expo-plugin/build/types/utils/fileDiscovery.d.ts.map +1 -0
- package/expo-plugin/build/types/utils/urlScheme.d.ts +10 -0
- package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -0
- package/expo-plugin/build/types/validation.d.ts +4 -0
- package/expo-plugin/build/types/validation.d.ts.map +1 -0
- package/expo-plugin/jest.config.js +18 -0
- package/expo-plugin/src/constants.ts +25 -0
- package/expo-plugin/src/index.ts +72 -0
- package/expo-plugin/src/ios/eas.ts +33 -0
- package/expo-plugin/src/ios/entitlements.ts +59 -0
- package/expo-plugin/src/ios/index.ts +47 -0
- package/expo-plugin/src/ios/infoPlist.ts +63 -0
- package/expo-plugin/src/ios/pushNotifications.ts +22 -0
- package/expo-plugin/src/ios-widget/eas.ts +79 -0
- package/expo-plugin/src/ios-widget/files/assets.ts +201 -0
- package/expo-plugin/src/ios-widget/files/entitlements.ts +48 -0
- package/expo-plugin/src/ios-widget/files/index.ts +74 -0
- package/expo-plugin/src/ios-widget/files/infoPlist.ts +57 -0
- package/expo-plugin/src/ios-widget/files/swift.node.test.ts +22 -0
- package/expo-plugin/src/ios-widget/files/swift.ts +476 -0
- package/expo-plugin/src/ios-widget/fonts.ts +132 -0
- package/expo-plugin/src/ios-widget/index.ts +73 -0
- package/expo-plugin/src/ios-widget/podfile.ts +44 -0
- package/expo-plugin/src/ios-widget/widgetPlist.ts +115 -0
- package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +301 -0
- package/expo-plugin/src/ios-widget/xcode/configurationList.ts +127 -0
- package/expo-plugin/src/ios-widget/xcode/groups.ts +101 -0
- package/expo-plugin/src/ios-widget/xcode/index.ts +149 -0
- package/expo-plugin/src/ios-widget/xcode/mainAppSettings.ts +95 -0
- package/expo-plugin/src/ios-widget/xcode/productFile.ts +91 -0
- package/expo-plugin/src/ios-widget/xcode/target.ts +148 -0
- package/expo-plugin/src/types.ts +89 -0
- package/expo-plugin/src/utils/fileDiscovery.ts +96 -0
- package/expo-plugin/src/utils/urlScheme.ts +45 -0
- package/expo-plugin/src/validation.node.test.ts +19 -0
- package/expo-plugin/src/validation.ts +63 -0
- package/expo-plugin/tsconfig.base.json +17 -0
- package/expo-plugin/tsconfig.cjs.json +9 -0
- package/expo-plugin/tsconfig.esm.json +9 -0
- package/expo-plugin/tsconfig.jest.json +8 -0
- package/expo-plugin/tsconfig.typecheck.json +12 -0
- package/expo-plugin/tsconfig.types.json +10 -0
- package/ios/Package.swift +68 -0
- package/ios/Tests/JSGradientParserTests.swift +201 -0
- package/ios/Tests/VoltraSharedTests/SmokeTests.swift +8 -0
- package/ios/VoltraWidget.podspec +30 -0
- package/ios/app/NativeVoltra.h +5 -0
- package/ios/app/NativeVoltra.mm +211 -0
- package/ios/app/VoltraActivity.swift +5 -0
- package/ios/app/VoltraImagePreload.swift +154 -0
- package/ios/app/VoltraLiveActivityManager.swift +228 -0
- package/ios/app/VoltraLiveActivityService.swift +306 -0
- package/ios/app/VoltraModule.swift +215 -0
- package/ios/app/VoltraModuleImpl.swift +331 -0
- package/ios/app/VoltraOptions.swift +27 -0
- package/ios/app/VoltraViewComponentView.h +10 -0
- package/ios/app/VoltraViewComponentView.mm +67 -0
- package/ios/app/VoltraViewRoot.swift +63 -0
- package/ios/app/VoltraWidgetService.swift +154 -0
- package/ios/shared/BrotliCompression.swift +81 -0
- package/ios/shared/ComponentTypeID.swift +117 -0
- package/ios/shared/Data+hexString.swift +8 -0
- package/ios/shared/Date+toTimerInterval.swift +25 -0
- package/ios/shared/JSONValue.swift +128 -0
- package/ios/shared/ShortNames.swift +180 -0
- package/ios/shared/VoltraAttributes.swift +58 -0
- package/ios/shared/VoltraConfig.swift +18 -0
- package/ios/shared/VoltraConstants.swift +45 -0
- package/ios/shared/VoltraElement.swift +153 -0
- package/ios/shared/VoltraEvent.swift +72 -0
- package/ios/shared/VoltraEventBus.swift +80 -0
- package/ios/shared/VoltraImageStore.swift +119 -0
- package/ios/shared/VoltraInitialStateLocale.swift +54 -0
- package/ios/shared/VoltraInteractionIntent.swift +39 -0
- package/ios/shared/VoltraKeychainHelper.swift +178 -0
- package/ios/shared/VoltraLiveActivityPayload.swift +113 -0
- package/ios/shared/VoltraLogger.swift +30 -0
- package/ios/shared/VoltraNode.swift +194 -0
- package/ios/shared/VoltraPayloadMigrator.swift +63 -0
- package/ios/shared/VoltraPersistentEventQueue.swift +67 -0
- package/ios/shared/VoltraRefreshIntent.swift +29 -0
- package/ios/shared/VoltraRegion.swift +37 -0
- package/ios/shared/VoltraWidgetDefaults.swift +114 -0
- package/ios/shared/VoltraWidgetServerFetcher.swift +166 -0
- package/ios/target/VoltraHomeWidget.swift +508 -0
- package/ios/target/VoltraWidget.swift +158 -0
- package/ios/ui/Extensions/View.modifiers.swift +28 -0
- package/ios/ui/Extensions/VoltraNode+Parameters.swift +18 -0
- package/ios/ui/Generated/Parameters/ButtonParameters.swift +15 -0
- package/ios/ui/Generated/Parameters/ChartParameters.swift +33 -0
- package/ios/ui/Generated/Parameters/CircularProgressViewParameters.swift +59 -0
- package/ios/ui/Generated/Parameters/ComponentParameters.swift +12 -0
- package/ios/ui/Generated/Parameters/DividerParameters.swift +12 -0
- package/ios/ui/Generated/Parameters/GaugeParameters.swift +54 -0
- package/ios/ui/Generated/Parameters/GlassContainerParameters.swift +15 -0
- package/ios/ui/Generated/Parameters/GroupBoxParameters.swift +12 -0
- package/ios/ui/Generated/Parameters/HStackParameters.swift +34 -0
- package/ios/ui/Generated/Parameters/ImageParameters.swift +29 -0
- package/ios/ui/Generated/Parameters/LabelParameters.swift +18 -0
- package/ios/ui/Generated/Parameters/LinearGradientParameters.swift +27 -0
- package/ios/ui/Generated/Parameters/LinearProgressViewParameters.swift +64 -0
- package/ios/ui/Generated/Parameters/LinkParameters.swift +15 -0
- package/ios/ui/Generated/Parameters/MaskParameters.swift +12 -0
- package/ios/ui/Generated/Parameters/SpacerParameters.swift +15 -0
- package/ios/ui/Generated/Parameters/SymbolParameters.swift +59 -0
- package/ios/ui/Generated/Parameters/TextParameters.swift +18 -0
- package/ios/ui/Generated/Parameters/TimerParameters.swift +54 -0
- package/ios/ui/Generated/Parameters/ToggleParameters.swift +24 -0
- package/ios/ui/Generated/Parameters/VStackParameters.swift +34 -0
- package/ios/ui/Generated/Parameters/ViewParameters.swift +12 -0
- package/ios/ui/Generated/Parameters/ZStackParameters.swift +15 -0
- package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +43 -0
- package/ios/ui/Helpers/VoltraProgressDriver.swift +52 -0
- package/ios/ui/Layout/FlexContainerHelper.swift +114 -0
- package/ios/ui/Layout/VoltraFlexStackLayout.swift +417 -0
- package/ios/ui/Protocols/VoltraView.swift +29 -0
- package/ios/ui/Style/BackgroundValue.swift +27 -0
- package/ios/ui/Style/CompositeStyle.swift +81 -0
- package/ios/ui/Style/DecorationStyle.swift +207 -0
- package/ios/ui/Style/FlexEnvironment.swift +35 -0
- package/ios/ui/Style/FontVariant.swift +4 -0
- package/ios/ui/Style/GlassEffect.swift +6 -0
- package/ios/ui/Style/JSColorParser.swift +517 -0
- package/ios/ui/Style/JSGradientParser.swift +674 -0
- package/ios/ui/Style/JSStyleParser.swift +223 -0
- package/ios/ui/Style/LayoutStyle.swift +184 -0
- package/ios/ui/Style/Overflow.swift +4 -0
- package/ios/ui/Style/RenderingStyle.swift +33 -0
- package/ios/ui/Style/StyleConverter.swift +202 -0
- package/ios/ui/Style/TextDecoration.swift +6 -0
- package/ios/ui/Style/TextStyle.swift +50 -0
- package/ios/ui/Style/View+applyStyle.swift +38 -0
- package/ios/ui/Views/VoltraButton.swift +57 -0
- package/ios/ui/Views/VoltraChart.swift +840 -0
- package/ios/ui/Views/VoltraCircularProgressView.swift +103 -0
- package/ios/ui/Views/VoltraDivider.swift +16 -0
- package/ios/ui/Views/VoltraFlexView.swift +33 -0
- package/ios/ui/Views/VoltraGauge.swift +65 -0
- package/ios/ui/Views/VoltraGlassContainer.swift +29 -0
- package/ios/ui/Views/VoltraGroupBox.swift +26 -0
- package/ios/ui/Views/VoltraHStack.swift +70 -0
- package/ios/ui/Views/VoltraImage.swift +109 -0
- package/ios/ui/Views/VoltraLabel.swift +24 -0
- package/ios/ui/Views/VoltraLinearGradient.swift +115 -0
- package/ios/ui/Views/VoltraLinearProgressView.swift +118 -0
- package/ios/ui/Views/VoltraLink.swift +25 -0
- package/ios/ui/Views/VoltraMask.swift +23 -0
- package/ios/ui/Views/VoltraSpacer.swift +16 -0
- package/ios/ui/Views/VoltraSymbol.swift +160 -0
- package/ios/ui/Views/VoltraText.swift +87 -0
- package/ios/ui/Views/VoltraTimer.swift +131 -0
- package/ios/ui/Views/VoltraToggle.swift +39 -0
- package/ios/ui/Views/VoltraVStack.swift +68 -0
- package/ios/ui/Views/VoltraZStack.swift +33 -0
- package/ios/ui/Voltra.swift +86 -0
- package/package.json +105 -19
- package/src/VoltraModule.ts +2 -0
- package/src/components/VoltraLiveActivityPreview.tsx +29 -0
- package/src/components/VoltraView.tsx +49 -0
- package/src/components/VoltraWidgetPreview.tsx +38 -0
- package/src/events.ts +70 -0
- package/src/helpers.ts +21 -0
- package/src/index.ts +52 -0
- package/src/live-activity/api.ts +224 -0
- package/src/logger.ts +35 -0
- package/src/native/NativeVoltra.ts +118 -0
- package/src/native/VoltraRNNativeComponent.ts +9 -0
- package/src/preload.ts +50 -0
- package/src/types.ts +12 -0
- package/src/utils/assertRunningOnApple.ts +10 -0
- package/src/utils/helpers.ts +1 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/useUpdateOnHMR.ts +25 -0
- package/src/widgets/server-credentials.ts +16 -0
- package/src/widgets/widget-api.ts +65 -0
- package/build/cjs/VoltraModule.js +0 -6
- package/build/cjs/VoltraModule.js.map +0 -1
- package/build/cjs/components/VoltraLiveActivityPreview.js +0 -20
- package/build/cjs/components/VoltraLiveActivityPreview.js.map +0 -1
- package/build/cjs/components/VoltraView.js +0 -37
- package/build/cjs/components/VoltraView.js.map +0 -1
- package/build/cjs/components/VoltraWidgetPreview.js +0 -26
- package/build/cjs/components/VoltraWidgetPreview.js.map +0 -1
- package/build/cjs/events.js +0 -19
- package/build/cjs/events.js.map +0 -1
- package/build/cjs/helpers.js +0 -30
- package/build/cjs/helpers.js.map +0 -1
- package/build/cjs/index.js +0 -49
- package/build/cjs/index.js.map +0 -1
- package/build/cjs/live-activity/api.js +0 -158
- package/build/cjs/live-activity/api.js.map +0 -1
- package/build/cjs/logger.js +0 -36
- package/build/cjs/logger.js.map +0 -1
- package/build/cjs/preload.js +0 -54
- package/build/cjs/preload.js.map +0 -1
- package/build/cjs/utils/assertRunningOnApple.js +0 -13
- package/build/cjs/utils/assertRunningOnApple.js.map +0 -1
- package/build/cjs/utils/helpers.js +0 -7
- package/build/cjs/utils/helpers.js.map +0 -1
- package/build/cjs/utils/index.js +0 -19
- package/build/cjs/utils/index.js.map +0 -1
- package/build/cjs/utils/useUpdateOnHMR.js +0 -22
- package/build/cjs/utils/useUpdateOnHMR.js.map +0 -1
- package/build/cjs/widgets/server-credentials.js +0 -18
- package/build/cjs/widgets/server-credentials.js.map +0 -1
- package/build/cjs/widgets/widget-api.js +0 -59
- package/build/cjs/widgets/widget-api.js.map +0 -1
- package/build/esm/VoltraModule.js +0 -4
- package/build/esm/VoltraModule.js.map +0 -1
- package/build/esm/components/VoltraLiveActivityPreview.js +0 -17
- package/build/esm/components/VoltraLiveActivityPreview.js.map +0 -1
- package/build/esm/components/VoltraView.js +0 -34
- package/build/esm/components/VoltraView.js.map +0 -1
- package/build/esm/components/VoltraWidgetPreview.js +0 -23
- package/build/esm/components/VoltraWidgetPreview.js.map +0 -1
- package/build/esm/events.js +0 -13
- package/build/esm/events.js.map +0 -1
- package/build/esm/helpers.js +0 -23
- package/build/esm/helpers.js.map +0 -1
- package/build/esm/index.js +0 -10
- package/build/esm/index.js.map +0 -1
- package/build/esm/live-activity/api.js +0 -146
- package/build/esm/live-activity/api.js.map +0 -1
- package/build/esm/logger.js +0 -33
- package/build/esm/logger.js.map +0 -1
- package/build/esm/preload.js +0 -46
- package/build/esm/preload.js.map +0 -1
- package/build/esm/utils/assertRunningOnApple.js +0 -9
- package/build/esm/utils/assertRunningOnApple.js.map +0 -1
- package/build/esm/utils/helpers.js.map +0 -1
- package/build/esm/utils/index.js.map +0 -1
- package/build/esm/utils/useUpdateOnHMR.js +0 -18
- package/build/esm/utils/useUpdateOnHMR.js.map +0 -1
- package/build/esm/widgets/server-credentials.js +0 -11
- package/build/esm/widgets/server-credentials.js.map +0 -1
- package/build/esm/widgets/widget-api.js +0 -47
- package/build/esm/widgets/widget-api.js.map +0 -1
- package/build/types/VoltraModule.d.ts +0 -44
- package/build/types/VoltraModule.d.ts.map +0 -1
- package/build/types/components/VoltraLiveActivityPreview.d.ts.map +0 -1
- package/build/types/components/VoltraView.d.ts.map +0 -1
- package/build/types/components/VoltraWidgetPreview.d.ts.map +0 -1
- package/build/types/events.d.ts.map +0 -1
- package/build/types/helpers.d.ts.map +0 -1
- package/build/types/index.d.ts.map +0 -1
- package/build/types/live-activity/api.d.ts.map +0 -1
- package/build/types/logger.d.ts.map +0 -1
- package/build/types/preload.d.ts.map +0 -1
- package/build/types/types.d.ts.map +0 -1
- package/build/types/utils/assertRunningOnApple.d.ts.map +0 -1
- package/build/types/utils/helpers.d.ts.map +0 -1
- package/build/types/utils/index.d.ts.map +0 -1
- package/build/types/utils/useUpdateOnHMR.d.ts.map +0 -1
- package/build/types/widgets/server-credentials.d.ts.map +0 -1
- package/build/types/widgets/widget-api.d.ts.map +0 -1
- /package/build/{types → typescript/commonjs}/components/VoltraLiveActivityPreview.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/components/VoltraView.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/components/VoltraWidgetPreview.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/events.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/helpers.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/live-activity/api.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/logger.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/preload.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/types.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/utils/assertRunningOnApple.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/utils/helpers.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/utils/index.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/utils/useUpdateOnHMR.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/widgets/server-credentials.d.ts +0 -0
- /package/build/{types → typescript/commonjs}/widgets/widget-api.d.ts +0 -0
- /package/{build → expo-plugin/build}/cjs/package.json +0 -0
- /package/{build → expo-plugin/build}/cjs/types.js +0 -0
- /package/{build → expo-plugin/build}/cjs/types.js.map +0 -0
- /package/{build → expo-plugin/build}/esm/package.json +0 -0
- /package/{build → expo-plugin/build}/esm/types.js +0 -0
- /package/{build → expo-plugin/build}/esm/types.js.map +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Handles fetching widget content from a remote Voltra SSR server.
|
|
5
|
+
/// Used by the TimelineProvider to pull server-driven widget updates.
|
|
6
|
+
public enum VoltraWidgetServerFetcher {
|
|
7
|
+
/// Errors that can occur during server fetch
|
|
8
|
+
public enum FetchError: Error, LocalizedError {
|
|
9
|
+
case noServerUrl
|
|
10
|
+
case invalidUrl(String)
|
|
11
|
+
case networkError(Error)
|
|
12
|
+
case httpError(statusCode: Int)
|
|
13
|
+
case invalidResponse
|
|
14
|
+
case emptyResponse
|
|
15
|
+
|
|
16
|
+
public var errorDescription: String? {
|
|
17
|
+
switch self {
|
|
18
|
+
case .noServerUrl:
|
|
19
|
+
return "No server URL configured for this widget"
|
|
20
|
+
case let .invalidUrl(url):
|
|
21
|
+
return "Invalid server URL: \(url)"
|
|
22
|
+
case let .networkError(error):
|
|
23
|
+
return "Network error: \(error.localizedDescription)"
|
|
24
|
+
case let .httpError(statusCode):
|
|
25
|
+
return "HTTP error: \(statusCode)"
|
|
26
|
+
case .invalidResponse:
|
|
27
|
+
return "Invalid response from server"
|
|
28
|
+
case .emptyResponse:
|
|
29
|
+
return "Empty response from server"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Read the server update URL for a widget from Info.plist / UserDefaults config.
|
|
35
|
+
public static func serverUrl(for widgetId: String) -> String? {
|
|
36
|
+
// Check Info.plist first (set at build time by config plugin)
|
|
37
|
+
if let urls = Bundle.main.object(forInfoDictionaryKey: VoltraStorageKeys.widgetServerUrls) as? [String: String],
|
|
38
|
+
let url = urls[widgetId]
|
|
39
|
+
{
|
|
40
|
+
return url
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback to UserDefaults (can be set at runtime)
|
|
44
|
+
if let group = VoltraConfig.groupIdentifier(),
|
|
45
|
+
let defaults = UserDefaults(suiteName: group)
|
|
46
|
+
{
|
|
47
|
+
return defaults.string(forKey: VoltraStorageKeys.widgetServerUrl(widgetId))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Read the update interval (in minutes) for a widget.
|
|
54
|
+
public static func updateInterval(for widgetId: String) -> Int {
|
|
55
|
+
if let intervals = Bundle.main.object(forInfoDictionaryKey: VoltraStorageKeys.widgetServerIntervals) as? [String: Int],
|
|
56
|
+
let interval = intervals[widgetId]
|
|
57
|
+
{
|
|
58
|
+
return interval
|
|
59
|
+
}
|
|
60
|
+
return 60 // default: 1 hour
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public static func isRefreshEnabled(for widgetId: String) -> Bool {
|
|
64
|
+
if let refreshDict = Bundle.main.object(forInfoDictionaryKey: VoltraStorageKeys.widgetServerRefresh) as? [String: Bool],
|
|
65
|
+
let enabled = refreshDict[widgetId]
|
|
66
|
+
{
|
|
67
|
+
return enabled
|
|
68
|
+
}
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private static func currentColorScheme() -> String {
|
|
73
|
+
if #available(iOSApplicationExtension 13.0, *) {
|
|
74
|
+
switch UITraitCollection.current.userInterfaceStyle {
|
|
75
|
+
case .dark:
|
|
76
|
+
return "dark"
|
|
77
|
+
case .light:
|
|
78
|
+
return "light"
|
|
79
|
+
default:
|
|
80
|
+
return "light"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return "light"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Fetch widget content from the remote Voltra SSR server.
|
|
87
|
+
///
|
|
88
|
+
/// The request includes:
|
|
89
|
+
/// - `widgetId` query parameter
|
|
90
|
+
/// - `family` query parameter (e.g., "systemSmall")
|
|
91
|
+
/// - `platform` query parameter (`ios`)
|
|
92
|
+
/// - `theme` query parameter (`light` or `dark`)
|
|
93
|
+
/// - `Authorization: Bearer <token>` header (if credentials stored in Keychain)
|
|
94
|
+
/// - Any custom headers stored in Keychain
|
|
95
|
+
///
|
|
96
|
+
/// Returns the raw JSON data from the server, ready to be parsed by VoltraNode.
|
|
97
|
+
public static func fetchWidgetContent(
|
|
98
|
+
widgetId: String,
|
|
99
|
+
family: String
|
|
100
|
+
) async throws -> Data {
|
|
101
|
+
guard let baseUrl = serverUrl(for: widgetId) else {
|
|
102
|
+
throw FetchError.noServerUrl
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Build URL with query parameters
|
|
106
|
+
guard var components = URLComponents(string: baseUrl) else {
|
|
107
|
+
throw FetchError.invalidUrl(baseUrl)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let theme = currentColorScheme()
|
|
111
|
+
|
|
112
|
+
var queryItems = components.queryItems ?? []
|
|
113
|
+
queryItems.append(URLQueryItem(name: "widgetId", value: widgetId))
|
|
114
|
+
queryItems.append(URLQueryItem(name: "family", value: family))
|
|
115
|
+
queryItems.append(URLQueryItem(name: "platform", value: "ios"))
|
|
116
|
+
queryItems.append(URLQueryItem(name: "theme", value: theme))
|
|
117
|
+
components.queryItems = queryItems
|
|
118
|
+
|
|
119
|
+
guard let url = components.url else {
|
|
120
|
+
throw FetchError.invalidUrl(baseUrl)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var request = URLRequest(url: url)
|
|
124
|
+
request.httpMethod = "GET"
|
|
125
|
+
request.timeoutInterval = 15 // Widgets have limited execution time
|
|
126
|
+
|
|
127
|
+
// Add auth token from Keychain if available
|
|
128
|
+
if let token = VoltraKeychainHelper.readToken() {
|
|
129
|
+
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Add custom headers from Keychain if available
|
|
133
|
+
if let headers = VoltraKeychainHelper.readHeaders() {
|
|
134
|
+
for (key, value) in headers {
|
|
135
|
+
request.setValue(value, forHTTPHeaderField: key)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Add Voltra-specific headers
|
|
140
|
+
let systemVersion = UIDevice.current.systemVersion
|
|
141
|
+
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
142
|
+
request.setValue("VoltraWidget/1.0 (iOS/\(systemVersion))", forHTTPHeaderField: "User-Agent")
|
|
143
|
+
|
|
144
|
+
do {
|
|
145
|
+
let (data, response) = try await URLSession.shared.data(for: request)
|
|
146
|
+
|
|
147
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
148
|
+
throw FetchError.invalidResponse
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
guard (200 ... 299).contains(httpResponse.statusCode) else {
|
|
152
|
+
throw FetchError.httpError(statusCode: httpResponse.statusCode)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
guard !data.isEmpty else {
|
|
156
|
+
throw FetchError.emptyResponse
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return data
|
|
160
|
+
} catch let error as FetchError {
|
|
161
|
+
throw error
|
|
162
|
+
} catch {
|
|
163
|
+
throw FetchError.networkError(error)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VoltraHomeWidget.swift
|
|
3
|
+
//
|
|
4
|
+
// Generic home screen widget infrastructure for Voltra.
|
|
5
|
+
// Widget definitions are generated dynamically by the config plugin.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import os
|
|
10
|
+
import SwiftUI
|
|
11
|
+
import WidgetKit
|
|
12
|
+
|
|
13
|
+
// MARK: - Shared storage helpers
|
|
14
|
+
|
|
15
|
+
public enum VoltraHomeWidgetStore {
|
|
16
|
+
public static func readJson(widgetId: String) -> Data? {
|
|
17
|
+
VoltraWidgetDefaults.widgetJson(for: widgetId).flatMap { $0.data(using: .utf8) }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static func readDeepLinkUrl(widgetId: String) -> String? {
|
|
21
|
+
VoltraWidgetDefaults.deepLinkUrl(for: widgetId)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static func readTimeline(widgetId: String) -> WidgetTimeline? {
|
|
25
|
+
guard let timelineString = VoltraWidgetDefaults.timelineString(for: widgetId),
|
|
26
|
+
let timelineData = timelineString.data(using: .utf8),
|
|
27
|
+
let json = try? JSONSerialization.jsonObject(with: timelineData) as? [String: Any],
|
|
28
|
+
let entriesJson = json["entries"] as? [[String: Any]]
|
|
29
|
+
else {
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let entries = entriesJson.compactMap { entryJson -> WidgetTimelineEntry? in
|
|
34
|
+
guard let timestampNumber = entryJson["date"] as? NSNumber,
|
|
35
|
+
let jsonString = entryJson["json"] as? String,
|
|
36
|
+
let jsonData = jsonString.data(using: .utf8)
|
|
37
|
+
else {
|
|
38
|
+
return nil
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let timestampMs = timestampNumber.doubleValue
|
|
42
|
+
let date = Date(timeIntervalSince1970: timestampMs / 1000.0)
|
|
43
|
+
let deepLinkUrl = entryJson["deepLinkUrl"] as? String
|
|
44
|
+
|
|
45
|
+
return WidgetTimelineEntry(date: date, json: jsonData, deepLinkUrl: deepLinkUrl)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let sortedEntries = entries.sorted { $0.date < $1.date }
|
|
49
|
+
return WidgetTimeline(entries: sortedEntries)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static func pruneExpiredEntries(widgetId: String) -> Int {
|
|
53
|
+
guard let timelineString = VoltraWidgetDefaults.timelineString(for: widgetId),
|
|
54
|
+
let timelineData = timelineString.data(using: .utf8),
|
|
55
|
+
var json = try? JSONSerialization.jsonObject(with: timelineData) as? [String: Any],
|
|
56
|
+
let entriesJson = json["entries"] as? [[String: Any]]
|
|
57
|
+
else {
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let now = Date()
|
|
62
|
+
let parsedEntries: [(date: Date, json: [String: Any])] = entriesJson.compactMap { entry in
|
|
63
|
+
guard let timestamp = entry["date"] as? NSNumber else { return nil }
|
|
64
|
+
let date = Date(timeIntervalSince1970: timestamp.doubleValue / 1000.0)
|
|
65
|
+
return (date: date, json: entry)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Keep all future entries, plus the most recent past entry (current state).
|
|
69
|
+
let pastEntries = parsedEntries.filter { $0.date <= now }.sorted { $0.date < $1.date }
|
|
70
|
+
let latestPastEntry = pastEntries.last?.json
|
|
71
|
+
let futureEntries = parsedEntries.filter { $0.date > now }.map(\.json)
|
|
72
|
+
|
|
73
|
+
var validEntries = futureEntries
|
|
74
|
+
if let latestPastEntry {
|
|
75
|
+
validEntries.insert(latestPastEntry, at: 0)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let prunedCount = entriesJson.count - validEntries.count
|
|
79
|
+
guard prunedCount > 0 else { return 0 }
|
|
80
|
+
|
|
81
|
+
json["entries"] = validEntries
|
|
82
|
+
if let updatedData = try? JSONSerialization.data(withJSONObject: json),
|
|
83
|
+
let updatedString = String(data: updatedData, encoding: .utf8)
|
|
84
|
+
{
|
|
85
|
+
try? VoltraWidgetDefaults.setTimeline(updatedString, for: widgetId)
|
|
86
|
+
VoltraLogger.widget.debug("Pruned \(prunedCount) expired timeline entries for '\(widgetId)'")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return prunedCount
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Timeline data structures (intermediate storage - still uses Data for flexibility)
|
|
94
|
+
public struct WidgetTimelineEntry {
|
|
95
|
+
let date: Date
|
|
96
|
+
let json: Data
|
|
97
|
+
let deepLinkUrl: String?
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public struct WidgetTimeline {
|
|
101
|
+
let entries: [WidgetTimelineEntry]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MARK: - Timeline + entries
|
|
105
|
+
|
|
106
|
+
/// The entry holds a pre-parsed VoltraNode (AST) instead of raw JSON.
|
|
107
|
+
public struct VoltraHomeWidgetEntry: TimelineEntry, Equatable {
|
|
108
|
+
public let date: Date
|
|
109
|
+
public let rootNode: VoltraNode?
|
|
110
|
+
public let widgetId: String
|
|
111
|
+
public let deepLinkUrl: String?
|
|
112
|
+
|
|
113
|
+
public init(date: Date, rootNode: VoltraNode?, widgetId: String, deepLinkUrl: String? = nil) {
|
|
114
|
+
self.date = date
|
|
115
|
+
self.rootNode = rootNode
|
|
116
|
+
self.widgetId = widgetId
|
|
117
|
+
self.deepLinkUrl = deepLinkUrl
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public struct VoltraHomeWidgetProvider: TimelineProvider {
|
|
122
|
+
public let widgetId: String
|
|
123
|
+
public let initialState: Data?
|
|
124
|
+
|
|
125
|
+
public init(widgetId: String, initialState: Data? = nil) {
|
|
126
|
+
self.widgetId = widgetId
|
|
127
|
+
self.initialState = initialState
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public func placeholder(in _: Context) -> VoltraHomeWidgetEntry {
|
|
131
|
+
VoltraHomeWidgetEntry(date: Date(), rootNode: nil, widgetId: widgetId)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public func getSnapshot(in context: Context, completion: @escaping (VoltraHomeWidgetEntry) -> Void) {
|
|
135
|
+
// Prioritize timeline data for consistency with getTimeline
|
|
136
|
+
if let timeline = VoltraHomeWidgetStore.readTimeline(widgetId: widgetId),
|
|
137
|
+
let firstEntry = timeline.entries.first
|
|
138
|
+
{
|
|
139
|
+
let node = parseJsonToNode(data: firstEntry.json, family: context.family)
|
|
140
|
+
completion(VoltraHomeWidgetEntry(
|
|
141
|
+
date: Date(),
|
|
142
|
+
rootNode: node,
|
|
143
|
+
widgetId: widgetId,
|
|
144
|
+
deepLinkUrl: firstEntry.deepLinkUrl
|
|
145
|
+
))
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fallback to single-entry data
|
|
150
|
+
let data = VoltraHomeWidgetStore.readJson(widgetId: widgetId) ?? initialState
|
|
151
|
+
let node = data.flatMap { parseJsonToNode(data: $0, family: context.family) }
|
|
152
|
+
completion(VoltraHomeWidgetEntry(date: Date(), rootNode: node, widgetId: widgetId))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public func getTimeline(in context: Context, completion: @escaping (Timeline<VoltraHomeWidgetEntry>) -> Void) {
|
|
156
|
+
// Prune expired timeline entries if any exist
|
|
157
|
+
VoltraHomeWidgetStore.pruneExpiredEntries(widgetId: widgetId)
|
|
158
|
+
|
|
159
|
+
// Check if server-driven updates are configured for this widget
|
|
160
|
+
if VoltraWidgetServerFetcher.serverUrl(for: widgetId) != nil {
|
|
161
|
+
getServerDrivenTimeline(in: context, completion: completion)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Local-only mode: use existing data from UserDefaults
|
|
166
|
+
getLocalTimeline(in: context, completion: completion)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// MARK: - Server-Driven Timeline
|
|
170
|
+
|
|
171
|
+
private static var lastFetchTimes: [String: Date] = [:]
|
|
172
|
+
private static let coalesceInterval: TimeInterval = 3 // seconds
|
|
173
|
+
|
|
174
|
+
/// Fetch widget content from a remote Voltra SSR server and build a timeline.
|
|
175
|
+
private func getServerDrivenTimeline(in context: Context, completion: @escaping (Timeline<VoltraHomeWidgetEntry>) -> Void) {
|
|
176
|
+
let familyKey = familyToKey(context.family)
|
|
177
|
+
let intervalMinutes = VoltraWidgetServerFetcher.updateInterval(for: widgetId)
|
|
178
|
+
|
|
179
|
+
// Coalesce: if we fetched for this widget very recently, use cached data.
|
|
180
|
+
// The server returns all families in every response, so subsequent getTimeline
|
|
181
|
+
// calls for different families can safely use the just-cached response —
|
|
182
|
+
// selectContentForFamily will pick the correct family-specific content.
|
|
183
|
+
if let lastFetch = Self.lastFetchTimes[widgetId],
|
|
184
|
+
Date().timeIntervalSince(lastFetch) < Self.coalesceInterval
|
|
185
|
+
{
|
|
186
|
+
VoltraLogger.widget.info("Coalescing fetch for '\(widgetId)' family '\(familyKey)' (last fetch \(Date().timeIntervalSince(lastFetch))s ago)")
|
|
187
|
+
getLocalTimeline(in: context, completion: completion)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Task {
|
|
192
|
+
do {
|
|
193
|
+
let data = try await VoltraWidgetServerFetcher.fetchWidgetContent(
|
|
194
|
+
widgetId: widgetId,
|
|
195
|
+
family: familyKey
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
// Record that we just fetched successfully
|
|
199
|
+
Self.lastFetchTimes[widgetId] = Date()
|
|
200
|
+
|
|
201
|
+
let node = parseJsonToNode(data: data, family: context.family)
|
|
202
|
+
|
|
203
|
+
// Only cache the data after successful parsing to avoid overwriting
|
|
204
|
+
// good cached content with unparseable responses
|
|
205
|
+
if node != nil, let jsonString = String(data: data, encoding: .utf8) {
|
|
206
|
+
try? VoltraWidgetDefaults.setWidgetJson(jsonString, for: widgetId, deepLinkUrl: nil)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let entry = VoltraHomeWidgetEntry(date: Date(), rootNode: node, widgetId: widgetId)
|
|
210
|
+
|
|
211
|
+
// Schedule next update based on configured interval
|
|
212
|
+
let nextUpdate = Calendar.current.date(byAdding: .minute, value: intervalMinutes, to: Date()) ?? Date().addingTimeInterval(900)
|
|
213
|
+
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
|
|
214
|
+
|
|
215
|
+
VoltraLogger.widget.info("Server-driven update succeeded for '\(widgetId)', next update in \(intervalMinutes)m")
|
|
216
|
+
} catch {
|
|
217
|
+
VoltraLogger.widget.error("Server-driven update failed for '\(widgetId)': \(error.localizedDescription)")
|
|
218
|
+
|
|
219
|
+
// Fall back to local data on network failure
|
|
220
|
+
getLocalTimeline(in: context, completion: completion)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// MARK: - Local Timeline
|
|
226
|
+
|
|
227
|
+
/// Build a timeline from locally stored data (UserDefaults / initial state).
|
|
228
|
+
private func getLocalTimeline(in context: Context, completion: @escaping (Timeline<VoltraHomeWidgetEntry>) -> Void) {
|
|
229
|
+
if let timeline = VoltraHomeWidgetStore.readTimeline(widgetId: widgetId), !timeline.entries.isEmpty {
|
|
230
|
+
let entries = timeline.entries.map { timelineEntry in
|
|
231
|
+
let node = parseJsonToNode(data: timelineEntry.json, family: context.family)
|
|
232
|
+
return VoltraHomeWidgetEntry(
|
|
233
|
+
date: timelineEntry.date,
|
|
234
|
+
rootNode: node,
|
|
235
|
+
widgetId: widgetId,
|
|
236
|
+
deepLinkUrl: timelineEntry.deepLinkUrl
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
completion(Timeline(entries: entries, policy: .never))
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Fallback to single-entry behavior
|
|
244
|
+
let data = VoltraHomeWidgetStore.readJson(widgetId: widgetId) ?? initialState
|
|
245
|
+
let node = data.flatMap { parseJsonToNode(data: $0, family: context.family) }
|
|
246
|
+
let entry = VoltraHomeWidgetEntry(date: Date(), rootNode: node, widgetId: widgetId)
|
|
247
|
+
|
|
248
|
+
// If server updates are configured but we're in fallback, retry sooner
|
|
249
|
+
if VoltraWidgetServerFetcher.serverUrl(for: widgetId) != nil {
|
|
250
|
+
let retryDate = Date().addingTimeInterval(900) // Retry in 15 minutes
|
|
251
|
+
completion(Timeline(entries: [entry], policy: .after(retryDate)))
|
|
252
|
+
} else {
|
|
253
|
+
completion(Timeline(entries: [entry], policy: .never))
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Parse JSON data into a VoltraNode for the given widget family.
|
|
258
|
+
/// This moves all parsing work to the Provider (background thread).
|
|
259
|
+
private func parseJsonToNode(data: Data, family: WidgetFamily) -> VoltraNode? {
|
|
260
|
+
// 1. Select content for the target family
|
|
261
|
+
let selectedData = selectContentForFamily(data, family: family)
|
|
262
|
+
|
|
263
|
+
// 2. Normalize the JSON
|
|
264
|
+
guard let normalized = normalizeJsonData(selectedData),
|
|
265
|
+
let jsonString = String(data: normalized, encoding: .utf8),
|
|
266
|
+
let json = try? JSONValue.parse(from: jsonString)
|
|
267
|
+
else {
|
|
268
|
+
return nil
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 3. Parse into VoltraNode AST
|
|
272
|
+
return VoltraNode.parse(from: json)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public struct VoltraHomeWidgetView: View {
|
|
277
|
+
public var entry: VoltraHomeWidgetEntry
|
|
278
|
+
|
|
279
|
+
@Environment(\.showsWidgetContainerBackground) private var showsWidgetContainerBackground
|
|
280
|
+
@Environment(\.widgetRenderingMode) private var widgetRenderingMode
|
|
281
|
+
|
|
282
|
+
public init(entry: VoltraHomeWidgetEntry) {
|
|
283
|
+
self.entry = entry
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private var showRefreshButton: Bool {
|
|
287
|
+
VoltraWidgetServerFetcher.isRefreshEnabled(for: entry.widgetId)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public var body: some View {
|
|
291
|
+
let mappedRenderingMode = mapWidgetRenderingMode(widgetRenderingMode)
|
|
292
|
+
|
|
293
|
+
Group {
|
|
294
|
+
if let root = entry.rootNode {
|
|
295
|
+
// No parsing here - just render the pre-parsed AST
|
|
296
|
+
let content = Voltra(
|
|
297
|
+
root: root,
|
|
298
|
+
activityId: "widget",
|
|
299
|
+
widget: VoltraWidgetEnvironment(
|
|
300
|
+
isHomeScreenWidget: true,
|
|
301
|
+
renderingMode: mappedRenderingMode,
|
|
302
|
+
showsContainerBackground: showsWidgetContainerBackground
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
306
|
+
.widgetURL(resolveDeepLinkURL(entry))
|
|
307
|
+
|
|
308
|
+
if showRefreshButton {
|
|
309
|
+
content.overlay(alignment: .topTrailing) {
|
|
310
|
+
refreshButton
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
content
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
placeholderView(widgetId: entry.widgetId)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
.disableWidgetMarginsIfAvailable()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private func mapWidgetRenderingMode(_ mode: WidgetRenderingMode) -> VoltraWidgetRenderingMode {
|
|
323
|
+
switch mode {
|
|
324
|
+
case .fullColor:
|
|
325
|
+
return .fullColor
|
|
326
|
+
case .accented:
|
|
327
|
+
return .accented
|
|
328
|
+
case .vibrant:
|
|
329
|
+
return .vibrant
|
|
330
|
+
default:
|
|
331
|
+
return .unknown
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@ViewBuilder
|
|
336
|
+
private var refreshButton: some View {
|
|
337
|
+
if #available(iOSApplicationExtension 17.0, *) {
|
|
338
|
+
Button(intent: VoltraRefreshIntent(widgetId: entry.widgetId)) {
|
|
339
|
+
Image(systemName: "arrow.clockwise")
|
|
340
|
+
.font(.system(size: 12, weight: .semibold))
|
|
341
|
+
.foregroundStyle(.secondary)
|
|
342
|
+
.padding(6)
|
|
343
|
+
.background(.ultraThinMaterial, in: Circle())
|
|
344
|
+
}
|
|
345
|
+
.buttonStyle(.plain)
|
|
346
|
+
.padding(12)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private func placeholderView(widgetId _: String) -> some View {
|
|
351
|
+
ZStack {
|
|
352
|
+
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
|
353
|
+
.fill(Color(UIColor.secondarySystemBackground))
|
|
354
|
+
ProgressView()
|
|
355
|
+
.progressViewStyle(.circular)
|
|
356
|
+
}
|
|
357
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
358
|
+
.padding(16)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// MARK: - Family-aware content selection
|
|
363
|
+
|
|
364
|
+
/// Maps WidgetFamily to the JSON key
|
|
365
|
+
private func familyToKey(_ family: WidgetFamily) -> String {
|
|
366
|
+
switch family {
|
|
367
|
+
case .systemSmall: return "systemSmall"
|
|
368
|
+
case .systemMedium: return "systemMedium"
|
|
369
|
+
case .systemLarge: return "systemLarge"
|
|
370
|
+
case .systemExtraLarge: return "systemExtraLarge"
|
|
371
|
+
case .accessoryCircular: return "accessoryCircular"
|
|
372
|
+
case .accessoryRectangular: return "accessoryRectangular"
|
|
373
|
+
case .accessoryInline: return "accessoryInline"
|
|
374
|
+
@unknown default: return "systemMedium"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/// Select content appropriate for the current widget family.
|
|
379
|
+
/// Uses the flat structure like live activities (e.g., "systemSmall", "systemMedium").
|
|
380
|
+
private func selectContentForFamily(_ data: Data, family: WidgetFamily) -> Data {
|
|
381
|
+
guard let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
382
|
+
// Not valid JSON, return empty
|
|
383
|
+
return Data("[]".utf8)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let familyKey = familyToKey(family)
|
|
387
|
+
|
|
388
|
+
// Try to get content for the specific family
|
|
389
|
+
if let familyContent = root[familyKey] {
|
|
390
|
+
return reconstructWithSharedData(content: familyContent, root: root)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Fallback: try families in order of preference
|
|
394
|
+
let fallbackOrder = ["systemMedium", "systemSmall", "systemLarge", "systemExtraLarge",
|
|
395
|
+
"accessoryRectangular", "accessoryCircular", "accessoryInline"]
|
|
396
|
+
for fallbackKey in fallbackOrder {
|
|
397
|
+
if let fallbackContent = root[fallbackKey] {
|
|
398
|
+
return reconstructWithSharedData(content: fallbackContent, root: root)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// No content found, return empty
|
|
403
|
+
return Data("[]".utf8)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// Reconstruct JSON with family-specific content plus shared stylesheet and elements.
|
|
407
|
+
/// This ensures VoltraNode.parse can resolve style references and element deduplication.
|
|
408
|
+
private func reconstructWithSharedData(content: Any, root: [String: Any]) -> Data {
|
|
409
|
+
var result: [String: Any] = [:]
|
|
410
|
+
|
|
411
|
+
// If content is a dictionary (single component), wrap it in the result
|
|
412
|
+
// If content is an array or other type, it will be returned as-is below
|
|
413
|
+
if let contentDict = content as? [String: Any] {
|
|
414
|
+
// Copy all keys from the component
|
|
415
|
+
result = contentDict
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Add shared stylesheet if present (key "s")
|
|
419
|
+
if let stylesheet = root["s"] {
|
|
420
|
+
result["s"] = stylesheet
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Add shared elements if present (key "e")
|
|
424
|
+
if let sharedElements = root["e"] {
|
|
425
|
+
result["e"] = sharedElements
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// If we built a result dict with shared data, serialize it
|
|
429
|
+
if !result.isEmpty {
|
|
430
|
+
if JSONSerialization.isValidJSONObject(result),
|
|
431
|
+
let data = try? JSONSerialization.data(withJSONObject: result)
|
|
432
|
+
{
|
|
433
|
+
return data
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Fallback: return content as-is if it's serializable
|
|
438
|
+
if JSONSerialization.isValidJSONObject(content),
|
|
439
|
+
let data = try? JSONSerialization.data(withJSONObject: content)
|
|
440
|
+
{
|
|
441
|
+
return data
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Final fallback: empty array
|
|
445
|
+
return Data("[]".utf8)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// MARK: - Deep link helpers
|
|
449
|
+
|
|
450
|
+
private extension View {
|
|
451
|
+
@ViewBuilder
|
|
452
|
+
func disableWidgetMarginsIfAvailable() -> some View {
|
|
453
|
+
if #available(iOSApplicationExtension 17.0, *) {
|
|
454
|
+
containerBackground(.clear, for: .widget)
|
|
455
|
+
} else {
|
|
456
|
+
self
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private func resolveDeepLinkURL(_ entry: VoltraHomeWidgetEntry) -> URL? {
|
|
462
|
+
// Prefer the timeline entry's deep link URL if available
|
|
463
|
+
if let entryUrl = entry.deepLinkUrl, !entryUrl.isEmpty {
|
|
464
|
+
if entryUrl.contains("://"), let url = URL(string: entryUrl) { return url }
|
|
465
|
+
if let scheme = VoltraDeepLinkResolver.deepLinkScheme() {
|
|
466
|
+
let path = entryUrl.hasPrefix("/") ? entryUrl : "/\(entryUrl)"
|
|
467
|
+
return URL(string: "\(scheme)://\(path.trimmingCharacters(in: CharacterSet(charactersIn: "/")))")
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Fallback to static deep link URL from UserDefaults
|
|
472
|
+
if let raw = VoltraHomeWidgetStore.readDeepLinkUrl(widgetId: entry.widgetId), !raw.isEmpty {
|
|
473
|
+
if raw.contains("://"), let url = URL(string: raw) { return url }
|
|
474
|
+
if let scheme = VoltraDeepLinkResolver.deepLinkScheme() {
|
|
475
|
+
let path = raw.hasPrefix("/") ? raw : "/\(raw)"
|
|
476
|
+
return URL(string: "\(scheme)://\(path.trimmingCharacters(in: CharacterSet(charactersIn: "/")))")
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Default deep link with widget info
|
|
481
|
+
guard let scheme = VoltraDeepLinkResolver.deepLinkScheme() else { return nil }
|
|
482
|
+
|
|
483
|
+
var tag = "unknown"
|
|
484
|
+
if let root = entry.rootNode, case let .element(element) = root {
|
|
485
|
+
tag = element.id ?? element.type
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return URL(string: "\(scheme)://voltraui?kind=widget&source=home_widget&tag=\(tag)")
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private func normalizeJsonData(_ data: Data) -> Data? {
|
|
492
|
+
guard let obj = try? JSONSerialization.jsonObject(with: data) else { return nil }
|
|
493
|
+
|
|
494
|
+
// If it's already an array, use it as-is
|
|
495
|
+
if obj is [Any] {
|
|
496
|
+
return data
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// If it's a single component (dictionary), return as-is
|
|
500
|
+
// Don't wrap in array - VoltraNode.parse handles single objects and needs
|
|
501
|
+
// to find "s" (stylesheet) and "e" (elements) at the root level
|
|
502
|
+
if obj is [String: Any] {
|
|
503
|
+
return data
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Invalid input (string, number, boolean, null) - return nil to indicate error
|
|
507
|
+
return nil
|
|
508
|
+
}
|