one 2.0.0-beta.148.1 → 2.0.0-beta.158.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/One.podspec +4 -4
  2. package/android/CMakeLists.txt +7 -2
  3. package/android/build.gradle +2 -0
  4. package/android/src/main/cpp/OneStorageDirectory.cpp +21 -0
  5. package/android/src/main/java/com/margelo/nitro/one/HybridOneAdaptive.kt +13 -6
  6. package/android/src/main/java/com/margelo/nitro/one/OneStorageDirectory.kt +19 -0
  7. package/android/src/main/java/dev/onejs/one/OneKotlinHostEvents.kt +41 -0
  8. package/android/src/main/java/dev/onejs/one/OneKotlinHostManager.kt +87 -0
  9. package/android/src/main/java/dev/onejs/one/OneKotlinHostView.kt +231 -0
  10. package/android/src/main/java/dev/onejs/one/OneNativeSourceViewDispatch.kt +13 -0
  11. package/android/src/main/java/dev/onejs/one/OneNativeSourceViewRegistry.kt +21 -0
  12. package/android/src/main/java/dev/onejs/one/OnePackage.kt +3 -0
  13. package/android/src/main/java/dev/onejs/one/sharetarget/OneShareDraftStore.kt +122 -0
  14. package/android/src/main/java/dev/onejs/one/sharetarget/OneShareIntake.kt +344 -0
  15. package/android/src/main/java/dev/onejs/one/sharetarget/OneShareModels.kt +36 -0
  16. package/android/src/main/java/dev/onejs/one/sharetarget/OneShareSessionModel.kt +207 -0
  17. package/android/src/main/java/dev/onejs/one/sharetarget/OneShareTargetActivity.kt +161 -0
  18. package/android/src/main/java/dev/onejs/one/sharetarget/OneShareTargetAdapter.kt +13 -0
  19. package/android/src/main/java/dev/onejs/onenative/OneNativeMenuTriggerManager.kt +102 -0
  20. package/cpp/HybridOneCrypto.cpp +37 -0
  21. package/cpp/HybridOneCrypto.hpp +19 -0
  22. package/cpp/HybridOneStorage.cpp +331 -0
  23. package/cpp/HybridOneStorage.hpp +23 -0
  24. package/cpp/OneStorageDirectory.hpp +11 -0
  25. package/dist/cjs/cli/prebuild.cjs +2 -2
  26. package/dist/cjs/cli/prebuild.native.cjs +4 -3
  27. package/dist/cjs/cli/prebuild.native.js +4 -3
  28. package/dist/cjs/cli/prebuild.native.js.map +1 -1
  29. package/dist/cjs/index.cjs +2 -0
  30. package/dist/cjs/index.native.cjs +2 -0
  31. package/dist/cjs/index.native.js +2 -0
  32. package/dist/cjs/index.native.js.map +1 -1
  33. package/dist/cjs/one.cjs +2 -1
  34. package/dist/cjs/one.native.cjs +2 -1
  35. package/dist/cjs/one.native.js +2 -1
  36. package/dist/cjs/one.native.js.map +1 -1
  37. package/dist/cjs/platform/AndroidMenu.cjs +14 -3
  38. package/dist/cjs/platform/AndroidMenu.native.cjs +14 -3
  39. package/dist/cjs/platform/AndroidMenu.native.js +14 -3
  40. package/dist/cjs/platform/AndroidMenu.native.js.map +1 -1
  41. package/dist/cjs/platform/Sheet.native.cjs +28 -2
  42. package/dist/cjs/platform/Sheet.native.js +28 -2
  43. package/dist/cjs/platform/Sheet.native.js.map +1 -1
  44. package/dist/cjs/platform/app-intents/index.cjs +29 -0
  45. package/dist/cjs/platform/app-intents/index.native.cjs +93 -0
  46. package/dist/cjs/platform/app-intents/index.native.js +95 -0
  47. package/dist/cjs/platform/app-intents/index.native.js.map +1 -0
  48. package/dist/cjs/platform/app-intents/validate.cjs +28 -0
  49. package/dist/cjs/platform/app-intents/validate.native.cjs +30 -0
  50. package/dist/cjs/platform/app-intents/validate.native.js +32 -0
  51. package/dist/cjs/platform/app-intents/validate.native.js.map +1 -0
  52. package/dist/cjs/platform/crypto/index.cjs +10 -6
  53. package/dist/cjs/platform/crypto/index.native.cjs +9 -13
  54. package/dist/cjs/platform/crypto/index.native.js +9 -13
  55. package/dist/cjs/platform/crypto/index.native.js.map +1 -1
  56. package/dist/cjs/platform/crypto/random.cjs +5 -8
  57. package/dist/cjs/platform/crypto/random.native.cjs +5 -8
  58. package/dist/cjs/platform/crypto/random.native.js +5 -8
  59. package/dist/cjs/platform/crypto/random.native.js.map +1 -1
  60. package/dist/cjs/platform/index.cjs +4 -2
  61. package/dist/cjs/platform/index.native.cjs +4 -2
  62. package/dist/cjs/platform/index.native.js +4 -2
  63. package/dist/cjs/platform/index.native.js.map +1 -1
  64. package/dist/cjs/platform/native-source/view.cjs +26 -0
  65. package/dist/cjs/platform/native-source/view.native.cjs +63 -0
  66. package/dist/cjs/platform/native-source/view.native.js +65 -0
  67. package/dist/cjs/platform/native-source/view.native.js.map +1 -0
  68. package/dist/cjs/platform/{specs/OnePreferences.nitro.cjs → native-source/viewTypes.cjs} +2 -2
  69. package/dist/cjs/platform/{specs/OnePreferences.nitro.native.cjs → native-source/viewTypes.native.cjs} +2 -2
  70. package/dist/cjs/platform/{specs/OnePreferences.nitro.native.js → native-source/viewTypes.native.js} +3 -3
  71. package/dist/cjs/platform/native-source/viewTypes.native.js.map +1 -0
  72. package/dist/cjs/platform/specs/OneAppIntents.nitro.cjs +16 -0
  73. package/dist/cjs/platform/specs/OneAppIntents.nitro.native.cjs +18 -0
  74. package/dist/cjs/platform/specs/OneAppIntents.nitro.native.js +20 -0
  75. package/dist/cjs/platform/specs/OneAppIntents.nitro.native.js.map +1 -0
  76. package/dist/cjs/platform/specs/OneKotlinHostNativeComponent.cjs +52 -0
  77. package/dist/cjs/platform/specs/OneKotlinHostNativeComponent.native.cjs +52 -0
  78. package/dist/cjs/platform/specs/OneKotlinHostNativeComponent.native.js +53 -0
  79. package/dist/cjs/platform/specs/OneKotlinHostNativeComponent.native.js.map +7 -0
  80. package/dist/cjs/platform/specs/OneNativeSheetNativeComponent.cjs +5 -0
  81. package/dist/cjs/platform/specs/OneNativeSheetNativeComponent.native.cjs +5 -0
  82. package/dist/cjs/platform/specs/OneNativeSheetNativeComponent.native.js +5 -0
  83. package/dist/cjs/platform/specs/OneNativeSheetNativeComponent.native.js.map +2 -2
  84. package/dist/cjs/platform/specs/OneStorage.nitro.cjs +16 -0
  85. package/dist/cjs/platform/specs/OneStorage.nitro.native.cjs +18 -0
  86. package/dist/cjs/platform/specs/OneStorage.nitro.native.js +20 -0
  87. package/dist/cjs/platform/specs/OneStorage.nitro.native.js.map +1 -0
  88. package/dist/cjs/platform/storage/index.cjs +52 -0
  89. package/dist/cjs/platform/storage/index.native.cjs +70 -0
  90. package/dist/cjs/platform/storage/index.native.js +72 -0
  91. package/dist/cjs/platform/storage/index.native.js.map +1 -0
  92. package/dist/cjs/platform/{preferences → storage}/validate.cjs +6 -10
  93. package/dist/cjs/platform/{preferences → storage}/validate.native.cjs +6 -10
  94. package/dist/cjs/platform/{preferences → storage}/validate.native.js +6 -10
  95. package/dist/cjs/platform/storage/validate.native.js.map +1 -0
  96. package/dist/cjs/tamagui.cjs +24 -0
  97. package/dist/cjs/tamagui.native.cjs +90 -0
  98. package/dist/cjs/tamagui.native.js +92 -0
  99. package/dist/cjs/tamagui.native.js.map +1 -0
  100. package/dist/cjs/useHeaderHeight.cjs +46 -0
  101. package/dist/cjs/useHeaderHeight.native.cjs +49 -0
  102. package/dist/cjs/useHeaderHeight.native.js +51 -0
  103. package/dist/cjs/useHeaderHeight.native.js.map +1 -0
  104. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +4 -7
  105. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.cjs +3 -6
  106. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +3 -6
  107. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  108. package/dist/cjs/vite/plugins/kotlinSourcePlugin.cjs +12 -4
  109. package/dist/cjs/vite/plugins/kotlinSourcePlugin.native.cjs +14 -4
  110. package/dist/cjs/vite/plugins/kotlinSourcePlugin.native.js +14 -4
  111. package/dist/cjs/vite/plugins/kotlinSourcePlugin.native.js.map +1 -1
  112. package/dist/esm/cli/prebuild.mjs +2 -2
  113. package/dist/esm/cli/prebuild.mjs.map +1 -1
  114. package/dist/esm/cli/prebuild.native.js +4 -3
  115. package/dist/esm/cli/prebuild.native.js.map +1 -1
  116. package/dist/esm/index.js +2 -1
  117. package/dist/esm/index.mjs +2 -1
  118. package/dist/esm/index.native.js +2 -1
  119. package/dist/esm/one.mjs +3 -2
  120. package/dist/esm/one.mjs.map +1 -1
  121. package/dist/esm/one.native.js +3 -2
  122. package/dist/esm/one.native.js.map +1 -1
  123. package/dist/esm/platform/AndroidMenu.mjs +15 -4
  124. package/dist/esm/platform/AndroidMenu.mjs.map +1 -1
  125. package/dist/esm/platform/AndroidMenu.native.js +15 -4
  126. package/dist/esm/platform/AndroidMenu.native.js.map +1 -1
  127. package/dist/esm/platform/Sheet.native.js +28 -2
  128. package/dist/esm/platform/Sheet.native.js.map +1 -1
  129. package/dist/esm/platform/app-intents/index.mjs +10 -0
  130. package/dist/esm/platform/app-intents/index.mjs.map +1 -0
  131. package/dist/esm/platform/app-intents/index.native.js +72 -0
  132. package/dist/esm/platform/app-intents/index.native.js.map +1 -0
  133. package/dist/esm/platform/app-intents/validate.mjs +8 -0
  134. package/dist/esm/platform/app-intents/validate.mjs.map +1 -0
  135. package/dist/esm/platform/app-intents/validate.native.js +8 -0
  136. package/dist/esm/platform/app-intents/validate.native.js.map +1 -0
  137. package/dist/esm/platform/crypto/index.mjs +11 -7
  138. package/dist/esm/platform/crypto/index.mjs.map +1 -1
  139. package/dist/esm/platform/crypto/index.native.js +10 -14
  140. package/dist/esm/platform/crypto/index.native.js.map +1 -1
  141. package/dist/esm/platform/crypto/random.mjs +6 -8
  142. package/dist/esm/platform/crypto/random.mjs.map +1 -1
  143. package/dist/esm/platform/crypto/random.native.js +6 -8
  144. package/dist/esm/platform/crypto/random.native.js.map +1 -1
  145. package/dist/esm/platform/index.mjs +3 -2
  146. package/dist/esm/platform/index.mjs.map +1 -1
  147. package/dist/esm/platform/index.native.js +3 -2
  148. package/dist/esm/platform/index.native.js.map +1 -1
  149. package/dist/esm/platform/native-source/view.mjs +6 -0
  150. package/dist/esm/platform/native-source/view.mjs.map +1 -0
  151. package/dist/esm/platform/native-source/view.native.js +36 -0
  152. package/dist/esm/platform/native-source/view.native.js.map +1 -0
  153. package/dist/esm/platform/specs/OneAppIntents.nitro.mjs +0 -0
  154. package/dist/esm/platform/specs/OneAppIntents.nitro.native.js +0 -0
  155. package/dist/esm/platform/specs/OneKotlinHostNativeComponent.mjs +34 -0
  156. package/dist/esm/platform/specs/OneKotlinHostNativeComponent.mjs.map +7 -0
  157. package/dist/esm/platform/specs/OneKotlinHostNativeComponent.native.js +34 -0
  158. package/dist/esm/platform/specs/OneKotlinHostNativeComponent.native.js.map +7 -0
  159. package/dist/esm/platform/specs/OneNativeSheetNativeComponent.mjs +5 -0
  160. package/dist/esm/platform/specs/OneNativeSheetNativeComponent.mjs.map +2 -2
  161. package/dist/esm/platform/specs/OneNativeSheetNativeComponent.native.js +5 -0
  162. package/dist/esm/platform/specs/OneNativeSheetNativeComponent.native.js.map +2 -2
  163. package/dist/esm/platform/specs/OneStorage.nitro.mjs +0 -0
  164. package/dist/esm/platform/specs/OneStorage.nitro.native.js +0 -0
  165. package/dist/esm/platform/storage/index.mjs +33 -0
  166. package/dist/esm/platform/storage/index.mjs.map +1 -0
  167. package/dist/esm/platform/storage/index.native.js +49 -0
  168. package/dist/esm/platform/storage/index.native.js.map +1 -0
  169. package/dist/esm/platform/storage/validate.mjs +9 -0
  170. package/dist/esm/platform/storage/validate.mjs.map +1 -0
  171. package/dist/esm/platform/storage/validate.native.js +9 -0
  172. package/dist/esm/platform/storage/validate.native.js.map +1 -0
  173. package/dist/esm/tamagui.mjs +4 -0
  174. package/dist/esm/tamagui.mjs.map +1 -0
  175. package/dist/esm/tamagui.native.js +69 -0
  176. package/dist/esm/tamagui.native.js.map +1 -0
  177. package/dist/esm/useHeaderHeight.mjs +27 -0
  178. package/dist/esm/useHeaderHeight.mjs.map +1 -0
  179. package/dist/esm/useHeaderHeight.native.js +28 -0
  180. package/dist/esm/useHeaderHeight.native.js.map +1 -0
  181. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +4 -7
  182. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
  183. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +3 -6
  184. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  185. package/dist/esm/vite/plugins/kotlinSourcePlugin.mjs +12 -4
  186. package/dist/esm/vite/plugins/kotlinSourcePlugin.mjs.map +1 -1
  187. package/dist/esm/vite/plugins/kotlinSourcePlugin.native.js +14 -4
  188. package/dist/esm/vite/plugins/kotlinSourcePlugin.native.js.map +1 -1
  189. package/ios/Generated/OneNativeSheetContent.swift +2 -0
  190. package/ios/Nitro/HybridOneAppIntents.swift +148 -0
  191. package/ios/Nitro/OneAppIntentsBridge.h +14 -0
  192. package/ios/Nitro/OneAppIntentsBridge.m +17 -0
  193. package/ios/Nitro/OneBackgroundTasksBridge.m +2 -4
  194. package/ios/Nitro/OneHeadlessHost.h +8 -0
  195. package/ios/Nitro/OneHeadlessHost.m +10 -0
  196. package/ios/OneNativeComposition.swift +18 -14
  197. package/ios/OneNativeSheetComponentView.mm +7 -1
  198. package/ios/OneNativeSheetView.swift +28 -3
  199. package/ios/OneNativeSlot.swift +9 -0
  200. package/ios/OneStorageDirectory.mm +28 -0
  201. package/ios/OneSwiftHostView.swift +6 -1
  202. package/ios/ShareTarget/OneShareComposeViewController.swift +337 -0
  203. package/ios/ShareTarget/OneShareTargetAdapter.swift +128 -0
  204. package/ios/ShareTarget/OneShareTargetConfiguration.swift +38 -0
  205. package/ios/ShareTarget/OneShareTargetDraftStore.swift +118 -0
  206. package/ios/ShareTarget/OneShareTargetIntake.swift +344 -0
  207. package/ios/ShareTarget/OneShareTargetSubmissionCoordinator.swift +249 -0
  208. package/ios/ShareTarget/OneShareTargetViewController.swift +30 -0
  209. package/ios/Tests/ShareTarget/Package.swift +34 -0
  210. package/ios/Tests/ShareTarget/Tests/OneShareTargetHarnessTests/OneShareTargetDraftStoreTests.swift +75 -0
  211. package/ios/Tests/ShareTarget/Tests/OneShareTargetHarnessTests/OneShareTargetIntakeTests.swift +266 -0
  212. package/ios/Tests/ShareTarget/Tests/OneShareTargetHarnessTests/OneShareTargetSubmissionCoordinatorTests.swift +234 -0
  213. package/ios/Tests/ShareTarget/Tests/OneShareTargetHarnessTests/RuntimeRegressionTests.swift +285 -0
  214. package/ios/Tests/ShareTarget/UIHarness/HostApp/AppDelegate.swift +92 -0
  215. package/ios/Tests/ShareTarget/UIHarness/HostApp/HostApp.entitlements +10 -0
  216. package/ios/Tests/ShareTarget/UIHarness/HostApp/Info.plist +43 -0
  217. package/ios/Tests/ShareTarget/UIHarness/ShareExtension/GeneratedHarnessShareViewController.swift +18 -0
  218. package/ios/Tests/ShareTarget/UIHarness/ShareExtension/HarnessShareTargetAdapterExtensionOnly.swift +39 -0
  219. package/ios/Tests/ShareTarget/UIHarness/ShareExtension/Info.plist +45 -0
  220. package/ios/Tests/ShareTarget/UIHarness/ShareExtension/ShareExtension.entitlements +10 -0
  221. package/ios/Tests/ShareTarget/UIHarness/Shared/HarnessShareTargetAdapter.swift +82 -0
  222. package/ios/Tests/ShareTarget/UIHarness/build.sh +60 -0
  223. package/ios/Tests/ShareTarget/UIHarness/evidence/01-host-app.png +0 -0
  224. package/ios/Tests/ShareTarget/UIHarness/evidence/02-system-share-sheet-with-extension.jpg +0 -0
  225. package/ios/Tests/ShareTarget/UIHarness/evidence/after-tests.txt +99 -0
  226. package/ios/Tests/ShareTarget/UIHarness/evidence/baseline-tests.txt +109 -0
  227. package/ios/Tests/ShareTarget/UIHarness/evidence/before-caption-draft.json +12 -0
  228. package/ios/Tests/ShareTarget/UIHarness/evidence/before-caption.webp +0 -0
  229. package/ios/Tests/ShareTarget/UIHarness/evidence/before-send-auto-dismiss.webp +0 -0
  230. package/ios/Tests/ShareTarget/UIHarness/evidence/configuration-probe.txt +98 -0
  231. package/ios/Tests/ShareTarget/UIHarness/evidence/final-cancelled-disk.json +50 -0
  232. package/ios/Tests/ShareTarget/UIHarness/evidence/final-cancelled.ax.json +822 -0
  233. package/ios/Tests/ShareTarget/UIHarness/evidence/final-cancelled.webp +0 -0
  234. package/ios/Tests/ShareTarget/UIHarness/evidence/final-delivered-disk.json +47 -0
  235. package/ios/Tests/ShareTarget/UIHarness/evidence/final-delivered.ax.json +375 -0
  236. package/ios/Tests/ShareTarget/UIHarness/evidence/final-delivered.webp +0 -0
  237. package/ios/Tests/ShareTarget/UIHarness/evidence/final-destination-error.ax.json +1068 -0
  238. package/ios/Tests/ShareTarget/UIHarness/evidence/final-destination-error.webp +0 -0
  239. package/ios/Tests/ShareTarget/UIHarness/evidence/final-destination-failed-disk.json +61 -0
  240. package/ios/Tests/ShareTarget/UIHarness/evidence/final-destination-recovered-disk.json +62 -0
  241. package/ios/Tests/ShareTarget/UIHarness/evidence/final-destination-recovered.ax.json +1068 -0
  242. package/ios/Tests/ShareTarget/UIHarness/evidence/final-destination-recovered.webp +0 -0
  243. package/ios/Tests/ShareTarget/UIHarness/evidence/final-edited.ax.json +1338 -0
  244. package/ios/Tests/ShareTarget/UIHarness/evidence/final-edited.webp +0 -0
  245. package/ios/Tests/ShareTarget/UIHarness/evidence/final-failed-mutations-blocked-disk.json +66 -0
  246. package/ios/Tests/ShareTarget/UIHarness/evidence/final-failed-send-disk.json +66 -0
  247. package/ios/Tests/ShareTarget/UIHarness/evidence/final-held-destinations-cancelled-disk.json +50 -0
  248. package/ios/Tests/ShareTarget/UIHarness/evidence/final-held-destinations-cancelled.ax.json +822 -0
  249. package/ios/Tests/ShareTarget/UIHarness/evidence/final-held-destinations-cancelled.webp +0 -0
  250. package/ios/Tests/ShareTarget/UIHarness/evidence/final-held-destinations-disk.json +61 -0
  251. package/ios/Tests/ShareTarget/UIHarness/evidence/final-held-destinations.ax.json +1068 -0
  252. package/ios/Tests/ShareTarget/UIHarness/evidence/final-held-destinations.webp +0 -0
  253. package/ios/Tests/ShareTarget/UIHarness/evidence/final-initial.ax.json +1092 -0
  254. package/ios/Tests/ShareTarget/UIHarness/evidence/final-initial.webp +0 -0
  255. package/ios/Tests/ShareTarget/UIHarness/evidence/final-load-edit-preserved-disk.json +62 -0
  256. package/ios/Tests/ShareTarget/UIHarness/evidence/final-load-edit-preserved.ax.json +1044 -0
  257. package/ios/Tests/ShareTarget/UIHarness/evidence/final-load-edit-preserved.webp +0 -0
  258. package/ios/Tests/ShareTarget/UIHarness/evidence/final-loading-disk.json +61 -0
  259. package/ios/Tests/ShareTarget/UIHarness/evidence/final-loading.ax.json +2256 -0
  260. package/ios/Tests/ShareTarget/UIHarness/evidence/final-loading.webp +0 -0
  261. package/ios/Tests/ShareTarget/UIHarness/evidence/final-picker.ax.json +1166 -0
  262. package/ios/Tests/ShareTarget/UIHarness/evidence/final-picker.webp +0 -0
  263. package/ios/Tests/ShareTarget/UIHarness/evidence/final-retried-disk.json +54 -0
  264. package/ios/Tests/ShareTarget/UIHarness/evidence/final-retry-ready.ax.json +1092 -0
  265. package/ios/Tests/ShareTarget/UIHarness/evidence/final-retry-ready.webp +0 -0
  266. package/ios/Tests/ShareTarget/UIHarness/evidence/final-retry-verification.json +30 -0
  267. package/ios/Tests/ShareTarget/UIHarness/evidence/final-send-error.ax.json +1068 -0
  268. package/ios/Tests/ShareTarget/UIHarness/evidence/final-send-error.webp +0 -0
  269. package/ios/Tests/ShareTarget/UIHarness/evidence/final-sending-disk.json +58 -0
  270. package/ios/Tests/ShareTarget/UIHarness/evidence/final-sending.ax.json +1215 -0
  271. package/ios/Tests/ShareTarget/UIHarness/evidence/final-sending.webp +0 -0
  272. package/ios/Tests/ShareTarget/UIHarness/evidence/principal-after-log.txt +74 -0
  273. package/ios/Tests/ShareTarget/UIHarness/evidence/share-receipt.json +15 -0
  274. package/ios/Tests/ShareTarget/UIHarness/evidence/strict-build.txt +8 -0
  275. package/ios/Tests/ShareTarget/UIHarness/interact.py +61 -0
  276. package/ios/Tests/ShareTarget/evidence.md +142 -0
  277. package/nitrogen/generated/android/One+autolinking.cmake +2 -2
  278. package/nitrogen/generated/android/OneOnLoad.cpp +15 -11
  279. package/nitrogen/generated/ios/One-Swift-Cxx-Bridge.cpp +25 -34
  280. package/nitrogen/generated/ios/One-Swift-Cxx-Bridge.hpp +80 -61
  281. package/nitrogen/generated/ios/One-Swift-Cxx-Umbrella.hpp +8 -10
  282. package/nitrogen/generated/ios/OneAutolinking.mm +19 -7
  283. package/nitrogen/generated/ios/OneAutolinking.swift +66 -78
  284. package/nitrogen/generated/ios/c++/{HybridOneCryptoSpecSwift.cpp → HybridOneAppIntentsSpecSwift.cpp} +2 -2
  285. package/nitrogen/generated/ios/c++/HybridOneAppIntentsSpecSwift.hpp +101 -0
  286. package/nitrogen/generated/ios/swift/AppIntentInvocation.swift +52 -0
  287. package/nitrogen/generated/ios/swift/Func_void_AppIntentInvocation.swift +46 -0
  288. package/nitrogen/generated/ios/swift/HybridOneAppIntentsSpec.swift +57 -0
  289. package/nitrogen/generated/ios/swift/HybridOneAppIntentsSpec_cxx.swift +175 -0
  290. package/nitrogen/generated/shared/c++/AppIntentInvocation.hpp +92 -0
  291. package/nitrogen/generated/shared/c++/HybridOneAppIntentsSpec.cpp +23 -0
  292. package/nitrogen/generated/shared/c++/HybridOneAppIntentsSpec.hpp +68 -0
  293. package/nitrogen/generated/shared/c++/HybridOneCryptoSpec.cpp +2 -1
  294. package/nitrogen/generated/shared/c++/HybridOneCryptoSpec.hpp +4 -2
  295. package/nitrogen/generated/shared/c++/HybridOneStorageSpec.cpp +24 -0
  296. package/nitrogen/generated/shared/c++/HybridOneStorageSpec.hpp +67 -0
  297. package/package.json +26 -12
  298. package/schema.json +11 -0
  299. package/src/cli/prebuild.ts +4 -2
  300. package/src/index.ts +1 -0
  301. package/src/one.ts +7 -8
  302. package/src/platform/AndroidMenu.test.tsx +1 -0
  303. package/src/platform/AndroidMenu.tsx +35 -8
  304. package/src/platform/Sheet.native.tsx +29 -0
  305. package/src/platform/app-intents/index.native.ts +58 -0
  306. package/src/platform/app-intents/index.ts +10 -0
  307. package/src/platform/app-intents/validate.ts +5 -0
  308. package/src/platform/crypto/index.native.ts +14 -17
  309. package/src/platform/crypto/index.ts +12 -8
  310. package/src/platform/crypto/random.ts +14 -15
  311. package/src/platform/generated/sheetTypes.ts +4 -0
  312. package/src/platform/index.native.ts +3 -1
  313. package/src/platform/index.ts +3 -1
  314. package/src/platform/native-source/view.native.tsx +51 -0
  315. package/src/platform/native-source/view.tsx +8 -0
  316. package/src/platform/native-source/viewTypes.ts +9 -0
  317. package/src/platform/specs/OneAppIntents.nitro.ts +13 -0
  318. package/src/platform/specs/OneCrypto.nitro.ts +7 -6
  319. package/src/platform/specs/OneKotlinHostNativeComponent.ts +25 -0
  320. package/src/platform/specs/OneNativeSheetNativeComponent.ts +4 -0
  321. package/src/platform/specs/OneStorage.nitro.ts +11 -0
  322. package/src/platform/storage/index.native.ts +59 -0
  323. package/src/platform/storage/index.ts +32 -0
  324. package/src/platform/storage/validate.ts +10 -0
  325. package/src/polyfills-mobile.ts +1 -1
  326. package/src/tamagui.native.tsx +96 -0
  327. package/src/tamagui.ts +2 -0
  328. package/src/useHeaderHeight.ts +40 -0
  329. package/src/vite/plugins/fileSystemRouterPlugin.test.ts +55 -2
  330. package/src/vite/plugins/fileSystemRouterPlugin.tsx +8 -11
  331. package/src/vite/plugins/kotlinSourcePlugin.ts +14 -2
  332. package/types/cli/prebuild.d.ts.map +1 -1
  333. package/types/index.d.ts +1 -0
  334. package/types/index.d.ts.map +1 -1
  335. package/types/one.d.ts +3 -2
  336. package/types/one.d.ts.map +1 -1
  337. package/types/platform/AndroidMenu.d.ts.map +1 -1
  338. package/types/platform/Sheet.native.d.ts +2 -2
  339. package/types/platform/Sheet.native.d.ts.map +1 -1
  340. package/types/platform/app-intents/index.d.ts +7 -0
  341. package/types/platform/app-intents/index.d.ts.map +1 -0
  342. package/types/platform/app-intents/index.native.d.ts +7 -0
  343. package/types/platform/app-intents/index.native.d.ts.map +1 -0
  344. package/types/platform/app-intents/validate.d.ts +2 -0
  345. package/types/platform/app-intents/validate.d.ts.map +1 -0
  346. package/types/platform/crypto/index.d.ts.map +1 -1
  347. package/types/platform/crypto/index.native.d.ts.map +1 -1
  348. package/types/platform/crypto/random.d.ts +6 -4
  349. package/types/platform/crypto/random.d.ts.map +1 -1
  350. package/types/platform/generated/sheetTypes.d.ts +4 -0
  351. package/types/platform/generated/sheetTypes.d.ts.map +1 -1
  352. package/types/platform/index.d.ts +3 -1
  353. package/types/platform/index.d.ts.map +1 -1
  354. package/types/platform/index.native.d.ts +3 -1
  355. package/types/platform/index.native.d.ts.map +1 -1
  356. package/types/platform/native-source/view.d.ts +4 -0
  357. package/types/platform/native-source/view.d.ts.map +1 -0
  358. package/types/platform/native-source/view.native.d.ts +4 -0
  359. package/types/platform/native-source/view.native.d.ts.map +1 -0
  360. package/types/platform/native-source/viewTypes.d.ts +7 -0
  361. package/types/platform/native-source/viewTypes.d.ts.map +1 -0
  362. package/types/platform/specs/OneAppIntents.nitro.d.ts +14 -0
  363. package/types/platform/specs/OneAppIntents.nitro.d.ts.map +1 -0
  364. package/types/platform/specs/OneCrypto.nitro.d.ts +4 -3
  365. package/types/platform/specs/OneCrypto.nitro.d.ts.map +1 -1
  366. package/types/platform/specs/OneKotlinHostNativeComponent.d.ts +24 -0
  367. package/types/platform/specs/OneKotlinHostNativeComponent.d.ts.map +1 -0
  368. package/types/platform/specs/OneNativeSheetNativeComponent.d.ts +5 -0
  369. package/types/platform/specs/OneNativeSheetNativeComponent.d.ts.map +1 -1
  370. package/types/platform/specs/OneStorage.nitro.d.ts +11 -0
  371. package/types/platform/specs/OneStorage.nitro.d.ts.map +1 -0
  372. package/types/platform/storage/index.d.ts +12 -0
  373. package/types/platform/storage/index.d.ts.map +1 -0
  374. package/types/platform/storage/index.native.d.ts +12 -0
  375. package/types/platform/storage/index.native.d.ts.map +1 -0
  376. package/types/platform/storage/validate.d.ts +3 -0
  377. package/types/platform/storage/validate.d.ts.map +1 -0
  378. package/types/tamagui.d.ts +2 -0
  379. package/types/tamagui.d.ts.map +1 -0
  380. package/types/tamagui.native.d.ts +2 -0
  381. package/types/tamagui.native.d.ts.map +1 -0
  382. package/types/useHeaderHeight.d.ts +6 -0
  383. package/types/useHeaderHeight.d.ts.map +1 -0
  384. package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
  385. package/types/vite/plugins/kotlinSourcePlugin.d.ts.map +1 -1
  386. package/android/src/main/java/com/margelo/nitro/one/HybridOneCrypto.kt +0 -23
  387. package/dist/cjs/platform/preferences/index.cjs +0 -61
  388. package/dist/cjs/platform/preferences/index.native.cjs +0 -84
  389. package/dist/cjs/platform/preferences/index.native.js +0 -86
  390. package/dist/cjs/platform/preferences/index.native.js.map +0 -1
  391. package/dist/cjs/platform/preferences/validate.native.js.map +0 -1
  392. package/dist/cjs/platform/specs/OnePreferences.nitro.native.js.map +0 -1
  393. package/dist/esm/platform/preferences/index.mjs +0 -42
  394. package/dist/esm/platform/preferences/index.mjs.map +0 -1
  395. package/dist/esm/platform/preferences/index.native.js +0 -63
  396. package/dist/esm/platform/preferences/index.native.js.map +0 -1
  397. package/dist/esm/platform/preferences/validate.mjs +0 -13
  398. package/dist/esm/platform/preferences/validate.mjs.map +0 -1
  399. package/dist/esm/platform/preferences/validate.native.js +0 -13
  400. package/dist/esm/platform/preferences/validate.native.js.map +0 -1
  401. package/ios/Nitro/HybridOneCrypto.swift +0 -20
  402. package/ios/Nitro/HybridOnePreferences.swift +0 -64
  403. package/nitrogen/generated/android/c++/JHybridOneCryptoSpec.cpp +0 -54
  404. package/nitrogen/generated/android/c++/JHybridOneCryptoSpec.hpp +0 -63
  405. package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/HybridOneCryptoSpec.kt +0 -57
  406. package/nitrogen/generated/ios/c++/HybridOneCryptoSpecSwift.hpp +0 -84
  407. package/nitrogen/generated/ios/c++/HybridOnePreferencesSpecSwift.cpp +0 -11
  408. package/nitrogen/generated/ios/c++/HybridOnePreferencesSpecSwift.hpp +0 -120
  409. package/nitrogen/generated/ios/swift/HybridOneCryptoSpec.swift +0 -55
  410. package/nitrogen/generated/ios/swift/HybridOneCryptoSpec_cxx.swift +0 -138
  411. package/nitrogen/generated/ios/swift/HybridOnePreferencesSpec.swift +0 -60
  412. package/nitrogen/generated/ios/swift/HybridOnePreferencesSpec_cxx.swift +0 -229
  413. package/nitrogen/generated/shared/c++/HybridOnePreferencesSpec.cpp +0 -26
  414. package/nitrogen/generated/shared/c++/HybridOnePreferencesSpec.hpp +0 -69
  415. package/src/platform/preferences/index.native.ts +0 -66
  416. package/src/platform/preferences/index.ts +0 -46
  417. package/src/platform/preferences/validate.ts +0 -12
  418. package/src/platform/specs/OnePreferences.nitro.ts +0 -10
  419. package/types/platform/preferences/index.d.ts +0 -16
  420. package/types/platform/preferences/index.d.ts.map +0 -1
  421. package/types/platform/preferences/index.native.d.ts +0 -16
  422. package/types/platform/preferences/index.native.d.ts.map +0 -1
  423. package/types/platform/preferences/validate.d.ts +0 -3
  424. package/types/platform/preferences/validate.d.ts.map +0 -1
  425. package/types/platform/specs/OnePreferences.nitro.d.ts +0 -12
  426. package/types/platform/specs/OnePreferences.nitro.d.ts.map +0 -1
  427. /package/dist/esm/platform/{specs/OnePreferences.nitro.mjs → native-source/viewTypes.mjs} +0 -0
  428. /package/dist/esm/platform/{specs/OnePreferences.nitro.native.js → native-source/viewTypes.native.js} +0 -0
@@ -0,0 +1,344 @@
1
+ package dev.onejs.one.sharetarget
2
+
3
+ import android.content.ContentResolver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.net.Uri
7
+ import android.provider.OpenableColumns
8
+ import android.util.Patterns
9
+ import java.io.File
10
+ import kotlinx.coroutines.CancellationException
11
+ import kotlinx.coroutines.Dispatchers
12
+ import kotlinx.coroutines.currentCoroutineContext
13
+ import kotlinx.coroutines.ensureActive
14
+ import kotlinx.coroutines.withContext
15
+
16
+ // every field is required: a caller must make an explicit, bounded decision
17
+ // for each limit rather than inherit an unbounded default. acceptedFileMimePrefixes
18
+ // may still be null (any file type accepted), but that is now a choice the
19
+ // caller wrote down, not a fallback.
20
+ data class OneShareIntakeLimits(
21
+ val maxItems: Int,
22
+ val maxItemBytes: Long,
23
+ val maxTotalBytes: Long,
24
+ // null accepts any file mime type; entries may end in "/*" as a prefix match
25
+ val acceptedFileMimePrefixes: Set<String>?,
26
+ val acceptedText: Boolean,
27
+ val acceptedUrls: Boolean,
28
+ )
29
+
30
+ enum class OneShareIntakeIssueReason {
31
+ UNACCEPTED_TYPE,
32
+ OVER_ITEM_LIMIT,
33
+ OVER_SIZE_LIMIT,
34
+ READ_ERROR,
35
+ }
36
+
37
+ data class OneShareIntakeIssue(val label: String, val reason: OneShareIntakeIssueReason)
38
+
39
+ data class OneShareIntakeResult(
40
+ // EXTRA_TEXT verbatim, meant for the editable composer field; never also
41
+ // duplicated into items.
42
+ val text: String,
43
+ val items: List<ShareItem>,
44
+ val truncated: Boolean,
45
+ val issues: List<OneShareIntakeIssue>,
46
+ )
47
+
48
+ // thrown for a failure that isn't a single rejected/oversized/unreadable
49
+ // item (those become OneShareIntakeIssue entries and the loop continues).
50
+ // callers must handle this rather than let it crash the host activity.
51
+ class OneShareIntakeException(message: String, cause: Throwable? = null) : Exception(message, cause)
52
+
53
+ // reads an incoming ACTION_SEND/ACTION_SEND_MULTIPLE intent into a bounded,
54
+ // private-storage-owned result. never reads a whole stream into memory and
55
+ // never decodes image bytes: every file is copied in fixed-size chunks
56
+ // straight to disk so size limits hold regardless of what the sender claims.
57
+ // text/url entries count against maxItems and their utf-8 byte length counts
58
+ // against maxTotalBytes exactly like files do.
59
+ internal object OneShareIntake {
60
+ private const val COPY_BUFFER_BYTES = 8192
61
+
62
+ // every resolver query, stream read and file write below is blocking
63
+ // disk/binder i/o; this always runs on Dispatchers.IO regardless of
64
+ // what dispatcher the caller is on, so a caller invoking this from a
65
+ // Main-dispatched coroutine (as the activity's LaunchedEffect does)
66
+ // never blocks the UI thread on it.
67
+ suspend fun intake(
68
+ context: Context,
69
+ intent: Intent,
70
+ destinationDir: File,
71
+ limits: OneShareIntakeLimits,
72
+ ): OneShareIntakeResult =
73
+ withContext(Dispatchers.IO) {
74
+ try {
75
+ intakeOrThrow(context, intent, destinationDir, limits)
76
+ } catch (e: CancellationException) {
77
+ throw e
78
+ } catch (e: Exception) {
79
+ throw OneShareIntakeException("Failed to read shared content: ${e.message}", e)
80
+ }
81
+ }
82
+
83
+ // no-drop: a single rejected/oversized/unreadable entry anywhere in the
84
+ // intent (including EXTRA_TEXT itself) aborts the whole intake rather
85
+ // than silently continuing without it. any files already copied in this
86
+ // call are deleted before returning, so a caller never sees a partial
87
+ // item set it could accidentally send -- the caller is expected to
88
+ // surface the single issue and block sending, not offer a truncated
89
+ // result as success.
90
+ private suspend fun intakeOrThrow(
91
+ context: Context,
92
+ intent: Intent,
93
+ destinationDir: File,
94
+ limits: OneShareIntakeLimits,
95
+ ): OneShareIntakeResult {
96
+ val resolver = context.contentResolver
97
+ // exact CharSequence bytes, never trimmed: whitespace the sender put
98
+ // in EXTRA_TEXT is part of what the user agreed to share.
99
+ val rawPrimaryText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString().orEmpty()
100
+
101
+ val seenTextValues = mutableSetOf<String>()
102
+ val items = mutableListOf<ShareItem>()
103
+ val copiedFiles = mutableListOf<File>()
104
+ var itemCount = 0
105
+ var totalBytes = 0L
106
+ var primaryText = ""
107
+
108
+ fun abort(issue: OneShareIntakeIssue): OneShareIntakeResult {
109
+ copiedFiles.forEach { it.delete() }
110
+ return OneShareIntakeResult(text = "", items = emptyList(), truncated = true, issues = listOf(issue))
111
+ }
112
+
113
+ if (rawPrimaryText.isNotEmpty()) {
114
+ val isUrl = isUrl(rawPrimaryText)
115
+ if (isUrl && !limits.acceptedUrls) {
116
+ return abort(OneShareIntakeIssue(rawPrimaryText, OneShareIntakeIssueReason.UNACCEPTED_TYPE))
117
+ }
118
+ if (!isUrl && !limits.acceptedText) {
119
+ return abort(OneShareIntakeIssue(rawPrimaryText, OneShareIntakeIssueReason.UNACCEPTED_TYPE))
120
+ }
121
+ val bytes = rawPrimaryText.toByteArray(Charsets.UTF_8).size.toLong()
122
+ if (bytes > limits.maxItemBytes || bytes > limits.maxTotalBytes) {
123
+ return abort(OneShareIntakeIssue(rawPrimaryText, OneShareIntakeIssueReason.OVER_SIZE_LIMIT))
124
+ }
125
+ if (limits.maxItems < 1) return abort(OneShareIntakeIssue(rawPrimaryText, OneShareIntakeIssueReason.OVER_ITEM_LIMIT))
126
+ itemCount += 1
127
+ primaryText = rawPrimaryText
128
+ totalBytes += bytes
129
+ seenTextValues.add(rawPrimaryText)
130
+ }
131
+
132
+ for (entry in collectEntries(intent)) {
133
+ currentCoroutineContext().ensureActive()
134
+
135
+ when (entry) {
136
+ is Entry.CharSequenceEntry -> {
137
+ val value = entry.value
138
+ if (!seenTextValues.add(value)) continue
139
+
140
+ val isUrl = isUrl(value)
141
+ if (isUrl && !limits.acceptedUrls) {
142
+ return abort(OneShareIntakeIssue(value, OneShareIntakeIssueReason.UNACCEPTED_TYPE))
143
+ }
144
+ if (!isUrl && !limits.acceptedText) {
145
+ return abort(OneShareIntakeIssue(value, OneShareIntakeIssueReason.UNACCEPTED_TYPE))
146
+ }
147
+ if (itemCount >= limits.maxItems) {
148
+ return abort(OneShareIntakeIssue(value, OneShareIntakeIssueReason.OVER_ITEM_LIMIT))
149
+ }
150
+ val bytes = value.toByteArray(Charsets.UTF_8).size.toLong()
151
+ if (bytes > limits.maxItemBytes || totalBytes + bytes > limits.maxTotalBytes) {
152
+ return abort(OneShareIntakeIssue(value, OneShareIntakeIssueReason.OVER_SIZE_LIMIT))
153
+ }
154
+
155
+ items.add(if (isUrl) ShareItem.Url(value) else ShareItem.Text(value))
156
+ itemCount += 1
157
+ totalBytes += bytes
158
+ }
159
+
160
+ is Entry.UriEntry -> {
161
+ val uri = entry.uri
162
+ val label = uri.toString()
163
+ // incoming senders must use a permission-granted provider uri.
164
+ // a file uri would be opened under the receiver's own uid.
165
+ if (uri.scheme != ContentResolver.SCHEME_CONTENT) {
166
+ return abort(OneShareIntakeIssue(label, OneShareIntakeIssueReason.UNACCEPTED_TYPE))
167
+ }
168
+ val mimeType = resolver.getType(uri) ?: "application/octet-stream"
169
+
170
+ if (!isAcceptedMime(mimeType, limits.acceptedFileMimePrefixes)) {
171
+ return abort(OneShareIntakeIssue(label, OneShareIntakeIssueReason.UNACCEPTED_TYPE))
172
+ }
173
+ if (itemCount >= limits.maxItems) {
174
+ return abort(OneShareIntakeIssue(label, OneShareIntakeIssueReason.OVER_ITEM_LIMIT))
175
+ }
176
+ val remainingTotal = limits.maxTotalBytes - totalBytes
177
+ if (remainingTotal <= 0) {
178
+ return abort(OneShareIntakeIssue(label, OneShareIntakeIssueReason.OVER_SIZE_LIMIT))
179
+ }
180
+
181
+ val (displayName, declaredSize) = queryMetadata(resolver, uri)
182
+ val cap = minOf(limits.maxItemBytes, remainingTotal)
183
+ if (declaredSize != null && declaredSize > cap) {
184
+ return abort(OneShareIntakeIssue(displayName ?: label, OneShareIntakeIssueReason.OVER_SIZE_LIMIT))
185
+ }
186
+
187
+ val copied = boundedCopy(resolver, uri, destinationDir, displayName, cap)
188
+ if (copied == null) {
189
+ return abort(OneShareIntakeIssue(displayName ?: label, OneShareIntakeIssueReason.READ_ERROR))
190
+ }
191
+ if (copied.hitLimit) {
192
+ copied.file.delete()
193
+ return abort(OneShareIntakeIssue(displayName ?: label, OneShareIntakeIssueReason.OVER_SIZE_LIMIT))
194
+ }
195
+
196
+ copiedFiles.add(copied.file)
197
+ items.add(
198
+ ShareItem.File(
199
+ uri = Uri.fromFile(copied.file),
200
+ name = displayName ?: copied.file.name,
201
+ mimeType = mimeType,
202
+ size = copied.bytesCopied,
203
+ )
204
+ )
205
+ itemCount += 1
206
+ totalBytes += copied.bytesCopied
207
+ }
208
+ }
209
+ }
210
+
211
+ return OneShareIntakeResult(
212
+ text = primaryText,
213
+ items = items,
214
+ truncated = false,
215
+ issues = emptyList(),
216
+ )
217
+ }
218
+
219
+ private sealed class Entry {
220
+ data class UriEntry(val uri: Uri) : Entry()
221
+
222
+ data class CharSequenceEntry(val value: String) : Entry()
223
+ }
224
+
225
+ // EXTRA_STREAM (single/multiple) and every ClipData item: a ClipData
226
+ // item carries either a content uri (file) or inline text/url, and a
227
+ // uri already seen via EXTRA_STREAM or an earlier ClipData item is
228
+ // never copied twice.
229
+ private fun collectEntries(intent: Intent): List<Entry> {
230
+ val seenUris = linkedSetOf<Uri>()
231
+ val entries = mutableListOf<Entry>()
232
+
233
+ fun addUri(uri: Uri) {
234
+ if (seenUris.add(uri)) entries.add(Entry.UriEntry(uri))
235
+ }
236
+
237
+ when (intent.action) {
238
+ Intent.ACTION_SEND -> {
239
+ @Suppress("DEPRECATION")
240
+ intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)?.let(::addUri)
241
+ }
242
+ Intent.ACTION_SEND_MULTIPLE -> {
243
+ @Suppress("DEPRECATION")
244
+ intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)?.forEach(::addUri)
245
+ }
246
+ }
247
+
248
+ intent.clipData?.let { clipData ->
249
+ for (i in 0 until clipData.itemCount) {
250
+ val item = clipData.getItemAt(i)
251
+ val uri = item.uri
252
+ if (uri != null) {
253
+ addUri(uri)
254
+ continue
255
+ }
256
+ val text = item.text?.toString()
257
+ if (!text.isNullOrEmpty()) entries.add(Entry.CharSequenceEntry(text))
258
+ }
259
+ }
260
+
261
+ return entries
262
+ }
263
+
264
+ private fun isUrl(value: String): Boolean = Patterns.WEB_URL.matcher(value).matches()
265
+
266
+ private fun isAcceptedMime(mimeType: String, prefixes: Set<String>?): Boolean {
267
+ if (prefixes == null) return true
268
+ return prefixes.any { prefix ->
269
+ if (prefix.endsWith("/*")) mimeType.startsWith(prefix.removeSuffix("*"))
270
+ else mimeType == prefix
271
+ }
272
+ }
273
+
274
+ private fun queryMetadata(resolver: ContentResolver, uri: Uri): Pair<String?, Long?> =
275
+ try {
276
+ resolver
277
+ .query(uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE), null, null, null)
278
+ ?.use { cursor ->
279
+ if (!cursor.moveToFirst()) return@use null to null
280
+ val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
281
+ val sizeIdx = cursor.getColumnIndex(OpenableColumns.SIZE)
282
+ val name = if (nameIdx >= 0) cursor.getString(nameIdx) else null
283
+ val size = if (sizeIdx >= 0 && !cursor.isNull(sizeIdx)) cursor.getLong(sizeIdx) else null
284
+ name to size
285
+ } ?: (null to null)
286
+ } catch (_: Exception) {
287
+ null to null
288
+ }
289
+
290
+ private data class CopyResult(val file: File, val bytesCopied: Long, val hitLimit: Boolean)
291
+
292
+ // streams uri -> destinationDir in COPY_BUFFER_BYTES chunks, checking
293
+ // cancellation and the byte cap every chunk; a cancelled coroutine or a
294
+ // cap overrun both leave the partial file for the caller to delete.
295
+ private suspend fun boundedCopy(
296
+ resolver: ContentResolver,
297
+ uri: Uri,
298
+ destinationDir: File,
299
+ displayName: String?,
300
+ capBytes: Long,
301
+ ): CopyResult? {
302
+ val input = try {
303
+ resolver.openInputStream(uri)
304
+ } catch (_: Exception) {
305
+ null
306
+ } ?: return null
307
+
308
+ destinationDir.mkdirs()
309
+ val file = File(destinationDir, uniqueFileName(displayName))
310
+ return try {
311
+ input.use { stream ->
312
+ file.outputStream().use { out ->
313
+ val buffer = ByteArray(COPY_BUFFER_BYTES)
314
+ var total = 0L
315
+ while (true) {
316
+ currentCoroutineContext().ensureActive()
317
+ val read = stream.read(buffer)
318
+ if (read < 0) break
319
+ total += read
320
+ if (total > capBytes) {
321
+ return@use CopyResult(file, total, true)
322
+ }
323
+ out.write(buffer, 0, read)
324
+ }
325
+ CopyResult(file, total, false)
326
+ }
327
+ }
328
+ } catch (e: Exception) {
329
+ file.delete()
330
+ if (e is CancellationException) throw e
331
+ null
332
+ }
333
+ }
334
+
335
+ private fun uniqueFileName(displayName: String?): String {
336
+ val sanitized = displayName
337
+ ?.replace(Regex("[^A-Za-z0-9._-]"), "_")
338
+ ?.trim('.', '_')
339
+ ?.takeLast(120)
340
+ ?.takeIf { it.isNotEmpty() }
341
+ ?: "item"
342
+ return "${System.nanoTime()}-$sanitized"
343
+ }
344
+ }
@@ -0,0 +1,36 @@
1
+ package dev.onejs.one.sharetarget
2
+
3
+ import android.net.Uri
4
+
5
+ // a place the user can send a share submission to (an account, a channel,
6
+ // a conversation...); the generated adapter decides what these mean.
7
+ data class ShareDestination(
8
+ val id: String,
9
+ val title: String,
10
+ val subtitle: String? = null,
11
+ )
12
+
13
+ sealed class ShareItem {
14
+ data class Text(val value: String) : ShareItem()
15
+
16
+ data class Url(val value: String) : ShareItem()
17
+
18
+ // uri points at a file this process owns (copied into private storage
19
+ // by OneShareIntake), never at the original content:// uri the sender
20
+ // granted only for the lifetime of the share intent.
21
+ data class File(
22
+ val uri: Uri,
23
+ val name: String,
24
+ val mimeType: String,
25
+ val size: Long,
26
+ ) : ShareItem()
27
+ }
28
+
29
+ // id is the stable draft id, stable across rotation/process recreation so
30
+ // the adapter and any retry logic can recognize a resubmission.
31
+ data class ShareSubmission(
32
+ val id: String,
33
+ val destinationId: String,
34
+ val text: String,
35
+ val items: List<ShareItem>,
36
+ )
@@ -0,0 +1,207 @@
1
+ package dev.onejs.one.sharetarget
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import androidx.lifecycle.ViewModel
6
+ import java.util.UUID
7
+ import kotlinx.coroutines.CancellationException
8
+ import kotlinx.coroutines.CoroutineScope
9
+ import kotlinx.coroutines.Dispatchers
10
+ import kotlinx.coroutines.Job
11
+ import kotlinx.coroutines.SupervisorJob
12
+ import kotlinx.coroutines.cancel
13
+ import kotlinx.coroutines.cancelAndJoin
14
+ import kotlinx.coroutines.flow.MutableStateFlow
15
+ import kotlinx.coroutines.flow.StateFlow
16
+ import kotlinx.coroutines.launch
17
+ import kotlinx.coroutines.sync.Mutex
18
+ import kotlinx.coroutines.sync.withLock
19
+ import kotlinx.coroutines.withContext
20
+
21
+ internal enum class SharePhase { LOADING, READY, FAILED, SENDING, CANCELLING, DELIVERED, CANCELLED }
22
+
23
+ internal data class ShareSessionState(
24
+ val id: String = "",
25
+ val phase: SharePhase = SharePhase.LOADING,
26
+ val text: String = "",
27
+ val items: List<ShareItem> = emptyList(),
28
+ val destinations: List<ShareDestination> = emptyList(),
29
+ val destinationId: String? = null,
30
+ val error: String? = null,
31
+ val pendingDelivery: Boolean = false,
32
+ )
33
+
34
+ // the activity only renders this retained owner. rotation never creates a
35
+ // second sender or loses the phase of a submission already in flight.
36
+ internal class OneShareSessionModel(
37
+ private val context: Context,
38
+ private val adapter: OneShareTargetAdapter,
39
+ private val limits: OneShareIntakeLimits,
40
+ ) : ViewModel() {
41
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
42
+ private val store = OneShareDraftStore(context)
43
+ private val writes = Mutex()
44
+ private val mutableState = MutableStateFlow(ShareSessionState())
45
+ val state: StateFlow<ShareSessionState> = mutableState
46
+ private var loading: Job? = null
47
+ private val transitions = Mutex()
48
+ private var incoming: Intent? = null
49
+
50
+ fun open(intent: Intent, restoredId: String? = null) {
51
+ if (state.value.id.isNotEmpty()) return
52
+ start(intent, restoredId ?: UUID.randomUUID().toString())
53
+ }
54
+
55
+ fun receive(intent: Intent) {
56
+ scope.launch { transitions.withLock {
57
+ val previous = state.value
58
+ if (previous.phase == SharePhase.LOADING || previous.phase == SharePhase.FAILED) {
59
+ loading?.cancelAndJoin()
60
+ // persist a completed intake; remove only an incomplete one.
61
+ writes.withLock {
62
+ withContext(Dispatchers.IO) {
63
+ if (store.load(previous.id) == null) store.delete(previous.id)
64
+ }
65
+ }
66
+ }
67
+ start(intent, UUID.randomUUID().toString())
68
+ } }
69
+ }
70
+
71
+ private fun start(intent: Intent, id: String) {
72
+ incoming = intent
73
+ mutableState.value = ShareSessionState(id = id)
74
+ load()
75
+ }
76
+
77
+ fun retry() {
78
+ if (state.value.phase != SharePhase.FAILED) return
79
+ mutableState.value = state.value.copy(phase = SharePhase.LOADING, error = null)
80
+ load()
81
+ }
82
+
83
+ private fun load() {
84
+ val id = state.value.id
85
+ val intent = incoming
86
+ loading = scope.launch {
87
+ try {
88
+ val restored = withContext(Dispatchers.IO) { store.load(id) }
89
+ if (restored != null) {
90
+ mutableState.value = state.value.copy(text = restored.editedText, items = restored.items,
91
+ destinationId = restored.destinationId, pendingDelivery = restored.pendingDelivery)
92
+ } else {
93
+ val directory = withContext(Dispatchers.IO) {
94
+ store.clearItems(id)
95
+ store.itemsDirectory(id)
96
+ }
97
+ val result = OneShareIntake.intake(context, requireNotNull(intent), directory, limits)
98
+ require(result.issues.isEmpty()) { result.issues.joinToString { "${it.label}: ${it.reason}" } }
99
+ mutableState.value = state.value.copy(text = result.text, items = result.items)
100
+ persist(state.value)
101
+ }
102
+ val destinations = adapter.destinations()
103
+ val current = state.value
104
+ if (current.id != id) return@launch
105
+ require(!current.pendingDelivery || destinations.any { it.id == current.destinationId }) {
106
+ "The original destination is unavailable. Retry when it reconnects."
107
+ }
108
+ mutableState.value = current.copy(phase = SharePhase.READY, destinations = destinations,
109
+ destinationId = current.destinationId?.takeIf { selected -> destinations.any { it.id == selected } }
110
+ ?: destinations.firstOrNull()?.id, error = if (current.pendingDelivery) "Earlier delivery may have been accepted. Retry sends the same submission." else null)
111
+ persist(state.value)
112
+ } catch (error: CancellationException) {
113
+ throw error
114
+ } catch (error: Exception) {
115
+ if (state.value.id == id) mutableState.value = state.value.copy(phase = SharePhase.FAILED, error = error.message)
116
+ }
117
+ }
118
+ }
119
+
120
+ fun edit(text: String) {
121
+ if (state.value.phase != SharePhase.READY || state.value.pendingDelivery) return
122
+ mutableState.value = state.value.copy(text = text)
123
+ saveEdit()
124
+ }
125
+
126
+ fun select(id: String) {
127
+ if (state.value.phase != SharePhase.READY || state.value.pendingDelivery || state.value.destinations.none { it.id == id }) return
128
+ mutableState.value = state.value.copy(destinationId = id)
129
+ saveEdit()
130
+ }
131
+
132
+ private fun saveEdit() {
133
+ val snapshot = state.value
134
+ scope.launch {
135
+ try { persist(snapshot) }
136
+ catch (error: Exception) {
137
+ if (state.value.id == snapshot.id && state.value.phase == SharePhase.READY)
138
+ mutableState.value = state.value.copy(phase = SharePhase.FAILED, error = error.message)
139
+ }
140
+ }
141
+ }
142
+
143
+ private suspend fun persist(snapshot: ShareSessionState, sending: Boolean = false) = writes.withLock {
144
+ // a queued edit cannot write after the send or cancellation barrier.
145
+ val current = state.value
146
+ if (!sending && (current.id != snapshot.id || current.phase == SharePhase.SENDING ||
147
+ current.phase == SharePhase.CANCELLING || current.phase == SharePhase.DELIVERED || current.phase == SharePhase.CANCELLED)) return@withLock
148
+ withContext(Dispatchers.IO) {
149
+ store.save(StoredShareDraft(snapshot.id, snapshot.destinationId, snapshot.text, snapshot.items, sending))
150
+ }
151
+ }
152
+
153
+ fun canSend(snapshot: ShareSessionState = state.value): Boolean {
154
+ val bytes = snapshot.text.toByteArray(Charsets.UTF_8).size.toLong()
155
+ val itemBytes = snapshot.items.sumOf { when (it) {
156
+ is ShareItem.File -> it.size
157
+ is ShareItem.Text -> it.value.toByteArray(Charsets.UTF_8).size.toLong()
158
+ is ShareItem.Url -> it.value.toByteArray(Charsets.UTF_8).size.toLong()
159
+ } }
160
+ return snapshot.phase == SharePhase.READY && snapshot.destinationId != null &&
161
+ snapshot.destinations.any { it.id == snapshot.destinationId } &&
162
+ bytes <= limits.maxItemBytes && bytes + itemBytes <= limits.maxTotalBytes &&
163
+ snapshot.items.size + (if (snapshot.text.isEmpty()) 0 else 1) <= limits.maxItems &&
164
+ (snapshot.text.isNotEmpty() || snapshot.items.isNotEmpty())
165
+ }
166
+
167
+ fun send() {
168
+ val snapshot = state.value
169
+ if (!canSend(snapshot)) return
170
+ mutableState.value = snapshot.copy(phase = SharePhase.SENDING, error = null)
171
+ scope.launch {
172
+ try {
173
+ // mark the attempt durably before transport; process recovery
174
+ // keeps this id so a retry uses the adapter's idempotency key.
175
+ persist(snapshot, sending = true)
176
+ if (state.value.id == snapshot.id) mutableState.value = state.value.copy(pendingDelivery = true)
177
+ adapter.send(ShareSubmission(snapshot.id, requireNotNull(snapshot.destinationId), snapshot.text, snapshot.items))
178
+ writes.withLock { withContext(Dispatchers.IO) { store.delete(snapshot.id) } }
179
+ if (state.value.id == snapshot.id) mutableState.value = state.value.copy(phase = SharePhase.DELIVERED)
180
+ } catch (error: CancellationException) {
181
+ throw error
182
+ } catch (error: Exception) {
183
+ if (state.value.id == snapshot.id) mutableState.value = state.value.copy(phase = SharePhase.READY, error = error.message)
184
+ }
185
+ }
186
+ }
187
+
188
+ fun discard() {
189
+ val snapshot = state.value
190
+ if (snapshot.pendingDelivery || snapshot.phase == SharePhase.SENDING || snapshot.phase == SharePhase.CANCELLING ||
191
+ snapshot.phase == SharePhase.DELIVERED || snapshot.phase == SharePhase.CANCELLED) return
192
+ mutableState.value = snapshot.copy(phase = SharePhase.CANCELLING)
193
+ scope.launch {
194
+ try {
195
+ loading?.cancelAndJoin()
196
+ writes.withLock { withContext(Dispatchers.IO) { store.delete(snapshot.id) } }
197
+ if (state.value.id == snapshot.id) mutableState.value = state.value.copy(phase = SharePhase.CANCELLED)
198
+ } catch (error: CancellationException) {
199
+ throw error
200
+ } catch (error: Exception) {
201
+ if (state.value.id == snapshot.id) mutableState.value = state.value.copy(phase = SharePhase.FAILED, error = error.message)
202
+ }
203
+ }
204
+ }
205
+
206
+ override fun onCleared() { scope.cancel() }
207
+ }