@use-voltra/ios-client 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +61 -8
  2. package/Voltra.podspec +1 -0
  3. package/build/commonjs/dynamic-widget/api.js +15 -0
  4. package/build/commonjs/dynamic-widget/api.js.map +1 -0
  5. package/build/commonjs/events.js +23 -0
  6. package/build/commonjs/events.js.map +1 -1
  7. package/build/commonjs/index.js +74 -7
  8. package/build/commonjs/index.js.map +1 -1
  9. package/build/commonjs/live-activity/api.js +3 -1
  10. package/build/commonjs/live-activity/api.js.map +1 -1
  11. package/build/commonjs/live-activity/dynamic-api.js +173 -0
  12. package/build/commonjs/live-activity/dynamic-api.js.map +1 -0
  13. package/build/commonjs/native/NativeVoltra.js.map +1 -1
  14. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js +47 -0
  15. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  16. package/build/commonjs/utils/enableWidgetHotReload.js +8 -11
  17. package/build/commonjs/utils/enableWidgetHotReload.js.map +1 -1
  18. package/build/commonjs/utils/useUpdateOnHMR.js +3 -7
  19. package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -1
  20. package/build/commonjs/widgets/server-credentials.js +9 -0
  21. package/build/commonjs/widgets/server-credentials.js.map +1 -1
  22. package/build/commonjs/widgets/server-update.js +78 -0
  23. package/build/commonjs/widgets/server-update.js.map +1 -0
  24. package/build/module/dynamic-widget/api.js +10 -0
  25. package/build/module/dynamic-widget/api.js.map +1 -0
  26. package/build/module/events.js +23 -0
  27. package/build/module/events.js.map +1 -1
  28. package/build/module/index.js +4 -0
  29. package/build/module/index.js.map +1 -1
  30. package/build/module/live-activity/api.js +2 -2
  31. package/build/module/live-activity/api.js.map +1 -1
  32. package/build/module/live-activity/dynamic-api.js +167 -0
  33. package/build/module/live-activity/dynamic-api.js.map +1 -0
  34. package/build/module/native/NativeVoltra.js.map +1 -1
  35. package/build/module/utils/enableDynamicLiveActivityHotReload.js +43 -0
  36. package/build/module/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  37. package/build/module/utils/enableWidgetHotReload.js +9 -11
  38. package/build/module/utils/enableWidgetHotReload.js.map +1 -1
  39. package/build/module/utils/useUpdateOnHMR.js +3 -7
  40. package/build/module/utils/useUpdateOnHMR.js.map +1 -1
  41. package/build/module/widgets/server-credentials.js +9 -0
  42. package/build/module/widgets/server-credentials.js.map +1 -1
  43. package/build/module/widgets/server-update.js +72 -0
  44. package/build/module/widgets/server-update.js.map +1 -0
  45. package/build/typescript/commonjs/dynamic-widget/api.d.ts +8 -0
  46. package/build/typescript/commonjs/dynamic-widget/api.d.ts.map +1 -0
  47. package/build/typescript/commonjs/events.d.ts +7 -0
  48. package/build/typescript/commonjs/events.d.ts.map +1 -1
  49. package/build/typescript/commonjs/index.d.ts +4 -0
  50. package/build/typescript/commonjs/index.d.ts.map +1 -1
  51. package/build/typescript/commonjs/live-activity/api.d.ts +7 -0
  52. package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -1
  53. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts +54 -0
  54. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts.map +1 -0
  55. package/build/typescript/commonjs/native/NativeVoltra.d.ts +21 -0
  56. package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -1
  57. package/build/typescript/commonjs/types.d.ts +1 -1
  58. package/build/typescript/commonjs/types.d.ts.map +1 -1
  59. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  60. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  61. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts +5 -7
  62. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts.map +1 -1
  63. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts +0 -3
  64. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -1
  65. package/build/typescript/commonjs/widgets/server-credentials.d.ts +8 -0
  66. package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -1
  67. package/build/typescript/commonjs/widgets/server-update.d.ts +64 -0
  68. package/build/typescript/commonjs/widgets/server-update.d.ts.map +1 -0
  69. package/build/typescript/module/dynamic-widget/api.d.ts +8 -0
  70. package/build/typescript/module/dynamic-widget/api.d.ts.map +1 -0
  71. package/build/typescript/module/events.d.ts +7 -0
  72. package/build/typescript/module/events.d.ts.map +1 -1
  73. package/build/typescript/module/index.d.ts +4 -0
  74. package/build/typescript/module/index.d.ts.map +1 -1
  75. package/build/typescript/module/live-activity/api.d.ts +7 -0
  76. package/build/typescript/module/live-activity/api.d.ts.map +1 -1
  77. package/build/typescript/module/live-activity/dynamic-api.d.ts +54 -0
  78. package/build/typescript/module/live-activity/dynamic-api.d.ts.map +1 -0
  79. package/build/typescript/module/native/NativeVoltra.d.ts +21 -0
  80. package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -1
  81. package/build/typescript/module/types.d.ts +1 -1
  82. package/build/typescript/module/types.d.ts.map +1 -1
  83. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  84. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  85. package/build/typescript/module/utils/enableWidgetHotReload.d.ts +5 -7
  86. package/build/typescript/module/utils/enableWidgetHotReload.d.ts.map +1 -1
  87. package/build/typescript/module/utils/useUpdateOnHMR.d.ts +0 -3
  88. package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -1
  89. package/build/typescript/module/widgets/server-credentials.d.ts +8 -0
  90. package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -1
  91. package/build/typescript/module/widgets/server-update.d.ts +64 -0
  92. package/build/typescript/module/widgets/server-update.d.ts.map +1 -0
  93. package/expo-plugin/build/cjs/constants.js +20 -1
  94. package/expo-plugin/build/cjs/constants.js.map +1 -1
  95. package/expo-plugin/build/cjs/index.js +9 -1
  96. package/expo-plugin/build/cjs/index.js.map +1 -1
  97. package/expo-plugin/build/cjs/ios/index.js +1 -0
  98. package/expo-plugin/build/cjs/ios/index.js.map +1 -1
  99. package/expo-plugin/build/cjs/ios/infoPlist.js +28 -5
  100. package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -1
  101. package/expo-plugin/build/cjs/ios/serverUpdate.js +25 -0
  102. package/expo-plugin/build/cjs/ios/serverUpdate.js.map +1 -0
  103. package/expo-plugin/build/cjs/ios-widget/clientRendered.js +4 -4
  104. package/expo-plugin/build/cjs/ios-widget/clientRendered.js.map +1 -1
  105. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js +5 -5
  106. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js.map +1 -1
  107. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js +130 -0
  108. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  109. package/expo-plugin/build/cjs/ios-widget/files/index.js +9 -2
  110. package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -1
  111. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +6 -3
  112. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -1
  113. package/expo-plugin/build/cjs/ios-widget/files/manifest.js +24 -0
  114. package/expo-plugin/build/cjs/ios-widget/files/manifest.js.map +1 -1
  115. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js +7 -0
  116. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js.map +1 -0
  117. package/expo-plugin/build/cjs/ios-widget/files/swift.js +56 -26
  118. package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -1
  119. package/expo-plugin/build/cjs/ios-widget/index.js +9 -3
  120. package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -1
  121. package/expo-plugin/build/cjs/ios-widget/podfile.js +38 -12
  122. package/expo-plugin/build/cjs/ios-widget/podfile.js.map +1 -1
  123. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +34 -22
  124. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -1
  125. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +194 -118
  126. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -1
  127. package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js +30 -22
  128. package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js.map +1 -1
  129. package/expo-plugin/build/cjs/ios-widget/xcode/fileReferences.js +50 -0
  130. package/expo-plugin/build/cjs/ios-widget/xcode/fileReferences.js.map +1 -0
  131. package/expo-plugin/build/cjs/ios-widget/xcode/groups.js +45 -43
  132. package/expo-plugin/build/cjs/ios-widget/xcode/groups.js.map +1 -1
  133. package/expo-plugin/build/cjs/ios-widget/xcode/index.js +93 -91
  134. package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -1
  135. package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js +41 -39
  136. package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js.map +1 -1
  137. package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js +2 -2
  138. package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js.map +1 -1
  139. package/expo-plugin/build/cjs/ios-widget/xcode/target.js +29 -63
  140. package/expo-plugin/build/cjs/ios-widget/xcode/target.js.map +1 -1
  141. package/expo-plugin/build/cjs/utils/urlScheme.js +36 -14
  142. package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -1
  143. package/expo-plugin/build/cjs/validation.js +54 -0
  144. package/expo-plugin/build/cjs/validation.js.map +1 -1
  145. package/expo-plugin/build/esm/constants.js +17 -0
  146. package/expo-plugin/build/esm/constants.js.map +1 -1
  147. package/expo-plugin/build/esm/index.js +9 -1
  148. package/expo-plugin/build/esm/index.js.map +1 -1
  149. package/expo-plugin/build/esm/ios/index.js +1 -0
  150. package/expo-plugin/build/esm/ios/index.js.map +1 -1
  151. package/expo-plugin/build/esm/ios/infoPlist.js +28 -5
  152. package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -1
  153. package/expo-plugin/build/esm/ios/serverUpdate.js +21 -0
  154. package/expo-plugin/build/esm/ios/serverUpdate.js.map +1 -0
  155. package/expo-plugin/build/esm/ios-widget/clientRendered.js +5 -5
  156. package/expo-plugin/build/esm/ios-widget/clientRendered.js.map +1 -1
  157. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js +6 -6
  158. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js.map +1 -1
  159. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js +122 -0
  160. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  161. package/expo-plugin/build/esm/ios-widget/files/index.js +10 -3
  162. package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -1
  163. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +6 -3
  164. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -1
  165. package/expo-plugin/build/esm/ios-widget/files/manifest.js +23 -1
  166. package/expo-plugin/build/esm/ios-widget/files/manifest.js.map +1 -1
  167. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js +4 -0
  168. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js.map +1 -0
  169. package/expo-plugin/build/esm/ios-widget/files/swift.js +50 -20
  170. package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -1
  171. package/expo-plugin/build/esm/ios-widget/index.js +9 -3
  172. package/expo-plugin/build/esm/ios-widget/index.js.map +1 -1
  173. package/expo-plugin/build/esm/ios-widget/podfile.js +36 -11
  174. package/expo-plugin/build/esm/ios-widget/podfile.js.map +1 -1
  175. package/expo-plugin/build/esm/ios-widget/widgetPlist.js +34 -22
  176. package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -1
  177. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +194 -117
  178. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -1
  179. package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js +31 -22
  180. package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js.map +1 -1
  181. package/expo-plugin/build/esm/ios-widget/xcode/fileReferences.js +45 -0
  182. package/expo-plugin/build/esm/ios-widget/xcode/fileReferences.js.map +1 -0
  183. package/expo-plugin/build/esm/ios-widget/xcode/groups.js +45 -42
  184. package/expo-plugin/build/esm/ios-widget/xcode/groups.js.map +1 -1
  185. package/expo-plugin/build/esm/ios-widget/xcode/index.js +97 -96
  186. package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -1
  187. package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js +41 -39
  188. package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js.map +1 -1
  189. package/expo-plugin/build/esm/ios-widget/xcode/productFile.js +3 -2
  190. package/expo-plugin/build/esm/ios-widget/xcode/productFile.js.map +1 -1
  191. package/expo-plugin/build/esm/ios-widget/xcode/target.js +28 -62
  192. package/expo-plugin/build/esm/ios-widget/xcode/target.js.map +1 -1
  193. package/expo-plugin/build/esm/utils/urlScheme.js +34 -14
  194. package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -1
  195. package/expo-plugin/build/esm/validation.js +54 -1
  196. package/expo-plugin/build/esm/validation.js.map +1 -1
  197. package/expo-plugin/build/types/constants.d.ts +10 -1
  198. package/expo-plugin/build/types/constants.d.ts.map +1 -1
  199. package/expo-plugin/build/types/index.d.ts +1 -1
  200. package/expo-plugin/build/types/index.d.ts.map +1 -1
  201. package/expo-plugin/build/types/ios/index.d.ts +2 -0
  202. package/expo-plugin/build/types/ios/index.d.ts.map +1 -1
  203. package/expo-plugin/build/types/ios/infoPlist.d.ts +2 -0
  204. package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -1
  205. package/expo-plugin/build/types/ios/serverUpdate.d.ts +11 -0
  206. package/expo-plugin/build/types/ios/serverUpdate.d.ts.map +1 -0
  207. package/expo-plugin/build/types/ios-widget/clientRendered.d.ts.map +1 -1
  208. package/expo-plugin/build/types/ios-widget/clientRenderedPrerender.d.ts.map +1 -1
  209. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts +7 -0
  210. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts.map +1 -0
  211. package/expo-plugin/build/types/ios-widget/files/index.d.ts +3 -1
  212. package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -1
  213. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +2 -1
  214. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -1
  215. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts +9 -2
  216. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts.map +1 -1
  217. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts +2 -0
  218. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts.map +1 -0
  219. package/expo-plugin/build/types/ios-widget/files/swift.d.ts +6 -2
  220. package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -1
  221. package/expo-plugin/build/types/ios-widget/index.d.ts +3 -1
  222. package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -1
  223. package/expo-plugin/build/types/ios-widget/podfile.d.ts +19 -1
  224. package/expo-plugin/build/types/ios-widget/podfile.d.ts.map +1 -1
  225. package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -1
  226. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +6 -8
  227. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -1
  228. package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts +15 -10
  229. package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts.map +1 -1
  230. package/expo-plugin/build/types/ios-widget/xcode/fileReferences.d.ts +16 -0
  231. package/expo-plugin/build/types/ios-widget/xcode/fileReferences.d.ts.map +1 -0
  232. package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts +0 -4
  233. package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts.map +1 -1
  234. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +23 -3
  235. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -1
  236. package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts +16 -4
  237. package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts.map +1 -1
  238. package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts +0 -4
  239. package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts.map +1 -1
  240. package/expo-plugin/build/types/ios-widget/xcode/target.d.ts +11 -26
  241. package/expo-plugin/build/types/ios-widget/xcode/target.d.ts.map +1 -1
  242. package/expo-plugin/build/types/types.d.ts +23 -4
  243. package/expo-plugin/build/types/types.d.ts.map +1 -1
  244. package/expo-plugin/build/types/utils/urlScheme.d.ts +16 -0
  245. package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -1
  246. package/expo-plugin/build/types/validation.d.ts +2 -1
  247. package/expo-plugin/build/types/validation.d.ts.map +1 -1
  248. package/expo-plugin/src/constants.ts +24 -1
  249. package/expo-plugin/src/index.ts +14 -1
  250. package/expo-plugin/src/ios/index.ts +3 -0
  251. package/expo-plugin/src/ios/infoPlist.ts +33 -5
  252. package/expo-plugin/src/ios/serverUpdate.ts +29 -0
  253. package/expo-plugin/src/ios-widget/clientRendered.ts +9 -5
  254. package/expo-plugin/src/ios-widget/clientRenderedPrerender.ts +11 -6
  255. package/expo-plugin/src/ios-widget/dynamic-live-activity/swift.ts +138 -0
  256. package/expo-plugin/src/ios-widget/files/index.ts +15 -4
  257. package/expo-plugin/src/ios-widget/files/infoPlist.ts +17 -3
  258. package/expo-plugin/src/ios-widget/files/manifest.ts +42 -2
  259. package/expo-plugin/src/ios-widget/files/swift-utils.ts +3 -0
  260. package/expo-plugin/src/ios-widget/files/swift.ts +69 -22
  261. package/expo-plugin/src/ios-widget/index.ts +13 -3
  262. package/expo-plugin/src/ios-widget/podfile.ts +46 -13
  263. package/expo-plugin/src/ios-widget/widgetPlist.ts +37 -23
  264. package/expo-plugin/src/ios-widget/xcode/__fixtures__/fresh.pbxproj +197 -0
  265. package/expo-plugin/src/ios-widget/xcode/__fixtures__/with-existing-extension.pbxproj +321 -0
  266. package/expo-plugin/src/ios-widget/xcode/__fixtures__/with-per-config-signing.pbxproj +203 -0
  267. package/expo-plugin/src/ios-widget/xcode/__snapshots__/applyXcodeChanges.node.test.ts.snap +363 -0
  268. package/expo-plugin/src/ios-widget/xcode/__tests__/pbxConsistency.ts +130 -0
  269. package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +210 -164
  270. package/expo-plugin/src/ios-widget/xcode/configurationList.ts +52 -35
  271. package/expo-plugin/src/ios-widget/xcode/fileReferences.ts +51 -0
  272. package/expo-plugin/src/ios-widget/xcode/groups.ts +52 -54
  273. package/expo-plugin/src/ios-widget/xcode/index.ts +124 -119
  274. package/expo-plugin/src/ios-widget/xcode/mainAppSettings.ts +59 -54
  275. package/expo-plugin/src/ios-widget/xcode/productFile.ts +3 -2
  276. package/expo-plugin/src/ios-widget/xcode/target.ts +31 -83
  277. package/expo-plugin/src/types.ts +28 -4
  278. package/expo-plugin/src/utils/urlScheme.ts +47 -17
  279. package/expo-plugin/src/validation.ts +76 -1
  280. package/ios/Package.swift +39 -4
  281. package/ios/VoltraWidget.podspec +1 -0
  282. package/ios/app/NativeVoltra.mm +126 -7
  283. package/ios/app/VoltraLiveActivityManager.swift +30 -0
  284. package/ios/app/VoltraLiveActivityService.swift +77 -5
  285. package/ios/app/VoltraModule.swift +117 -1
  286. package/ios/app/VoltraModuleImpl.swift +116 -2
  287. package/ios/app/VoltraViewComponentView.mm +3 -3
  288. package/ios/app/VoltraWidgetService.swift +147 -12
  289. package/ios/app/dynamic-live-activity/VoltraDynamicLiveActivityService.swift +97 -0
  290. package/ios/app/dynamic-live-activity/VoltraLiveActivityChronology.swift +68 -0
  291. package/ios/shared/DynamicWidgetPropsCodec.swift +51 -0
  292. package/ios/shared/DynamicWidgetPropsStorage.swift +53 -0
  293. package/ios/shared/DynamicWidgetPropsStore.swift +117 -0
  294. package/ios/shared/DynamicWidgetRenderCoordinator.swift +31 -0
  295. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerFetchResolver.swift +34 -0
  296. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerProps.swift +55 -0
  297. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerPropsStore.swift +146 -0
  298. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerUpdateRunner.swift +193 -0
  299. package/ios/shared/DynamicWidgetUpdater.swift +30 -0
  300. package/ios/shared/ServerWidgetContentResolver.swift +60 -0
  301. package/ios/shared/ServerWidgetResponseStore.swift +25 -0
  302. package/ios/shared/ShortNames.swift +6 -0
  303. package/ios/shared/VoltraConfig.swift +41 -0
  304. package/ios/shared/VoltraConstants.swift +19 -0
  305. package/ios/shared/VoltraElement.swift +127 -43
  306. package/ios/shared/VoltraEventBus.swift +52 -7
  307. package/ios/shared/VoltraJSRenderer.swift +93 -23
  308. package/ios/shared/VoltraNode.swift +7 -111
  309. package/ios/shared/VoltraRefreshIntent.swift +1 -1
  310. package/ios/shared/VoltraWidgetDefaults.swift +9 -1
  311. package/ios/shared/VoltraWidgetKind.swift +41 -0
  312. package/ios/shared/VoltraWidgetServerFetcher.swift +52 -107
  313. package/ios/shared/WidgetServer/VoltraWidgetAppearance.swift +17 -0
  314. package/ios/shared/WidgetServer/VoltraWidgetServer.swift +138 -0
  315. package/ios/shared/WidgetServer/WidgetCanonicalConfiguration.swift +77 -0
  316. package/ios/shared/WidgetServer/WidgetScope.swift +55 -0
  317. package/ios/shared/WidgetServer/WidgetServerEtagStore.swift +55 -0
  318. package/ios/shared/WidgetServer/WidgetServerFetcher.swift +179 -0
  319. package/ios/shared/WidgetServer/WidgetServerRequestBuilder.swift +114 -0
  320. package/ios/shared/WidgetServer/WidgetServerSettingsCodec.swift +47 -0
  321. package/ios/shared/WidgetServer/WidgetServerSettingsResolver.swift +129 -0
  322. package/ios/shared/WidgetServer/WidgetServerSettingsStore.swift +170 -0
  323. package/ios/shared/WidgetServer/WidgetServerSettingsValidator.swift +73 -0
  324. package/ios/shared/WidgetServer/WidgetServerUpdateSettings.swift +123 -0
  325. package/ios/shared/WidgetServer/WidgetServerUpdateSettingsJson.swift +115 -0
  326. package/ios/shared/dynamic-live-activity/DynamicLiveActivityPropsCodec.swift +13 -0
  327. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityBundleSource.swift +156 -0
  328. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityObserver.swift +97 -0
  329. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityOperations.swift +105 -0
  330. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityPayloadValidator.swift +55 -0
  331. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRegistry.swift +143 -0
  332. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureQueue.swift +158 -0
  333. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureReporter.swift +120 -0
  334. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderer.swift +370 -0
  335. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityTypes.swift +87 -0
  336. package/ios/shared/dynamic-live-activity/VoltraLiveActivityOrder.swift +19 -0
  337. package/ios/target/VoltraClientWidgetRuntime.swift +60 -10
  338. package/ios/target/VoltraDynamicWidgetServerUpdateProvider.swift +204 -0
  339. package/ios/target/VoltraHomeWidget.swift +91 -92
  340. package/ios/target/VoltraWidget.swift +3 -1
  341. package/ios/ui/Extensions/VoltraNode+View.swift +113 -0
  342. package/ios/ui/Generated/Parameters/ChartParameters.swift +3 -0
  343. package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +5 -3
  344. package/ios/ui/Helpers/VoltraProgressDriver.swift +6 -2
  345. package/ios/ui/Layout/VoltraFlexStackLayout.swift +9 -3
  346. package/ios/ui/Style/JSColorParser.swift +24 -8
  347. package/ios/ui/Style/JSGradientParser.swift +36 -12
  348. package/ios/ui/Style/JSStyleParser.swift +36 -12
  349. package/ios/ui/Style/LayoutStyle.swift +12 -4
  350. package/ios/ui/Views/VoltraChart.swift +160 -16
  351. package/ios/ui/Views/VoltraLinearGradient.swift +6 -2
  352. package/package.json +5 -5
  353. package/src/dynamic-widget/api.ts +25 -0
  354. package/src/events.ts +34 -0
  355. package/src/index.ts +23 -0
  356. package/src/live-activity/api.ts +2 -2
  357. package/src/live-activity/dynamic-api.ts +240 -0
  358. package/src/native/NativeVoltra.ts +22 -0
  359. package/src/types.ts +4 -0
  360. package/src/utils/enableDynamicLiveActivityHotReload.ts +50 -0
  361. package/src/utils/enableWidgetHotReload.ts +9 -16
  362. package/src/utils/useUpdateOnHMR.ts +3 -12
  363. package/src/widgets/server-credentials.ts +8 -0
  364. package/src/widgets/server-update.ts +84 -0
  365. /package/ios/shared/{VoltraKeychainHelper.swift → WidgetServer/VoltraKeychainHelper.swift} +0 -0
@@ -89,6 +89,14 @@ public class VoltraModuleImpl {
89
89
  VoltraHeadlessState.shared.clear()
90
90
  }
91
91
 
92
+ func drainDynamicLiveActivityRenderFailures() {
93
+ VoltraEventBus.shared.drainDynamicLiveActivityRenderFailures()
94
+ }
95
+
96
+ func setDynamicLiveActivityRenderFailureListenerActive(_ active: Bool) {
97
+ VoltraEventBus.shared.setDynamicLiveActivityRenderFailureListenerActive(active)
98
+ }
99
+
92
100
  var pushNotificationsEnabled: Bool {
93
101
  // Support both keys for compatibility with older plugin and new Voltra naming
94
102
  let main = Bundle.main
@@ -182,6 +190,48 @@ public class VoltraModuleImpl {
182
190
  }
183
191
  }
184
192
 
193
+ func startDynamicLiveActivity(definitionId: String, propsJson: String, options: StartVoltraOptions?) async throws -> String {
194
+ guard #available(iOS 16.4, *) else { throw VoltraErrors.unsupportedOS }
195
+ let name = options?.activityName?.trimmingCharacters(in: .whitespacesAndNewlines)
196
+ let activityName = name?.isEmpty == false ? name! : UUID().uuidString
197
+ do {
198
+ let props = try VoltraDynamicLiveActivityPayloadValidator.decodeProps(propsJson)
199
+ let staleDate = options?.staleDate.map { Date(timeIntervalSince1970: $0.doubleValue / 1000.0) }
200
+ let pushType = try resolvePushType(channelId: options?.channelId)
201
+ let request = VoltraDynamicLiveActivityCreateRequest(
202
+ definitionId: definitionId,
203
+ name: activityName,
204
+ deepLinkUrl: options?.deepLinkUrl,
205
+ props: props,
206
+ staleDate: staleDate,
207
+ relevanceScore: options?.relevanceScore?.doubleValue ?? 0.0,
208
+ pushType: pushType
209
+ )
210
+ return try await liveActivityService.createDynamicActivity(request)
211
+ } catch {
212
+ VoltraLogger.module.error("startDynamicLiveActivity failed: \(error)")
213
+ throw mapError(error)
214
+ }
215
+ }
216
+
217
+ func updateDynamicLiveActivity(activityId: String, propsJson: String, options: UpdateVoltraOptions?) async throws {
218
+ guard #available(iOS 16.4, *) else { throw VoltraErrors.unsupportedOS }
219
+ do {
220
+ let props = try VoltraDynamicLiveActivityPayloadValidator.decodeProps(propsJson)
221
+ let staleDate = options?.staleDate.map { Date(timeIntervalSince1970: $0.doubleValue / 1000.0) }
222
+ try await liveActivityService.updateDynamicActivity(
223
+ byName: activityId,
224
+ request: VoltraDynamicLiveActivityUpdateRequest(
225
+ props: props,
226
+ staleDate: staleDate,
227
+ relevanceScore: options?.relevanceScore?.doubleValue ?? 0.0
228
+ )
229
+ )
230
+ } catch {
231
+ throw mapError(error)
232
+ }
233
+ }
234
+
185
235
  func endLiveActivity(activityId: String, options: EndVoltraOptions?) async throws {
186
236
  guard #available(iOS 16.4, *) else { throw VoltraErrors.unsupportedOS }
187
237
 
@@ -202,12 +252,17 @@ public class VoltraModuleImpl {
202
252
 
203
253
  func getLatestVoltraActivityId() -> String? {
204
254
  guard #available(iOS 16.4, *) else { return nil }
205
- return liveActivityService.getLatestActivity()?.id
255
+ return liveActivityService.latestActivityId()
206
256
  }
207
257
 
208
258
  func listVoltraActivityIds() -> [String] {
209
259
  guard #available(iOS 16.4, *) else { return [] }
210
- return liveActivityService.getAllActivities().map(\.id)
260
+ return liveActivityService.getAllActivityReferences().map(\.id)
261
+ }
262
+
263
+ func getDynamicLiveActivityDefinitionIds() -> [String] {
264
+ guard #available(iOS 16.4, *) else { return [] }
265
+ return liveActivityService.dynamicLiveActivityDefinitionIds()
211
266
  }
212
267
 
213
268
  func isLiveActivityActive(name: String) -> Bool {
@@ -245,6 +300,13 @@ public class VoltraModuleImpl {
245
300
  }
246
301
  }
247
302
 
303
+ func reloadDynamicLiveActivities(definitionIds: [String]?) async {
304
+ #if DEBUG
305
+ syncDevServerURL()
306
+ #endif
307
+ await liveActivityService.reloadDynamicActivities(definitionIds: definitionIds)
308
+ }
309
+
248
310
  // MARK: - Image Preloading
249
311
 
250
312
  func preloadImages(images: [PreloadImageOptions]) async throws -> PreloadImagesResult {
@@ -257,6 +319,29 @@ public class VoltraModuleImpl {
257
319
 
258
320
  // MARK: - Widgets
259
321
 
322
+ func updateDynamicWidget(
323
+ dynamicWidgetId: String,
324
+ dynamicWidgetPropsJson: String
325
+ ) async throws {
326
+ let dynamicWidgetUpdater = DynamicWidgetUpdater(
327
+ dynamicWidgetPropsPersistence: DynamicWidgetPropsStore(),
328
+ dynamicWidgetTimelineReload: { dynamicWidgetId in
329
+ // On a server-driven widget the reload below runs getTimeline, which would otherwise
330
+ // fetch and overwrite what was just written. ADR 0002 says the *next scheduled* fetch
331
+ // overwrites app-written props, not the reload the write itself caused.
332
+ if VoltraWidgetServer.isServerDriven(dynamicWidgetId) {
333
+ DynamicWidgetServerPropsStore().noteAppWrite(for: .of(dynamicWidgetId))
334
+ }
335
+
336
+ VoltraWidgetService.reloadTimeline(for: dynamicWidgetId)
337
+ }
338
+ )
339
+ try await dynamicWidgetUpdater.updateDynamicWidget(
340
+ dynamicWidgetID: dynamicWidgetId,
341
+ dynamicWidgetPropsJSON: dynamicWidgetPropsJson
342
+ )
343
+ }
344
+
260
345
  func updateWidget(widgetId: String, jsonString: String, options: UpdateWidgetOptions?) async throws {
261
346
  try VoltraWidgetService.setWidgetData(widgetId: widgetId, jsonString: jsonString, deepLinkUrl: options?.deepLinkUrl)
262
347
 
@@ -291,11 +376,17 @@ public class VoltraModuleImpl {
291
376
 
292
377
  func clearWidget(widgetId: String) async {
293
378
  VoltraWidgetService.removeAllData(for: widgetId)
379
+ VoltraWidgetService.clearWidgetServerState(for: widgetId)
294
380
  VoltraWidgetService.reloadTimeline(for: widgetId)
295
381
  }
296
382
 
297
383
  func clearAllWidgets() async {
298
384
  VoltraWidgetService.removeAllWidgets()
385
+
386
+ for widgetId in VoltraWidgetServer.serverDrivenWidgetIds {
387
+ VoltraWidgetService.clearWidgetServerState(for: widgetId)
388
+ }
389
+
299
390
  VoltraWidgetService.reloadAllTimelines()
300
391
  }
301
392
 
@@ -303,6 +394,19 @@ public class VoltraModuleImpl {
303
394
  try await VoltraWidgetService.getActiveWidgets()
304
395
  }
305
396
 
397
+ /// - Returns: an error message when the settings were rejected, or nil when they were applied.
398
+ func setWidgetServerUpdate(settingsJson: String, widgetId: String?) -> String? {
399
+ VoltraWidgetService.setWidgetServerUpdate(settingsJson: settingsJson, widgetId: widgetId)
400
+ }
401
+
402
+ func clearWidgetServerUpdate(widgetId: String?) -> String? {
403
+ VoltraWidgetService.clearWidgetServerUpdate(widgetId: widgetId)
404
+ }
405
+
406
+ func getWidgetServerUpdate(widgetId: String?) -> String? {
407
+ VoltraWidgetService.getWidgetServerUpdate(widgetId: widgetId)
408
+ }
409
+
306
410
  func setWidgetServerCredentials(token: String, headers: [String: String]?) {
307
411
  VoltraWidgetService.setWidgetServerCredentials(token: token, headers: headers)
308
412
  }
@@ -326,6 +430,16 @@ public class VoltraModuleImpl {
326
430
  return VoltraErrors.liveActivitiesNotEnabled
327
431
  case .notFound:
328
432
  return VoltraErrors.notFound
433
+ case .rendererMismatch:
434
+ return VoltraErrors.rendererMismatch
435
+ }
436
+ }
437
+ if let dynamicError = error as? VoltraDynamicLiveActivityError {
438
+ switch dynamicError {
439
+ case .rendererMismatch:
440
+ return VoltraErrors.rendererMismatch
441
+ case .unknownDefinition, .payloadTooLarge, .resourceUnavailable:
442
+ return VoltraErrors.unexpectedError(error)
329
443
  }
330
444
  }
331
445
  return VoltraErrors.unexpectedError(error)
@@ -1,9 +1,9 @@
1
1
  #import "VoltraViewComponentView.h"
2
2
 
3
- #if __has_include("Voltra/Voltra-Swift.h")
4
- #import "Voltra/Voltra-Swift.h"
3
+ #if __has_include("Voltra/VoltraRuntime-Swift.h")
4
+ #import "Voltra/VoltraRuntime-Swift.h"
5
5
  #else
6
- #import "Voltra-Swift.h"
6
+ #import "VoltraRuntime-Swift.h"
7
7
  #endif
8
8
 
9
9
  #import <react/renderer/components/VoltraSpec/ComponentDescriptors.h>
@@ -40,7 +40,7 @@ enum VoltraWidgetService {
40
40
  // MARK: - Reload
41
41
 
42
42
  static func reloadTimeline(for widgetId: String) {
43
- WidgetCenter.shared.reloadTimelines(ofKind: "\(VoltraStorageKeys.widgetKindPrefix)\(widgetId)")
43
+ WidgetCenter.shared.reloadTimelines(ofKind: VoltraWidgetKind.kind(for: widgetId))
44
44
  VoltraLogger.widget.info("Reloaded timeline for '\(widgetId)'")
45
45
  }
46
46
 
@@ -49,10 +49,145 @@ enum VoltraWidgetService {
49
49
  VoltraLogger.widget.info("Reloaded all timelines")
50
50
  }
51
51
 
52
- // MARK: - Server credentials
52
+ // MARK: - Server update settings
53
+
54
+ /// Applies runtime server-update settings and reloads the widgets they affect.
55
+ ///
56
+ /// Storing is only half of what an app expects from `setWidgetServerUpdate`: a new URL should be
57
+ /// fetched from now on, and `enabled: false` should take effect without waiting out the current
58
+ /// interval. A reload makes the affected timelines re-resolve their settings straight away.
59
+ ///
60
+ /// - Parameter widgetId: the widget to scope the settings to, or nil for every server-driven one.
61
+ /// - Returns: an error message, or nil when the settings were applied.
62
+ static func setWidgetServerUpdate(settingsJson: String, widgetId: String?) -> String? {
63
+ let settings: WidgetServerUpdateSettings
64
+
65
+ switch WidgetServerUpdateSettingsJson.parse(settingsJson) {
66
+ case let .invalid(reason):
67
+ return reason
68
+ case let .parsed(parsed):
69
+ settings = parsed
70
+ }
71
+
72
+ if let widgetId, let error = rejectIfNotServerDriven(widgetId) {
73
+ return error
74
+ }
75
+
76
+ if let error = WidgetServerSettingsValidator.validate(settings, isDebugBuild: VoltraWidgetServer.isDebugBuild) {
77
+ return error
78
+ }
79
+
80
+ WidgetServerSettingsStore.set(settings, scope: widgetId.map { .of($0) })
81
+ reloadServerDrivenWidgets(widgetId: widgetId)
82
+
83
+ return nil
84
+ }
85
+
86
+ /// Drops the runtime settings for one widget, or the global ones, so the widget falls back to
87
+ /// what app.json configured. Clearing the global settings is the logout gesture.
88
+ static func clearWidgetServerUpdate(widgetId: String?) -> String? {
89
+ if let widgetId, let error = rejectIfNotServerDriven(widgetId) {
90
+ return error
91
+ }
92
+
93
+ WidgetServerSettingsStore.clear(scope: widgetId.map { .of($0) })
94
+
95
+ // Clearing the global settings is logout: what the previous account's server sent has to go
96
+ // with it, or the widget keeps showing their data. A widget-scoped clear only drops that
97
+ // widget's overrides, so its props are left alone.
98
+ if widgetId == nil {
99
+ clearFetchedState()
100
+ }
101
+
102
+ reloadServerDrivenWidgets(widgetId: widgetId)
103
+
104
+ return nil
105
+ }
106
+
107
+ /// Drops what the server last sent for every server-driven widget, so a Dynamic Widget falls
108
+ /// back to `{}` with `env.serverUpdate.status` of `never` — the state it is in before its first
109
+ /// fetch.
110
+ private static func clearFetchedState() {
111
+ let statuses = DynamicWidgetServerPropsStore()
112
+ let propsStore = DynamicWidgetPropsStore()
113
+
114
+ for widgetId in VoltraWidgetServer.serverDrivenWidgetIds {
115
+ let scope = WidgetScope.of(widgetId)
116
+
117
+ try? propsStore.clearDynamicWidgetProps(for: widgetId)
118
+ statuses.clear(scope)
119
+ WidgetServerEtagStore.clear(scope)
120
+ }
121
+ }
122
+
123
+ /// The engine is chosen at generate time, so a runtime URL cannot turn a locally-driven widget
124
+ /// into a server-driven one. Saying so at call time is much easier to act on than a widget that
125
+ /// quietly never fetches.
126
+ private static func rejectIfNotServerDriven(_ widgetId: String) -> String? {
127
+ guard !VoltraWidgetServer.isServerDriven(widgetId) else { return nil }
128
+
129
+ return "Widget '\(widgetId)' is not server-driven. Add a serverUpdate entry for it in app.json "
130
+ + "and rebuild; a runtime url does not change how a widget is rendered."
131
+ }
132
+
133
+ private static func reloadServerDrivenWidgets(widgetId: String?) {
134
+ guard let widgetId else {
135
+ for id in VoltraWidgetServer.serverDrivenWidgetIds {
136
+ reloadTimeline(for: id)
137
+ }
138
+ return
139
+ }
140
+
141
+ reloadTimeline(for: widgetId)
142
+ }
143
+
144
+ /// Reads settings back rather than reasoning about what was set: with a `widgetId`, the fully
145
+ /// resolved settings that widget would fetch with right now (nil if it is not server-driven);
146
+ /// with none, the raw global layer only, no defaults applied.
147
+ static func getWidgetServerUpdate(widgetId: String?) -> String? {
148
+ if let widgetId {
149
+ let scope = WidgetScope.of(widgetId)
150
+
151
+ guard VoltraWidgetServer.resolver.isServerDriven(scope) else { return nil }
152
+
153
+ let resolved = VoltraWidgetServer.resolver.resolve(scope)
154
+ let settings = WidgetServerUpdateSettings(
155
+ url: resolved.url,
156
+ intervalMinutes: resolved.intervalMinutes,
157
+ enabled: resolved.enabled,
158
+ method: resolved.method,
159
+ query: resolved.query,
160
+ headers: resolved.headers,
161
+ body: resolved.body
162
+ )
163
+
164
+ return WidgetServerUpdateSettingsJson.stringify(settings)
165
+ }
166
+
167
+ guard let global = VoltraWidgetServer.resolver.globalSettings() else { return nil }
168
+
169
+ return WidgetServerUpdateSettingsJson.stringify(global)
170
+ }
171
+
172
+ /// Drops one widget's runtime settings and its fetch history, for `clearWidget`.
173
+ static func clearWidgetServerState(for widgetId: String) {
174
+ guard VoltraWidgetServer.isServerDriven(widgetId) else { return }
175
+
176
+ let scope = WidgetScope.of(widgetId)
177
+
178
+ WidgetServerSettingsStore.clear(scope: scope)
179
+ WidgetServerEtagStore.clear(scope)
180
+ DynamicWidgetServerPropsStore().clear(scope)
181
+ }
182
+
183
+ // MARK: - Server credentials (deprecated)
53
184
 
54
185
  /// Saves widget server credentials to the Keychain and reloads all widget timelines
55
186
  /// so extensions can use them on the next fetch.
187
+ ///
188
+ /// Deprecated in favour of `setWidgetServerUpdate` with an `Authorization` header. Kept as a
189
+ /// wrapper over the same Keychain accounts, so an app that has not migrated keeps working and
190
+ /// nothing has to be moved on device.
56
191
  static func setWidgetServerCredentials(token: String, headers: [String: String]?) {
57
192
  VoltraKeychainHelper.saveToken(token)
58
193
  if let headers = headers {
@@ -60,13 +195,20 @@ enum VoltraWidgetService {
60
195
  } else {
61
196
  VoltraKeychainHelper.deleteHeaders()
62
197
  }
198
+ // The credentials layer does not go through the settings store, but a new token is exactly the
199
+ // thing a widget stuck on a 401 is waiting for, so an in-flight fetch built with the old one
200
+ // must not commit.
201
+ WidgetServerSettingsStore.bumpRevision()
63
202
  VoltraLogger.widget.info("Server credentials saved")
64
203
  reloadAllTimelines()
65
204
  }
66
205
 
67
206
  /// Clears widget server credentials from the Keychain and reloads all widget timelines.
207
+ ///
208
+ /// Deprecated alongside `setWidgetServerCredentials`.
68
209
  static func clearWidgetServerCredentials() {
69
210
  VoltraKeychainHelper.clearAll()
211
+ WidgetServerSettingsStore.bumpRevision()
70
212
  VoltraLogger.widget.info("Server credentials cleared")
71
213
  reloadAllTimelines()
72
214
  }
@@ -80,10 +222,7 @@ enum VoltraWidgetService {
80
222
  switch result {
81
223
  case let .success(widgetInfos):
82
224
  let mapped = widgetInfos.map { widget -> [String: String] in
83
- let prefix = VoltraStorageKeys.widgetKindPrefix
84
- let name = widget.kind.hasPrefix(prefix)
85
- ? String(widget.kind.dropFirst(prefix.count))
86
- : widget.kind
225
+ let name = VoltraWidgetKind.widgetId(for: widget.kind) ?? widget.kind
87
226
 
88
227
  return [
89
228
  "name": name,
@@ -100,17 +239,13 @@ enum VoltraWidgetService {
100
239
  }
101
240
 
102
241
  /// Returns the set of widget IDs currently installed on the device.
103
- /// Only IDs whose kind carries the Voltra prefix are included.
242
+ /// Only Voltra widgets (default `Voltra_Widget_` kind or a pinned `kind`) are included.
104
243
  static func getInstalledWidgetIds() async throws -> Set<String> {
105
244
  try await withCheckedThrowingContinuation { continuation in
106
245
  WidgetCenter.shared.getCurrentConfigurations { result in
107
246
  switch result {
108
247
  case let .success(configs):
109
- let ids = Set(configs.compactMap { config -> String? in
110
- let prefix = VoltraStorageKeys.widgetKindPrefix
111
- guard config.kind.hasPrefix(prefix) else { return nil }
112
- return String(config.kind.dropFirst(prefix.count))
113
- })
248
+ let ids = Set(configs.compactMap { VoltraWidgetKind.widgetId(for: $0.kind) })
114
249
  continuation.resume(returning: ids)
115
250
  case let .failure(error):
116
251
  continuation.resume(throwing: error)
@@ -0,0 +1,97 @@
1
+ import ActivityKit
2
+ import Foundation
3
+
4
+ /// App-process orchestration for the dynamic renderer. The legacy service keeps
5
+ /// only thin cross-engine coordination points and delegates feature-specific
6
+ /// catalog, bundle, payload, and reload behavior here.
7
+ final class VoltraDynamicLiveActivityService {
8
+ private let chronology: VoltraLiveActivityChronology
9
+
10
+ init(chronology: VoltraLiveActivityChronology = .shared) {
11
+ self.chronology = chronology
12
+ }
13
+
14
+ func isActive(name: String) -> Bool {
15
+ activityReferences().contains { $0.name == name }
16
+ }
17
+
18
+ func activityReferences() -> [VoltraDynamicLiveActivityReference] {
19
+ VoltraDynamicLiveActivityRegistry.shared.activities()
20
+ }
21
+
22
+ func definitionIds() -> [String] {
23
+ VoltraDynamicLiveActivityRegistry.shared.definitionIds()
24
+ }
25
+
26
+ func create(_ request: VoltraDynamicLiveActivityCreateRequest) async throws -> String {
27
+ guard VoltraDynamicLiveActivityRegistry.shared.contains(request.definitionId) else {
28
+ throw VoltraDynamicLiveActivityError.unknownDefinition(request.definitionId)
29
+ }
30
+ do {
31
+ let source = try await VoltraDynamicLiveActivityBundleSource.loadForApp(definitionId: request.definitionId)
32
+ guard VoltraJSRenderer.evaluateLiveActivityBundle(source: source, definitionId: request.definitionId) else {
33
+ throw VoltraDynamicLiveActivityError.resourceUnavailable(
34
+ NSError(
35
+ domain: "VoltraDynamicLiveActivity",
36
+ code: -1,
37
+ userInfo: [NSLocalizedDescriptionKey: "Dynamic Live Activity bundle could not be evaluated."]
38
+ )
39
+ )
40
+ }
41
+ } catch let error as VoltraDynamicLiveActivityError {
42
+ throw error
43
+ } catch {
44
+ throw VoltraDynamicLiveActivityError.resourceUnavailable(error)
45
+ }
46
+ try VoltraDynamicLiveActivityPayloadValidator.validate(
47
+ name: request.name,
48
+ deepLinkUrl: request.deepLinkUrl,
49
+ props: request.props
50
+ )
51
+ guard let activity = try await VoltraDynamicLiveActivityRegistry.shared.create(request) else {
52
+ throw VoltraDynamicLiveActivityError.unknownDefinition(request.definitionId)
53
+ }
54
+ chronology.record(activity.id)
55
+ return request.name
56
+ }
57
+
58
+ func update(byName name: String, request: VoltraDynamicLiveActivityUpdateRequest) async throws -> Bool {
59
+ try VoltraDynamicLiveActivityPayloadValidator.validateContentState(request.props)
60
+ return await VoltraDynamicLiveActivityRegistry.shared.update(byName: name, request: request)
61
+ }
62
+
63
+ func end(byName name: String, dismissalPolicy: ActivityUIDismissalPolicy) async -> Bool {
64
+ await VoltraDynamicLiveActivityRegistry.shared.end(byName: name, dismissalPolicy: dismissalPolicy)
65
+ }
66
+
67
+ func endAll(dismissalPolicy: ActivityUIDismissalPolicy) async {
68
+ await VoltraDynamicLiveActivityRegistry.shared.endAll(dismissalPolicy: dismissalPolicy)
69
+ }
70
+
71
+ func reload(definitionIds: [String]?) async {
72
+ #if DEBUG
73
+ let requested = definitionIds.map(Set.init)
74
+ let ids = requested ?? Set(self.definitionIds())
75
+ var refreshed = Set<String>()
76
+ for definitionId in ids.sorted() {
77
+ guard VoltraDynamicLiveActivityRegistry.shared.contains(definitionId) else { continue }
78
+ do {
79
+ let source = try await VoltraDynamicLiveActivityBundleSource.loadForApp(definitionId: definitionId)
80
+ guard VoltraJSRenderer.evaluateLiveActivityBundle(source: source, definitionId: definitionId) else {
81
+ throw VoltraDynamicLiveActivityError.resourceUnavailable(
82
+ NSError(
83
+ domain: "VoltraDynamicLiveActivity",
84
+ code: -2,
85
+ userInfo: [NSLocalizedDescriptionKey: "Dynamic Live Activity bundle could not be evaluated."]
86
+ )
87
+ )
88
+ }
89
+ refreshed.insert(definitionId)
90
+ } catch {
91
+ VoltraLogger.activity.error("Failed to refresh Dynamic Live Activity definition '\(definitionId)': \(error)")
92
+ }
93
+ }
94
+ await VoltraDynamicLiveActivityRegistry.shared.reload(definitionIds: refreshed)
95
+ #endif
96
+ }
97
+ }
@@ -0,0 +1,68 @@
1
+ import Foundation
2
+
3
+ /// Persists creation-time observations made by this app process. ActivityKit
4
+ /// does not expose a cross-attributes-type creation timestamp, so an instance
5
+ /// first encountered after a process restart must never be assigned an order
6
+ /// merely because a catalog happened to enumerate it first.
7
+ final class VoltraLiveActivityChronology: @unchecked Sendable {
8
+ static let shared = VoltraLiveActivityChronology()
9
+
10
+ private static let storageKey = "Voltra_LiveActivityChronology"
11
+
12
+ private let lock = NSLock()
13
+ private let defaults: UserDefaults
14
+ private var timestamps: [String: TimeInterval]
15
+
16
+ init(defaults: UserDefaults = .standard) {
17
+ self.defaults = defaults
18
+ if let stored = defaults.dictionary(forKey: Self.storageKey) as? [String: TimeInterval] {
19
+ timestamps = stored
20
+ } else {
21
+ // Migrate the previous observation-order ledger without reordering it.
22
+ let legacy = defaults.stringArray(forKey: Self.storageKey) ?? []
23
+ timestamps = Dictionary(uniqueKeysWithValues: legacy.enumerated().map { index, id in (id, Double(index)) })
24
+ }
25
+ }
26
+
27
+ func record(_ activityId: String) {
28
+ lock.lock()
29
+ defer { lock.unlock() }
30
+ guard timestamps[activityId] == nil else { return }
31
+ timestamps[activityId] = Date().timeIntervalSince1970
32
+ persist()
33
+ }
34
+
35
+ func order(
36
+ _ references: [VoltraDynamicLiveActivityReference]
37
+ ) -> [VoltraDynamicLiveActivityReference] {
38
+ lock.lock()
39
+ defer { lock.unlock() }
40
+
41
+ let activeIds = Set(references.map(\.id))
42
+ let nextTimestamps = timestamps.filter { activeIds.contains($0.key) }
43
+ if nextTimestamps != timestamps {
44
+ timestamps = nextTimestamps
45
+ persist()
46
+ }
47
+ return references.sorted { lhs, rhs in
48
+ switch (timestamps[lhs.id], timestamps[rhs.id]) {
49
+ case let (left?, right?): return left < right
50
+ case (_?, nil): return true
51
+ case (nil, _?): return false
52
+ case (nil, nil): return lhs.id < rhs.id
53
+ }
54
+ }
55
+ }
56
+
57
+ func latest(_ references: [VoltraDynamicLiveActivityReference]) -> VoltraDynamicLiveActivityReference? {
58
+ lock.lock()
59
+ defer { lock.unlock() }
60
+ return references.compactMap { reference in
61
+ timestamps[reference.id].map { (reference, $0) }
62
+ }.max { $0.1 < $1.1 }?.0
63
+ }
64
+
65
+ private func persist() {
66
+ defaults.set(timestamps, forKey: Self.storageKey)
67
+ }
68
+ }
@@ -0,0 +1,51 @@
1
+ import Foundation
2
+
3
+ enum DynamicWidgetPropsCodec {
4
+ static let emptyDynamicWidgetPropsJSON = "{}"
5
+
6
+ private static let currentStorageVersion = 1
7
+
8
+ static func encodeStorageEntry(dynamicWidgetPropsJSON: String) throws -> String {
9
+ let dynamicWidgetProps: JSONValue
10
+ do {
11
+ dynamicWidgetProps = try JSONValue.parse(from: dynamicWidgetPropsJSON)
12
+ } catch {
13
+ throw DynamicWidgetPropsStoreError.invalidJSON
14
+ }
15
+
16
+ guard case .object = dynamicWidgetProps else {
17
+ throw DynamicWidgetPropsStoreError.topLevelValueMustBeObject
18
+ }
19
+
20
+ let entry = DynamicWidgetPropsStorageEntry(
21
+ dynamicWidgetPropsStorageVersion: currentStorageVersion,
22
+ dynamicWidgetProps: dynamicWidgetProps
23
+ )
24
+ let data = try JSONEncoder().encode(entry)
25
+ guard let json = String(data: data, encoding: .utf8) else {
26
+ throw DynamicWidgetPropsStoreError.invalidJSON
27
+ }
28
+ return json
29
+ }
30
+
31
+ static func decodeDynamicWidgetProps(storageEntryJSON: String?) -> String {
32
+ guard
33
+ let storageEntryJSON,
34
+ let data = storageEntryJSON.data(using: .utf8),
35
+ let entry = try? JSONDecoder().decode(DynamicWidgetPropsStorageEntry.self, from: data),
36
+ entry.dynamicWidgetPropsStorageVersion == currentStorageVersion,
37
+ case .object = entry.dynamicWidgetProps,
38
+ let dynamicWidgetPropsData = try? JSONEncoder().encode(entry.dynamicWidgetProps),
39
+ let dynamicWidgetPropsJSON = String(data: dynamicWidgetPropsData, encoding: .utf8)
40
+ else {
41
+ return emptyDynamicWidgetPropsJSON
42
+ }
43
+
44
+ return dynamicWidgetPropsJSON
45
+ }
46
+ }
47
+
48
+ private struct DynamicWidgetPropsStorageEntry: Codable {
49
+ let dynamicWidgetPropsStorageVersion: Int
50
+ let dynamicWidgetProps: JSONValue
51
+ }
@@ -0,0 +1,53 @@
1
+ import Foundation
2
+
3
+ enum DynamicWidgetPropsStoreError: Error, Equatable, LocalizedError {
4
+ case appGroupNotConfigured
5
+ case userDefaultsUnavailable
6
+ case persistenceFailed
7
+ case invalidJSON
8
+ case topLevelValueMustBeObject
9
+
10
+ var errorDescription: String? {
11
+ switch self {
12
+ case .appGroupNotConfigured:
13
+ return "App Group not configured. Set 'groupIdentifier' in the Voltra config plugin to use Dynamic Widgets."
14
+ case .userDefaultsUnavailable:
15
+ return "Unable to access UserDefaults for the Dynamic Widget App Group."
16
+ case .persistenceFailed:
17
+ return "Failed to persist Dynamic Widget props to the App Group."
18
+ case .invalidJSON:
19
+ return "Dynamic Widget props must be valid JSON."
20
+ case .topLevelValueMustBeObject:
21
+ return "Dynamic Widget props must be a top-level JSON object."
22
+ }
23
+ }
24
+ }
25
+
26
+ protocol DynamicWidgetPropsStorage {
27
+ func string(forKey key: String) throws -> String?
28
+ func set(_ value: String, forKey key: String) throws
29
+ func removeObject(forKey key: String) throws
30
+ }
31
+
32
+ struct DynamicWidgetPropsUserDefaultsStorage: DynamicWidgetPropsStorage {
33
+ private let userDefaults: UserDefaults
34
+
35
+ init(userDefaults: UserDefaults) {
36
+ self.userDefaults = userDefaults
37
+ }
38
+
39
+ func string(forKey key: String) throws -> String? {
40
+ userDefaults.string(forKey: key)
41
+ }
42
+
43
+ func set(_ value: String, forKey key: String) throws {
44
+ userDefaults.set(value, forKey: key)
45
+ guard userDefaults.synchronize() else {
46
+ throw DynamicWidgetPropsStoreError.persistenceFailed
47
+ }
48
+ }
49
+
50
+ func removeObject(forKey key: String) throws {
51
+ userDefaults.removeObject(forKey: key)
52
+ }
53
+ }