@oxy.so/services 11.2.0-oxy-one.3 → 11.2.0-oxy-one.4

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 (1025) hide show
  1. package/lib/commonjs/assets/assets/icons/OxyServices.tsx +42 -40
  2. package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +58 -31
  3. package/lib/commonjs/assets/icons/OxyServices.js.map +1 -1
  4. package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -1
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/notifications/deviceNotifications.js +8 -4
  7. package/lib/commonjs/notifications/deviceNotifications.js.map +1 -1
  8. package/lib/commonjs/ui/analytics/productAnalytics.js.map +1 -1
  9. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  10. package/lib/commonjs/ui/components/BenefitList.js.map +1 -1
  11. package/lib/commonjs/ui/components/BloomLocaleBridge.js.map +1 -1
  12. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  13. package/lib/commonjs/ui/components/FollowTargetButton.js +1 -1
  14. package/lib/commonjs/ui/components/FollowTargetButton.js.map +1 -1
  15. package/lib/commonjs/ui/components/LanguageBridge.js.map +1 -1
  16. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  17. package/lib/commonjs/ui/components/OxyAuthPrompt.js.map +1 -1
  18. package/lib/commonjs/ui/components/OxyConsentScreen.js.map +1 -1
  19. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  20. package/lib/commonjs/ui/components/OxySignInButton.js +1 -1
  21. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  22. package/lib/commonjs/ui/components/OxySignInRequestSurface.js.map +1 -1
  23. package/lib/commonjs/ui/components/PeableButton.js.map +1 -1
  24. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  25. package/lib/commonjs/ui/components/ProfileSummaryCard.js +1 -1
  26. package/lib/commonjs/ui/components/ProfileSummaryCard.js.map +1 -1
  27. package/lib/commonjs/ui/components/RequireOxyAuth.js.map +1 -1
  28. package/lib/commonjs/ui/components/SettingsIcon.js.map +1 -1
  29. package/lib/commonjs/ui/components/SurfaceHeaderAction.js.map +1 -1
  30. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
  31. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  32. package/lib/commonjs/ui/components/authChooser/TroubleDisclosure.js.map +1 -1
  33. package/lib/commonjs/ui/components/authChooser/requestSurfaces.js.map +1 -1
  34. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
  35. package/lib/commonjs/ui/components/feedback/constants.js.map +1 -1
  36. package/lib/commonjs/ui/components/feedback/useFeedbackForm.js.map +1 -1
  37. package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
  38. package/lib/commonjs/ui/components/fileManagement/FileViewer.js +1 -1
  39. package/lib/commonjs/ui/components/fileManagement/FileViewer.js.map +1 -1
  40. package/lib/commonjs/ui/components/fileManagement/UploadPreview.js.map +1 -1
  41. package/lib/commonjs/ui/components/followButtonAccessibility.js.map +1 -1
  42. package/lib/commonjs/ui/components/followRules.js.map +1 -1
  43. package/lib/commonjs/ui/components/icon/FAIRWalletIcon.js.map +1 -1
  44. package/lib/commonjs/ui/components/logo/LogoText.js.map +1 -1
  45. package/lib/commonjs/ui/components/modals/DeleteAccountModal.js.map +1 -1
  46. package/lib/commonjs/ui/components/oauthNavigation.js.map +1 -1
  47. package/lib/commonjs/ui/components/payment/PaymentDetailsStep.js.map +1 -1
  48. package/lib/commonjs/ui/components/payment/PaymentMethodStep.js.map +1 -1
  49. package/lib/commonjs/ui/components/payment/PaymentReviewStep.js.map +1 -1
  50. package/lib/commonjs/ui/components/payment/PaymentSuccessStep.js.map +1 -1
  51. package/lib/commonjs/ui/components/payment/PaymentSummaryStep.js +1 -1
  52. package/lib/commonjs/ui/components/payment/PaymentSummaryStep.js.map +1 -1
  53. package/lib/commonjs/ui/components/payment/constants.js.map +1 -1
  54. package/lib/commonjs/ui/components/payment/paymentStyles.js.map +1 -1
  55. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  56. package/lib/commonjs/ui/components/signIn/InlineCommonsQr.js.map +1 -1
  57. package/lib/commonjs/ui/components/signIn/OxyAccountPicker.js.map +1 -1
  58. package/lib/commonjs/ui/components/signIn/OxyAuthScreen.js.map +1 -1
  59. package/lib/commonjs/ui/components/signIn/OxyAuthenticatorPanel.js.map +1 -1
  60. package/lib/commonjs/ui/components/signIn/OxyDeleteAccountPanel.js.map +1 -1
  61. package/lib/commonjs/ui/components/signIn/OxyLinkCommonsPanel.js.map +1 -1
  62. package/lib/commonjs/ui/components/signIn/OxyPasswordPanel.js.map +1 -1
  63. package/lib/commonjs/ui/components/signIn/OxySignInPanel.js.map +1 -1
  64. package/lib/commonjs/ui/components/signIn/OxySignUpPanel.js.map +1 -1
  65. package/lib/commonjs/ui/components/signIn/ReauthStep.js.map +1 -1
  66. package/lib/commonjs/ui/components/signIn/accountFlowParts.js.map +1 -1
  67. package/lib/commonjs/ui/components/signIn/artwork.js.map +1 -1
  68. package/lib/commonjs/ui/components/signIn/signInFlowStore.js.map +1 -1
  69. package/lib/commonjs/ui/components/styles/overlay.js.map +1 -1
  70. package/lib/commonjs/ui/components/styles/shadow.js.map +1 -1
  71. package/lib/commonjs/ui/components/surfaces/ActionSheetSurface.js.map +1 -1
  72. package/lib/commonjs/ui/components/utils/hasTouchHandler.js.map +1 -1
  73. package/lib/commonjs/ui/components/utils/roundLayoutSize.js.map +1 -1
  74. package/lib/commonjs/ui/components/utils/splitStyles.js.map +1 -1
  75. package/lib/commonjs/ui/constants/spacing.js.map +1 -1
  76. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  77. package/lib/commonjs/ui/context/commitSessionFlow.js.map +1 -1
  78. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  79. package/lib/commonjs/ui/context/isolatedOAuthSession.js.map +1 -1
  80. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  81. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -1
  82. package/lib/commonjs/ui/hooks/accountQueryPersistence.js.map +1 -1
  83. package/lib/commonjs/ui/hooks/mutations/mutationFactory.js.map +1 -1
  84. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  85. package/lib/commonjs/ui/hooks/persistedQueryCache.js.map +1 -1
  86. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
  87. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  88. package/lib/commonjs/ui/hooks/queries/useFileQueries.js.map +1 -1
  89. package/lib/commonjs/ui/hooks/queries/usePaymentQueries.js.map +1 -1
  90. package/lib/commonjs/ui/hooks/queries/usePersonalPlans.js.map +1 -1
  91. package/lib/commonjs/ui/hooks/queries/useSecurityQueries.js.map +1 -1
  92. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
  93. package/lib/commonjs/ui/hooks/queries/userCache.js +38 -38
  94. package/lib/commonjs/ui/hooks/queries/userCache.js.map +1 -1
  95. package/lib/commonjs/ui/hooks/queryClient.js.map +1 -1
  96. package/lib/commonjs/ui/hooks/useAssets.js.map +1 -1
  97. package/lib/commonjs/ui/hooks/useAsyncAction.js.map +1 -1
  98. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  99. package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
  100. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js.map +1 -1
  101. package/lib/commonjs/ui/hooks/useFileFiltering.js.map +1 -1
  102. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
  103. package/lib/commonjs/ui/hooks/useI18n.js.map +1 -1
  104. package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
  105. package/lib/commonjs/ui/hooks/useReduceMotion.js.map +1 -1
  106. package/lib/commonjs/ui/hooks/useResolvedFileUrls.js.map +1 -1
  107. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  108. package/lib/commonjs/ui/hooks/useSettingToggle.js.map +1 -1
  109. package/lib/commonjs/ui/hooks/useStorage.js.map +1 -1
  110. package/lib/commonjs/ui/hooks/useSurfaceFrameWidth.js.map +1 -1
  111. package/lib/commonjs/ui/icons/Ionicons.js.map +1 -1
  112. package/lib/commonjs/ui/icons/MaterialCommunityIcons.js.map +1 -1
  113. package/lib/commonjs/ui/icons/decorativeIconSet.js.map +1 -1
  114. package/lib/commonjs/ui/isFrontend.js.map +1 -1
  115. package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
  116. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  117. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  118. package/lib/commonjs/ui/oauth/applicationSessionLane.js.map +1 -1
  119. package/lib/commonjs/ui/oauth/browserAuthTransport.js.map +1 -1
  120. package/lib/commonjs/ui/oauth/browserBridge.js.map +1 -1
  121. package/lib/commonjs/ui/oauth/completeOAuthCode.js.map +1 -1
  122. package/lib/commonjs/ui/oauth/nativeAuthTransport.js.map +1 -1
  123. package/lib/commonjs/ui/oauth/oauthHandshake.js.map +1 -1
  124. package/lib/commonjs/ui/oauth/oauthPopup.js.map +1 -1
  125. package/lib/commonjs/ui/oauth/oauthPopupMessages.js.map +1 -1
  126. package/lib/commonjs/ui/runtime/createOxyRuntime.js.map +1 -1
  127. package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
  128. package/lib/commonjs/ui/screens/AccountSecurityPanelScreen.js.map +1 -1
  129. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  130. package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -1
  131. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  132. package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
  133. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -1
  134. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
  135. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  136. package/lib/commonjs/ui/screens/DomainsScreen.js.map +1 -1
  137. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +1 -1
  138. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
  139. package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -1
  140. package/lib/commonjs/ui/screens/FileManagementScreen.js +2 -5
  141. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  142. package/lib/commonjs/ui/screens/HelpSupportScreen.js.map +1 -1
  143. package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -1
  144. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  145. package/lib/commonjs/ui/screens/LearnMoreUsernamesScreen.js.map +1 -1
  146. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -1
  147. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  148. package/lib/commonjs/ui/screens/NotificationsScreen.js.map +1 -1
  149. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  150. package/lib/commonjs/ui/screens/PreferencesScreen.js.map +1 -1
  151. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +3 -3
  152. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  153. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  154. package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -1
  155. package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -1
  156. package/lib/commonjs/ui/screens/UserListScreen.js +1 -1
  157. package/lib/commonjs/ui/screens/UserListScreen.js.map +1 -1
  158. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  159. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js.map +1 -1
  160. package/lib/commonjs/ui/screens/fileManagement/FileListSection.js.map +1 -1
  161. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +7 -7
  162. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  163. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js.map +1 -1
  164. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  165. package/lib/commonjs/ui/screens/fileManagement/photoGridLayout.js.map +1 -1
  166. package/lib/commonjs/ui/screens/fileManagement/shared.js.map +1 -1
  167. package/lib/commonjs/ui/screens/linkFormat.js.map +1 -1
  168. package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js.map +1 -1
  169. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  170. package/lib/commonjs/ui/screens/trust/TrustFAQScreen.js.map +1 -1
  171. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  172. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
  173. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  174. package/lib/commonjs/ui/server.js.map +1 -1
  175. package/lib/commonjs/ui/session/authStore.js.map +1 -1
  176. package/lib/commonjs/ui/session/backgroundSession.js.map +1 -1
  177. package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
  178. package/lib/commonjs/ui/session/deviceCredentialTracker.js.map +1 -1
  179. package/lib/commonjs/ui/session/identityBinding.js.map +1 -1
  180. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js.map +1 -1
  181. package/lib/commonjs/ui/session/tokenLossRecovery.js.map +1 -1
  182. package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
  183. package/lib/commonjs/ui/stores/assetStore.js.map +1 -1
  184. package/lib/commonjs/ui/stores/followStore.js.map +1 -1
  185. package/lib/commonjs/ui/utils/accountDeletionHandoff.js.map +1 -1
  186. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  187. package/lib/commonjs/ui/utils/clipboard.native.js +23 -6
  188. package/lib/commonjs/ui/utils/clipboard.native.js.map +1 -1
  189. package/lib/commonjs/ui/utils/commonsStoreLinks.js.map +1 -1
  190. package/lib/commonjs/ui/utils/errorHandlers.js.map +1 -1
  191. package/lib/commonjs/ui/utils/fileManagement.js +24 -24
  192. package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
  193. package/lib/commonjs/ui/utils/iconNames.js.map +1 -1
  194. package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
  195. package/lib/commonjs/ui/utils/userUtils.js.map +1 -1
  196. package/lib/commonjs/utils/hookUtils.js +2 -4
  197. package/lib/commonjs/utils/hookUtils.js.map +1 -1
  198. package/lib/module/assets/assets/icons/OxyServices.tsx +42 -40
  199. package/lib/module/assets/assets/illustrations/HighFive.tsx +58 -31
  200. package/lib/module/assets/icons/OxyServices.js.map +1 -1
  201. package/lib/module/assets/illustrations/HighFive.js.map +1 -1
  202. package/lib/module/index.js.map +1 -1
  203. package/lib/module/notifications/deviceNotifications.js +8 -4
  204. package/lib/module/notifications/deviceNotifications.js.map +1 -1
  205. package/lib/module/ui/analytics/productAnalytics.js.map +1 -1
  206. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  207. package/lib/module/ui/client.js.map +1 -1
  208. package/lib/module/ui/components/BenefitList.js.map +1 -1
  209. package/lib/module/ui/components/BloomLocaleBridge.js.map +1 -1
  210. package/lib/module/ui/components/FollowButton.js.map +1 -1
  211. package/lib/module/ui/components/FollowTargetButton.js +1 -1
  212. package/lib/module/ui/components/FollowTargetButton.js.map +1 -1
  213. package/lib/module/ui/components/LanguageBridge.js.map +1 -1
  214. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  215. package/lib/module/ui/components/OxyAuthPrompt.js.map +1 -1
  216. package/lib/module/ui/components/OxyConsentScreen.js.map +1 -1
  217. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  218. package/lib/module/ui/components/OxySignInButton.js +1 -1
  219. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  220. package/lib/module/ui/components/OxySignInRequestSurface.js.map +1 -1
  221. package/lib/module/ui/components/PeableButton.js.map +1 -1
  222. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  223. package/lib/module/ui/components/ProfileSummaryCard.js +1 -1
  224. package/lib/module/ui/components/ProfileSummaryCard.js.map +1 -1
  225. package/lib/module/ui/components/RequireOxyAuth.js.map +1 -1
  226. package/lib/module/ui/components/SettingsIcon.js.map +1 -1
  227. package/lib/module/ui/components/SurfaceHeaderAction.js.map +1 -1
  228. package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
  229. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  230. package/lib/module/ui/components/authChooser/TroubleDisclosure.js.map +1 -1
  231. package/lib/module/ui/components/authChooser/requestSurfaces.js.map +1 -1
  232. package/lib/module/ui/components/authChooser/types.js.map +1 -1
  233. package/lib/module/ui/components/feedback/constants.js.map +1 -1
  234. package/lib/module/ui/components/feedback/useFeedbackForm.js.map +1 -1
  235. package/lib/module/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
  236. package/lib/module/ui/components/fileManagement/FileViewer.js +1 -1
  237. package/lib/module/ui/components/fileManagement/FileViewer.js.map +1 -1
  238. package/lib/module/ui/components/fileManagement/UploadPreview.js.map +1 -1
  239. package/lib/module/ui/components/followButtonAccessibility.js.map +1 -1
  240. package/lib/module/ui/components/followRules.js.map +1 -1
  241. package/lib/module/ui/components/icon/FAIRWalletIcon.js.map +1 -1
  242. package/lib/module/ui/components/logo/LogoText.js.map +1 -1
  243. package/lib/module/ui/components/modals/DeleteAccountModal.js.map +1 -1
  244. package/lib/module/ui/components/oauthNavigation.js.map +1 -1
  245. package/lib/module/ui/components/payment/PaymentDetailsStep.js.map +1 -1
  246. package/lib/module/ui/components/payment/PaymentMethodStep.js.map +1 -1
  247. package/lib/module/ui/components/payment/PaymentReviewStep.js.map +1 -1
  248. package/lib/module/ui/components/payment/PaymentSuccessStep.js.map +1 -1
  249. package/lib/module/ui/components/payment/PaymentSummaryStep.js +1 -1
  250. package/lib/module/ui/components/payment/PaymentSummaryStep.js.map +1 -1
  251. package/lib/module/ui/components/payment/constants.js.map +1 -1
  252. package/lib/module/ui/components/payment/paymentStyles.js.map +1 -1
  253. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  254. package/lib/module/ui/components/signIn/InlineCommonsQr.js.map +1 -1
  255. package/lib/module/ui/components/signIn/OxyAccountPicker.js.map +1 -1
  256. package/lib/module/ui/components/signIn/OxyAuthScreen.js.map +1 -1
  257. package/lib/module/ui/components/signIn/OxyAuthenticatorPanel.js.map +1 -1
  258. package/lib/module/ui/components/signIn/OxyDeleteAccountPanel.js.map +1 -1
  259. package/lib/module/ui/components/signIn/OxyLinkCommonsPanel.js.map +1 -1
  260. package/lib/module/ui/components/signIn/OxyPasswordPanel.js.map +1 -1
  261. package/lib/module/ui/components/signIn/OxySignInPanel.js.map +1 -1
  262. package/lib/module/ui/components/signIn/OxySignUpPanel.js.map +1 -1
  263. package/lib/module/ui/components/signIn/ReauthStep.js.map +1 -1
  264. package/lib/module/ui/components/signIn/accountFlowParts.js.map +1 -1
  265. package/lib/module/ui/components/signIn/artwork.js.map +1 -1
  266. package/lib/module/ui/components/signIn/signInFlowStore.js.map +1 -1
  267. package/lib/module/ui/components/styles/overlay.js.map +1 -1
  268. package/lib/module/ui/components/styles/shadow.js.map +1 -1
  269. package/lib/module/ui/components/surfaces/ActionSheetSurface.js.map +1 -1
  270. package/lib/module/ui/components/utils/hasTouchHandler.js.map +1 -1
  271. package/lib/module/ui/components/utils/roundLayoutSize.js.map +1 -1
  272. package/lib/module/ui/components/utils/splitStyles.js.map +1 -1
  273. package/lib/module/ui/constants/spacing.js.map +1 -1
  274. package/lib/module/ui/context/OxyContext.js.map +1 -1
  275. package/lib/module/ui/context/commitSessionFlow.js.map +1 -1
  276. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  277. package/lib/module/ui/context/isolatedOAuthSession.js.map +1 -1
  278. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  279. package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -1
  280. package/lib/module/ui/hooks/accountQueryPersistence.js.map +1 -1
  281. package/lib/module/ui/hooks/mutations/mutationFactory.js.map +1 -1
  282. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  283. package/lib/module/ui/hooks/persistedQueryCache.js.map +1 -1
  284. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
  285. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  286. package/lib/module/ui/hooks/queries/useFileQueries.js.map +1 -1
  287. package/lib/module/ui/hooks/queries/usePaymentQueries.js.map +1 -1
  288. package/lib/module/ui/hooks/queries/usePersonalPlans.js.map +1 -1
  289. package/lib/module/ui/hooks/queries/useSecurityQueries.js.map +1 -1
  290. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
  291. package/lib/module/ui/hooks/queries/userCache.js +38 -38
  292. package/lib/module/ui/hooks/queries/userCache.js.map +1 -1
  293. package/lib/module/ui/hooks/queryClient.js.map +1 -1
  294. package/lib/module/ui/hooks/useAssets.js.map +1 -1
  295. package/lib/module/ui/hooks/useAsyncAction.js.map +1 -1
  296. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  297. package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
  298. package/lib/module/ui/hooks/useDeviceSwitcher.js.map +1 -1
  299. package/lib/module/ui/hooks/useFileFiltering.js.map +1 -1
  300. package/lib/module/ui/hooks/useFollow.js.map +1 -1
  301. package/lib/module/ui/hooks/useI18n.js.map +1 -1
  302. package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
  303. package/lib/module/ui/hooks/useReduceMotion.js.map +1 -1
  304. package/lib/module/ui/hooks/useResolvedFileUrls.js.map +1 -1
  305. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  306. package/lib/module/ui/hooks/useSettingToggle.js.map +1 -1
  307. package/lib/module/ui/hooks/useStorage.js.map +1 -1
  308. package/lib/module/ui/hooks/useSurfaceFrameWidth.js.map +1 -1
  309. package/lib/module/ui/icons/Ionicons.js.map +1 -1
  310. package/lib/module/ui/icons/MaterialCommunityIcons.js.map +1 -1
  311. package/lib/module/ui/icons/decorativeIconSet.js.map +1 -1
  312. package/lib/module/ui/index.js.map +1 -1
  313. package/lib/module/ui/isFrontend.js.map +1 -1
  314. package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
  315. package/lib/module/ui/navigation/routes.js.map +1 -1
  316. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  317. package/lib/module/ui/oauth/applicationSessionLane.js.map +1 -1
  318. package/lib/module/ui/oauth/browserAuthTransport.js.map +1 -1
  319. package/lib/module/ui/oauth/browserBridge.js.map +1 -1
  320. package/lib/module/ui/oauth/completeOAuthCode.js.map +1 -1
  321. package/lib/module/ui/oauth/nativeAuthTransport.js.map +1 -1
  322. package/lib/module/ui/oauth/oauthHandshake.js.map +1 -1
  323. package/lib/module/ui/oauth/oauthPopup.js.map +1 -1
  324. package/lib/module/ui/oauth/oauthPopupMessages.js.map +1 -1
  325. package/lib/module/ui/runtime/createOxyRuntime.js.map +1 -1
  326. package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
  327. package/lib/module/ui/screens/AccountSecurityPanelScreen.js.map +1 -1
  328. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  329. package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -1
  330. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  331. package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
  332. package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -1
  333. package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
  334. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  335. package/lib/module/ui/screens/DomainsScreen.js.map +1 -1
  336. package/lib/module/ui/screens/EditProfileFieldScreen.js +1 -1
  337. package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
  338. package/lib/module/ui/screens/EditProfileScreen.js.map +1 -1
  339. package/lib/module/ui/screens/FileManagementScreen.js +2 -5
  340. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  341. package/lib/module/ui/screens/HelpSupportScreen.js.map +1 -1
  342. package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -1
  343. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  344. package/lib/module/ui/screens/LearnMoreUsernamesScreen.js.map +1 -1
  345. package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -1
  346. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  347. package/lib/module/ui/screens/NotificationsScreen.js.map +1 -1
  348. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  349. package/lib/module/ui/screens/PreferencesScreen.js.map +1 -1
  350. package/lib/module/ui/screens/PrivacySettingsScreen.js +3 -3
  351. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  352. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  353. package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -1
  354. package/lib/module/ui/screens/UserLinksScreen.js.map +1 -1
  355. package/lib/module/ui/screens/UserListScreen.js +1 -1
  356. package/lib/module/ui/screens/UserListScreen.js.map +1 -1
  357. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  358. package/lib/module/ui/screens/fileManagement/FileLibraryError.js.map +1 -1
  359. package/lib/module/ui/screens/fileManagement/FileListSection.js.map +1 -1
  360. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +7 -7
  361. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  362. package/lib/module/ui/screens/fileManagement/UploadBar.js.map +1 -1
  363. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  364. package/lib/module/ui/screens/fileManagement/photoGridLayout.js.map +1 -1
  365. package/lib/module/ui/screens/fileManagement/shared.js.map +1 -1
  366. package/lib/module/ui/screens/linkFormat.js.map +1 -1
  367. package/lib/module/ui/screens/trust/TrustAboutScreen.js.map +1 -1
  368. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  369. package/lib/module/ui/screens/trust/TrustFAQScreen.js.map +1 -1
  370. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  371. package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
  372. package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  373. package/lib/module/ui/server.js.map +1 -1
  374. package/lib/module/ui/session/authStore.js.map +1 -1
  375. package/lib/module/ui/session/backgroundSession.js.map +1 -1
  376. package/lib/module/ui/session/createSessionClient.js.map +1 -1
  377. package/lib/module/ui/session/deviceCredentialTracker.js.map +1 -1
  378. package/lib/module/ui/session/identityBinding.js.map +1 -1
  379. package/lib/module/ui/session/sharedDeviceCredentialStore.js.map +1 -1
  380. package/lib/module/ui/session/tokenLossRecovery.js.map +1 -1
  381. package/lib/module/ui/session/tokenTransport.js.map +1 -1
  382. package/lib/module/ui/stores/assetStore.js.map +1 -1
  383. package/lib/module/ui/stores/followStore.js.map +1 -1
  384. package/lib/module/ui/utils/accountDeletionHandoff.js.map +1 -1
  385. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  386. package/lib/module/ui/utils/clipboard.native.js +23 -6
  387. package/lib/module/ui/utils/clipboard.native.js.map +1 -1
  388. package/lib/module/ui/utils/commonsStoreLinks.js.map +1 -1
  389. package/lib/module/ui/utils/errorHandlers.js.map +1 -1
  390. package/lib/module/ui/utils/fileManagement.js +25 -25
  391. package/lib/module/ui/utils/fileManagement.js.map +1 -1
  392. package/lib/module/ui/utils/iconNames.js.map +1 -1
  393. package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
  394. package/lib/module/ui/utils/userUtils.js.map +1 -1
  395. package/lib/module/utils/hookUtils.js +2 -4
  396. package/lib/module/utils/hookUtils.js.map +1 -1
  397. package/lib/typescript/commonjs/assets/icons/OxyServices.d.ts.map +1 -1
  398. package/lib/typescript/commonjs/assets/illustrations/HighFive.d.ts.map +1 -1
  399. package/lib/typescript/commonjs/index.d.ts +4 -4
  400. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  401. package/lib/typescript/commonjs/notifications/deviceNotifications.d.ts +8 -4
  402. package/lib/typescript/commonjs/notifications/deviceNotifications.d.ts.map +1 -1
  403. package/lib/typescript/commonjs/ui/analytics/productAnalytics.d.ts.map +1 -1
  404. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  405. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  406. package/lib/typescript/commonjs/ui/components/BenefitList.d.ts.map +1 -1
  407. package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
  408. package/lib/typescript/commonjs/ui/components/FollowTargetButton.d.ts +1 -1
  409. package/lib/typescript/commonjs/ui/components/FollowTargetButton.d.ts.map +1 -1
  410. package/lib/typescript/commonjs/ui/components/LanguageBridge.d.ts +1 -1
  411. package/lib/typescript/commonjs/ui/components/LanguageBridge.d.ts.map +1 -1
  412. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  413. package/lib/typescript/commonjs/ui/components/OxyAuthPrompt.d.ts.map +1 -1
  414. package/lib/typescript/commonjs/ui/components/OxyConsentScreen.d.ts.map +1 -1
  415. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  416. package/lib/typescript/commonjs/ui/components/OxySignInButton.d.ts.map +1 -1
  417. package/lib/typescript/commonjs/ui/components/OxySignInRequestSurface.d.ts.map +1 -1
  418. package/lib/typescript/commonjs/ui/components/PeableButton.d.ts.map +1 -1
  419. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  420. package/lib/typescript/commonjs/ui/components/ProfileSummaryCard.d.ts.map +1 -1
  421. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts.map +1 -1
  422. package/lib/typescript/commonjs/ui/components/SettingsIcon.d.ts.map +1 -1
  423. package/lib/typescript/commonjs/ui/components/SurfaceHeaderAction.d.ts.map +1 -1
  424. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +1 -1
  425. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
  426. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  427. package/lib/typescript/commonjs/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -1
  428. package/lib/typescript/commonjs/ui/components/authChooser/requestSurfaces.d.ts.map +1 -1
  429. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
  430. package/lib/typescript/commonjs/ui/components/feedback/constants.d.ts.map +1 -1
  431. package/lib/typescript/commonjs/ui/components/feedback/types.d.ts.map +1 -1
  432. package/lib/typescript/commonjs/ui/components/feedback/useFeedbackForm.d.ts.map +1 -1
  433. package/lib/typescript/commonjs/ui/components/fileManagement/FileDetailsModal.d.ts.map +1 -1
  434. package/lib/typescript/commonjs/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
  435. package/lib/typescript/commonjs/ui/components/fileManagement/UploadPreview.d.ts.map +1 -1
  436. package/lib/typescript/commonjs/ui/components/followButtonAccessibility.d.ts.map +1 -1
  437. package/lib/typescript/commonjs/ui/components/followRules.d.ts.map +1 -1
  438. package/lib/typescript/commonjs/ui/components/icon/FAIRWalletIcon.d.ts.map +1 -1
  439. package/lib/typescript/commonjs/ui/components/logo/LogoText.d.ts.map +1 -1
  440. package/lib/typescript/commonjs/ui/components/modals/DeleteAccountModal.d.ts.map +1 -1
  441. package/lib/typescript/commonjs/ui/components/oauthNavigation.d.ts.map +1 -1
  442. package/lib/typescript/commonjs/ui/components/payment/PaymentDetailsStep.d.ts.map +1 -1
  443. package/lib/typescript/commonjs/ui/components/payment/PaymentMethodStep.d.ts.map +1 -1
  444. package/lib/typescript/commonjs/ui/components/payment/PaymentReviewStep.d.ts.map +1 -1
  445. package/lib/typescript/commonjs/ui/components/payment/PaymentSuccessStep.d.ts.map +1 -1
  446. package/lib/typescript/commonjs/ui/components/payment/PaymentSummaryStep.d.ts.map +1 -1
  447. package/lib/typescript/commonjs/ui/components/payment/constants.d.ts.map +1 -1
  448. package/lib/typescript/commonjs/ui/components/payment/paymentStyles.d.ts.map +1 -1
  449. package/lib/typescript/commonjs/ui/components/payment/types.d.ts +6 -6
  450. package/lib/typescript/commonjs/ui/components/payment/types.d.ts.map +1 -1
  451. package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  452. package/lib/typescript/commonjs/ui/components/signIn/InlineCommonsQr.d.ts.map +1 -1
  453. package/lib/typescript/commonjs/ui/components/signIn/OxyAccountPicker.d.ts.map +1 -1
  454. package/lib/typescript/commonjs/ui/components/signIn/OxyAuthScreen.d.ts.map +1 -1
  455. package/lib/typescript/commonjs/ui/components/signIn/OxyAuthenticatorPanel.d.ts.map +1 -1
  456. package/lib/typescript/commonjs/ui/components/signIn/OxyDeleteAccountPanel.d.ts.map +1 -1
  457. package/lib/typescript/commonjs/ui/components/signIn/OxyLinkCommonsPanel.d.ts.map +1 -1
  458. package/lib/typescript/commonjs/ui/components/signIn/OxyPasswordPanel.d.ts.map +1 -1
  459. package/lib/typescript/commonjs/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
  460. package/lib/typescript/commonjs/ui/components/signIn/OxySignUpPanel.d.ts.map +1 -1
  461. package/lib/typescript/commonjs/ui/components/signIn/ReauthStep.d.ts.map +1 -1
  462. package/lib/typescript/commonjs/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
  463. package/lib/typescript/commonjs/ui/components/signIn/artwork.d.ts.map +1 -1
  464. package/lib/typescript/commonjs/ui/components/signIn/signInFlowStore.d.ts.map +1 -1
  465. package/lib/typescript/commonjs/ui/components/styles/overlay.d.ts.map +1 -1
  466. package/lib/typescript/commonjs/ui/components/surfaces/ActionSheetSurface.d.ts.map +1 -1
  467. package/lib/typescript/commonjs/ui/components/utils/hasTouchHandler.d.ts.map +1 -1
  468. package/lib/typescript/commonjs/ui/components/utils/roundLayoutSize.d.ts.map +1 -1
  469. package/lib/typescript/commonjs/ui/components/utils/splitStyles.d.ts.map +1 -1
  470. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  471. package/lib/typescript/commonjs/ui/context/commitSessionFlow.d.ts.map +1 -1
  472. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  473. package/lib/typescript/commonjs/ui/context/isolatedOAuthSession.d.ts.map +1 -1
  474. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  475. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  476. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
  477. package/lib/typescript/commonjs/ui/hooks/accountQueryPersistence.d.ts.map +1 -1
  478. package/lib/typescript/commonjs/ui/hooks/mutations/mutationFactory.d.ts.map +1 -1
  479. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  480. package/lib/typescript/commonjs/ui/hooks/persistedQueryCache.d.ts.map +1 -1
  481. package/lib/typescript/commonjs/ui/hooks/queries/paymentTypes.d.ts +2 -2
  482. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  483. package/lib/typescript/commonjs/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  484. package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts.map +1 -1
  485. package/lib/typescript/commonjs/ui/hooks/queries/usePaymentQueries.d.ts.map +1 -1
  486. package/lib/typescript/commonjs/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -1
  487. package/lib/typescript/commonjs/ui/hooks/queries/useSecurityQueries.d.ts.map +1 -1
  488. package/lib/typescript/commonjs/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  489. package/lib/typescript/commonjs/ui/hooks/queries/userCache.d.ts +5 -5
  490. package/lib/typescript/commonjs/ui/hooks/queries/userCache.d.ts.map +1 -1
  491. package/lib/typescript/commonjs/ui/hooks/queryClient.d.ts.map +1 -1
  492. package/lib/typescript/commonjs/ui/hooks/useAssets.d.ts.map +1 -1
  493. package/lib/typescript/commonjs/ui/hooks/useAsyncAction.d.ts.map +1 -1
  494. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  495. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  496. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts.map +1 -1
  497. package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts.map +1 -1
  498. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts.map +1 -1
  499. package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
  500. package/lib/typescript/commonjs/ui/hooks/useResolvedFileUrls.d.ts.map +1 -1
  501. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  502. package/lib/typescript/commonjs/ui/hooks/useSettingToggle.d.ts.map +1 -1
  503. package/lib/typescript/commonjs/ui/hooks/useStorage.d.ts +1 -1
  504. package/lib/typescript/commonjs/ui/hooks/useStorage.d.ts.map +1 -1
  505. package/lib/typescript/commonjs/ui/hooks/useSurfaceFrameWidth.d.ts.map +1 -1
  506. package/lib/typescript/commonjs/ui/icons/Ionicons.d.ts.map +1 -1
  507. package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts.map +1 -1
  508. package/lib/typescript/commonjs/ui/icons/decorativeIconSet.d.ts.map +1 -1
  509. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  510. package/lib/typescript/commonjs/ui/isFrontend.d.ts.map +1 -1
  511. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  512. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  513. package/lib/typescript/commonjs/ui/oauth/applicationSessionLane.d.ts.map +1 -1
  514. package/lib/typescript/commonjs/ui/oauth/browserAuthTransport.d.ts.map +1 -1
  515. package/lib/typescript/commonjs/ui/oauth/browserBridge.d.ts.map +1 -1
  516. package/lib/typescript/commonjs/ui/oauth/completeOAuthCode.d.ts.map +1 -1
  517. package/lib/typescript/commonjs/ui/oauth/nativeAuthTransport.d.ts.map +1 -1
  518. package/lib/typescript/commonjs/ui/oauth/oauthHandshake.d.ts.map +1 -1
  519. package/lib/typescript/commonjs/ui/oauth/oauthPopup.d.ts.map +1 -1
  520. package/lib/typescript/commonjs/ui/oauth/oauthPopupMessages.d.ts.map +1 -1
  521. package/lib/typescript/commonjs/ui/oauth/types.d.ts.map +1 -1
  522. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts.map +1 -1
  523. package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  524. package/lib/typescript/commonjs/ui/screens/AccountSecurityPanelScreen.d.ts.map +1 -1
  525. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  526. package/lib/typescript/commonjs/ui/screens/AccountVerificationScreen.d.ts.map +1 -1
  527. package/lib/typescript/commonjs/ui/screens/AppInfoScreen.d.ts.map +1 -1
  528. package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  529. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
  530. package/lib/typescript/commonjs/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  531. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  532. package/lib/typescript/commonjs/ui/screens/DomainsScreen.d.ts.map +1 -1
  533. package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  534. package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -1
  535. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  536. package/lib/typescript/commonjs/ui/screens/HelpSupportScreen.d.ts.map +1 -1
  537. package/lib/typescript/commonjs/ui/screens/HistoryViewScreen.d.ts.map +1 -1
  538. package/lib/typescript/commonjs/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  539. package/lib/typescript/commonjs/ui/screens/LearnMoreUsernamesScreen.d.ts.map +1 -1
  540. package/lib/typescript/commonjs/ui/screens/LegalDocumentsScreen.d.ts.map +1 -1
  541. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  542. package/lib/typescript/commonjs/ui/screens/NotificationsScreen.d.ts.map +1 -1
  543. package/lib/typescript/commonjs/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  544. package/lib/typescript/commonjs/ui/screens/PreferencesScreen.d.ts.map +1 -1
  545. package/lib/typescript/commonjs/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  546. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  547. package/lib/typescript/commonjs/ui/screens/SearchSettingsScreen.d.ts.map +1 -1
  548. package/lib/typescript/commonjs/ui/screens/UserLinksScreen.d.ts.map +1 -1
  549. package/lib/typescript/commonjs/ui/screens/UserListScreen.d.ts.map +1 -1
  550. package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  551. package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -1
  552. package/lib/typescript/commonjs/ui/screens/fileManagement/FileListSection.d.ts.map +1 -1
  553. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  554. package/lib/typescript/commonjs/ui/screens/fileManagement/UploadBar.d.ts.map +1 -1
  555. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  556. package/lib/typescript/commonjs/ui/screens/fileManagement/photoGridLayout.d.ts.map +1 -1
  557. package/lib/typescript/commonjs/ui/screens/fileManagement/shared.d.ts.map +1 -1
  558. package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts.map +1 -1
  559. package/lib/typescript/commonjs/ui/screens/trust/TrustAboutScreen.d.ts.map +1 -1
  560. package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -1
  561. package/lib/typescript/commonjs/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -1
  562. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  563. package/lib/typescript/commonjs/ui/screens/trust/TrustRewardsScreen.d.ts.map +1 -1
  564. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  565. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
  566. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
  567. package/lib/typescript/commonjs/ui/session/backgroundSession.d.ts.map +1 -1
  568. package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
  569. package/lib/typescript/commonjs/ui/session/deviceCredentialTracker.d.ts.map +1 -1
  570. package/lib/typescript/commonjs/ui/session/identityBinding.d.ts.map +1 -1
  571. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -1
  572. package/lib/typescript/commonjs/ui/session/tokenLossRecovery.d.ts.map +1 -1
  573. package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
  574. package/lib/typescript/commonjs/ui/stores/assetStore.d.ts.map +1 -1
  575. package/lib/typescript/commonjs/ui/stores/followStore.d.ts.map +1 -1
  576. package/lib/typescript/commonjs/ui/types/fileManagement.d.ts.map +1 -1
  577. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  578. package/lib/typescript/commonjs/ui/types/surfaceScreen.d.ts.map +1 -1
  579. package/lib/typescript/commonjs/ui/utils/accountDeletionHandoff.d.ts.map +1 -1
  580. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  581. package/lib/typescript/commonjs/ui/utils/clipboard.native.d.ts +16 -5
  582. package/lib/typescript/commonjs/ui/utils/clipboard.native.d.ts.map +1 -1
  583. package/lib/typescript/commonjs/ui/utils/commonsStoreLinks.d.ts.map +1 -1
  584. package/lib/typescript/commonjs/ui/utils/errorHandlers.d.ts +1 -1
  585. package/lib/typescript/commonjs/ui/utils/errorHandlers.d.ts.map +1 -1
  586. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +4 -4
  587. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
  588. package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts +1 -1
  589. package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts.map +1 -1
  590. package/lib/typescript/commonjs/ui/utils/userUtils.d.ts.map +1 -1
  591. package/lib/typescript/commonjs/utils/hookUtils.d.ts.map +1 -1
  592. package/lib/typescript/module/assets/icons/OxyServices.d.ts.map +1 -1
  593. package/lib/typescript/module/assets/illustrations/HighFive.d.ts.map +1 -1
  594. package/lib/typescript/module/index.d.ts +4 -4
  595. package/lib/typescript/module/index.d.ts.map +1 -1
  596. package/lib/typescript/module/notifications/deviceNotifications.d.ts +8 -4
  597. package/lib/typescript/module/notifications/deviceNotifications.d.ts.map +1 -1
  598. package/lib/typescript/module/ui/analytics/productAnalytics.d.ts.map +1 -1
  599. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  600. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  601. package/lib/typescript/module/ui/components/BenefitList.d.ts.map +1 -1
  602. package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
  603. package/lib/typescript/module/ui/components/FollowTargetButton.d.ts +1 -1
  604. package/lib/typescript/module/ui/components/FollowTargetButton.d.ts.map +1 -1
  605. package/lib/typescript/module/ui/components/LanguageBridge.d.ts +1 -1
  606. package/lib/typescript/module/ui/components/LanguageBridge.d.ts.map +1 -1
  607. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  608. package/lib/typescript/module/ui/components/OxyAuthPrompt.d.ts.map +1 -1
  609. package/lib/typescript/module/ui/components/OxyConsentScreen.d.ts.map +1 -1
  610. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  611. package/lib/typescript/module/ui/components/OxySignInButton.d.ts.map +1 -1
  612. package/lib/typescript/module/ui/components/OxySignInRequestSurface.d.ts.map +1 -1
  613. package/lib/typescript/module/ui/components/PeableButton.d.ts.map +1 -1
  614. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  615. package/lib/typescript/module/ui/components/ProfileSummaryCard.d.ts.map +1 -1
  616. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts.map +1 -1
  617. package/lib/typescript/module/ui/components/SettingsIcon.d.ts.map +1 -1
  618. package/lib/typescript/module/ui/components/SurfaceHeaderAction.d.ts.map +1 -1
  619. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +1 -1
  620. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
  621. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  622. package/lib/typescript/module/ui/components/authChooser/TroubleDisclosure.d.ts.map +1 -1
  623. package/lib/typescript/module/ui/components/authChooser/requestSurfaces.d.ts.map +1 -1
  624. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
  625. package/lib/typescript/module/ui/components/feedback/constants.d.ts.map +1 -1
  626. package/lib/typescript/module/ui/components/feedback/types.d.ts.map +1 -1
  627. package/lib/typescript/module/ui/components/feedback/useFeedbackForm.d.ts.map +1 -1
  628. package/lib/typescript/module/ui/components/fileManagement/FileDetailsModal.d.ts.map +1 -1
  629. package/lib/typescript/module/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
  630. package/lib/typescript/module/ui/components/fileManagement/UploadPreview.d.ts.map +1 -1
  631. package/lib/typescript/module/ui/components/followButtonAccessibility.d.ts.map +1 -1
  632. package/lib/typescript/module/ui/components/followRules.d.ts.map +1 -1
  633. package/lib/typescript/module/ui/components/icon/FAIRWalletIcon.d.ts.map +1 -1
  634. package/lib/typescript/module/ui/components/logo/LogoText.d.ts.map +1 -1
  635. package/lib/typescript/module/ui/components/modals/DeleteAccountModal.d.ts.map +1 -1
  636. package/lib/typescript/module/ui/components/oauthNavigation.d.ts.map +1 -1
  637. package/lib/typescript/module/ui/components/payment/PaymentDetailsStep.d.ts.map +1 -1
  638. package/lib/typescript/module/ui/components/payment/PaymentMethodStep.d.ts.map +1 -1
  639. package/lib/typescript/module/ui/components/payment/PaymentReviewStep.d.ts.map +1 -1
  640. package/lib/typescript/module/ui/components/payment/PaymentSuccessStep.d.ts.map +1 -1
  641. package/lib/typescript/module/ui/components/payment/PaymentSummaryStep.d.ts.map +1 -1
  642. package/lib/typescript/module/ui/components/payment/constants.d.ts.map +1 -1
  643. package/lib/typescript/module/ui/components/payment/paymentStyles.d.ts.map +1 -1
  644. package/lib/typescript/module/ui/components/payment/types.d.ts +6 -6
  645. package/lib/typescript/module/ui/components/payment/types.d.ts.map +1 -1
  646. package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  647. package/lib/typescript/module/ui/components/signIn/InlineCommonsQr.d.ts.map +1 -1
  648. package/lib/typescript/module/ui/components/signIn/OxyAccountPicker.d.ts.map +1 -1
  649. package/lib/typescript/module/ui/components/signIn/OxyAuthScreen.d.ts.map +1 -1
  650. package/lib/typescript/module/ui/components/signIn/OxyAuthenticatorPanel.d.ts.map +1 -1
  651. package/lib/typescript/module/ui/components/signIn/OxyDeleteAccountPanel.d.ts.map +1 -1
  652. package/lib/typescript/module/ui/components/signIn/OxyLinkCommonsPanel.d.ts.map +1 -1
  653. package/lib/typescript/module/ui/components/signIn/OxyPasswordPanel.d.ts.map +1 -1
  654. package/lib/typescript/module/ui/components/signIn/OxySignInPanel.d.ts.map +1 -1
  655. package/lib/typescript/module/ui/components/signIn/OxySignUpPanel.d.ts.map +1 -1
  656. package/lib/typescript/module/ui/components/signIn/ReauthStep.d.ts.map +1 -1
  657. package/lib/typescript/module/ui/components/signIn/accountFlowParts.d.ts.map +1 -1
  658. package/lib/typescript/module/ui/components/signIn/artwork.d.ts.map +1 -1
  659. package/lib/typescript/module/ui/components/signIn/signInFlowStore.d.ts.map +1 -1
  660. package/lib/typescript/module/ui/components/styles/overlay.d.ts.map +1 -1
  661. package/lib/typescript/module/ui/components/surfaces/ActionSheetSurface.d.ts.map +1 -1
  662. package/lib/typescript/module/ui/components/utils/hasTouchHandler.d.ts.map +1 -1
  663. package/lib/typescript/module/ui/components/utils/roundLayoutSize.d.ts.map +1 -1
  664. package/lib/typescript/module/ui/components/utils/splitStyles.d.ts.map +1 -1
  665. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  666. package/lib/typescript/module/ui/context/commitSessionFlow.d.ts.map +1 -1
  667. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  668. package/lib/typescript/module/ui/context/isolatedOAuthSession.d.ts.map +1 -1
  669. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  670. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  671. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -1
  672. package/lib/typescript/module/ui/hooks/accountQueryPersistence.d.ts.map +1 -1
  673. package/lib/typescript/module/ui/hooks/mutations/mutationFactory.d.ts.map +1 -1
  674. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  675. package/lib/typescript/module/ui/hooks/persistedQueryCache.d.ts.map +1 -1
  676. package/lib/typescript/module/ui/hooks/queries/paymentTypes.d.ts +2 -2
  677. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  678. package/lib/typescript/module/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  679. package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts.map +1 -1
  680. package/lib/typescript/module/ui/hooks/queries/usePaymentQueries.d.ts.map +1 -1
  681. package/lib/typescript/module/ui/hooks/queries/usePersonalPlans.d.ts.map +1 -1
  682. package/lib/typescript/module/ui/hooks/queries/useSecurityQueries.d.ts.map +1 -1
  683. package/lib/typescript/module/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  684. package/lib/typescript/module/ui/hooks/queries/userCache.d.ts +5 -5
  685. package/lib/typescript/module/ui/hooks/queries/userCache.d.ts.map +1 -1
  686. package/lib/typescript/module/ui/hooks/queryClient.d.ts.map +1 -1
  687. package/lib/typescript/module/ui/hooks/useAssets.d.ts.map +1 -1
  688. package/lib/typescript/module/ui/hooks/useAsyncAction.d.ts.map +1 -1
  689. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  690. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  691. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts.map +1 -1
  692. package/lib/typescript/module/ui/hooks/useFollow.d.ts.map +1 -1
  693. package/lib/typescript/module/ui/hooks/useI18n.d.ts.map +1 -1
  694. package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
  695. package/lib/typescript/module/ui/hooks/useResolvedFileUrls.d.ts.map +1 -1
  696. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  697. package/lib/typescript/module/ui/hooks/useSettingToggle.d.ts.map +1 -1
  698. package/lib/typescript/module/ui/hooks/useStorage.d.ts +1 -1
  699. package/lib/typescript/module/ui/hooks/useStorage.d.ts.map +1 -1
  700. package/lib/typescript/module/ui/hooks/useSurfaceFrameWidth.d.ts.map +1 -1
  701. package/lib/typescript/module/ui/icons/Ionicons.d.ts.map +1 -1
  702. package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts.map +1 -1
  703. package/lib/typescript/module/ui/icons/decorativeIconSet.d.ts.map +1 -1
  704. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  705. package/lib/typescript/module/ui/isFrontend.d.ts.map +1 -1
  706. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  707. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  708. package/lib/typescript/module/ui/oauth/applicationSessionLane.d.ts.map +1 -1
  709. package/lib/typescript/module/ui/oauth/browserAuthTransport.d.ts.map +1 -1
  710. package/lib/typescript/module/ui/oauth/browserBridge.d.ts.map +1 -1
  711. package/lib/typescript/module/ui/oauth/completeOAuthCode.d.ts.map +1 -1
  712. package/lib/typescript/module/ui/oauth/nativeAuthTransport.d.ts.map +1 -1
  713. package/lib/typescript/module/ui/oauth/oauthHandshake.d.ts.map +1 -1
  714. package/lib/typescript/module/ui/oauth/oauthPopup.d.ts.map +1 -1
  715. package/lib/typescript/module/ui/oauth/oauthPopupMessages.d.ts.map +1 -1
  716. package/lib/typescript/module/ui/oauth/types.d.ts.map +1 -1
  717. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts.map +1 -1
  718. package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  719. package/lib/typescript/module/ui/screens/AccountSecurityPanelScreen.d.ts.map +1 -1
  720. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  721. package/lib/typescript/module/ui/screens/AccountVerificationScreen.d.ts.map +1 -1
  722. package/lib/typescript/module/ui/screens/AppInfoScreen.d.ts.map +1 -1
  723. package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  724. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
  725. package/lib/typescript/module/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  726. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  727. package/lib/typescript/module/ui/screens/DomainsScreen.d.ts.map +1 -1
  728. package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  729. package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -1
  730. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  731. package/lib/typescript/module/ui/screens/HelpSupportScreen.d.ts.map +1 -1
  732. package/lib/typescript/module/ui/screens/HistoryViewScreen.d.ts.map +1 -1
  733. package/lib/typescript/module/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  734. package/lib/typescript/module/ui/screens/LearnMoreUsernamesScreen.d.ts.map +1 -1
  735. package/lib/typescript/module/ui/screens/LegalDocumentsScreen.d.ts.map +1 -1
  736. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  737. package/lib/typescript/module/ui/screens/NotificationsScreen.d.ts.map +1 -1
  738. package/lib/typescript/module/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  739. package/lib/typescript/module/ui/screens/PreferencesScreen.d.ts.map +1 -1
  740. package/lib/typescript/module/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  741. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  742. package/lib/typescript/module/ui/screens/SearchSettingsScreen.d.ts.map +1 -1
  743. package/lib/typescript/module/ui/screens/UserLinksScreen.d.ts.map +1 -1
  744. package/lib/typescript/module/ui/screens/UserListScreen.d.ts.map +1 -1
  745. package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  746. package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -1
  747. package/lib/typescript/module/ui/screens/fileManagement/FileListSection.d.ts.map +1 -1
  748. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  749. package/lib/typescript/module/ui/screens/fileManagement/UploadBar.d.ts.map +1 -1
  750. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  751. package/lib/typescript/module/ui/screens/fileManagement/photoGridLayout.d.ts.map +1 -1
  752. package/lib/typescript/module/ui/screens/fileManagement/shared.d.ts.map +1 -1
  753. package/lib/typescript/module/ui/screens/linkFormat.d.ts.map +1 -1
  754. package/lib/typescript/module/ui/screens/trust/TrustAboutScreen.d.ts.map +1 -1
  755. package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -1
  756. package/lib/typescript/module/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -1
  757. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  758. package/lib/typescript/module/ui/screens/trust/TrustRewardsScreen.d.ts.map +1 -1
  759. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  760. package/lib/typescript/module/ui/server.d.ts.map +1 -1
  761. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
  762. package/lib/typescript/module/ui/session/backgroundSession.d.ts.map +1 -1
  763. package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
  764. package/lib/typescript/module/ui/session/deviceCredentialTracker.d.ts.map +1 -1
  765. package/lib/typescript/module/ui/session/identityBinding.d.ts.map +1 -1
  766. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -1
  767. package/lib/typescript/module/ui/session/tokenLossRecovery.d.ts.map +1 -1
  768. package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
  769. package/lib/typescript/module/ui/stores/assetStore.d.ts.map +1 -1
  770. package/lib/typescript/module/ui/stores/followStore.d.ts.map +1 -1
  771. package/lib/typescript/module/ui/types/fileManagement.d.ts.map +1 -1
  772. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  773. package/lib/typescript/module/ui/types/surfaceScreen.d.ts.map +1 -1
  774. package/lib/typescript/module/ui/utils/accountDeletionHandoff.d.ts.map +1 -1
  775. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  776. package/lib/typescript/module/ui/utils/clipboard.native.d.ts +16 -5
  777. package/lib/typescript/module/ui/utils/clipboard.native.d.ts.map +1 -1
  778. package/lib/typescript/module/ui/utils/commonsStoreLinks.d.ts.map +1 -1
  779. package/lib/typescript/module/ui/utils/errorHandlers.d.ts +1 -1
  780. package/lib/typescript/module/ui/utils/errorHandlers.d.ts.map +1 -1
  781. package/lib/typescript/module/ui/utils/fileManagement.d.ts +4 -4
  782. package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
  783. package/lib/typescript/module/ui/utils/sessionHelpers.d.ts +1 -1
  784. package/lib/typescript/module/ui/utils/sessionHelpers.d.ts.map +1 -1
  785. package/lib/typescript/module/ui/utils/userUtils.d.ts.map +1 -1
  786. package/lib/typescript/module/utils/hookUtils.d.ts.map +1 -1
  787. package/lib/typescript/types/expo-crypto.d.ts +2 -4
  788. package/lib/typescript/types/expo-document-picker.d.ts +27 -28
  789. package/lib/typescript/types/expo-image-picker.d.ts +27 -27
  790. package/lib/typescript/types/expo-secure-store.d.ts +11 -4
  791. package/lib/typescript/types/express.d.ts +22 -7
  792. package/package.json +4 -5
  793. package/src/assets/icons/OxyServices.tsx +42 -40
  794. package/src/assets/illustrations/HighFive.tsx +58 -31
  795. package/src/index.ts +99 -86
  796. package/src/notifications/deviceNotifications.ts +9 -7
  797. package/src/types/expo-crypto.d.ts +2 -4
  798. package/src/types/expo-document-picker.d.ts +27 -28
  799. package/src/types/expo-image-picker.d.ts +27 -27
  800. package/src/types/expo-secure-store.d.ts +11 -4
  801. package/src/types/express.d.ts +22 -7
  802. package/src/ui/analytics/productAnalytics.tsx +5 -5
  803. package/src/ui/boot/__tests__/coldBootNoIdpRedirect.test.ts +91 -29
  804. package/src/ui/boot/runProviderColdBoot.ts +12 -3
  805. package/src/ui/client.ts +31 -31
  806. package/src/ui/components/BenefitList.tsx +78 -77
  807. package/src/ui/components/BloomLocaleBridge.tsx +3 -3
  808. package/src/ui/components/FollowButton.tsx +88 -84
  809. package/src/ui/components/FollowTargetButton.tsx +15 -6
  810. package/src/ui/components/LanguageBridge.tsx +24 -19
  811. package/src/ui/components/OxyAuthChooser.tsx +17 -3
  812. package/src/ui/components/OxyAuthPrompt.tsx +79 -98
  813. package/src/ui/components/OxyConsentScreen.tsx +51 -12
  814. package/src/ui/components/OxyProvider.tsx +189 -182
  815. package/src/ui/components/OxySignInButton.tsx +415 -405
  816. package/src/ui/components/OxySignInRequestSurface.tsx +7 -1
  817. package/src/ui/components/PeableButton.tsx +57 -49
  818. package/src/ui/components/ProfileButton.tsx +158 -109
  819. package/src/ui/components/ProfileSummaryCard.tsx +64 -64
  820. package/src/ui/components/RequireOxyAuth.tsx +15 -3
  821. package/src/ui/components/SettingsIcon.tsx +24 -19
  822. package/src/ui/components/SurfaceHeaderAction.tsx +6 -1
  823. package/src/ui/components/SurfaceScreen.tsx +21 -16
  824. package/src/ui/components/__tests__/BloomLocaleBridge.test.tsx +1 -1
  825. package/src/ui/components/__tests__/LanguageBridge.test.tsx +15 -5
  826. package/src/ui/components/__tests__/OxyConsentScreen.test.tsx +21 -11
  827. package/src/ui/components/__tests__/followButtonAccessibility.test.tsx +42 -15
  828. package/src/ui/components/__tests__/followTargetButton.test.ts +23 -20
  829. package/src/ui/components/authChooser/AccountsMenuView.tsx +70 -56
  830. package/src/ui/components/authChooser/TroubleDisclosure.tsx +6 -1
  831. package/src/ui/components/authChooser/requestSurfaces.tsx +3 -1
  832. package/src/ui/components/authChooser/types.ts +6 -1
  833. package/src/ui/components/feedback/constants.ts +36 -12
  834. package/src/ui/components/feedback/types.ts +30 -30
  835. package/src/ui/components/feedback/useFeedbackForm.ts +38 -35
  836. package/src/ui/components/fileManagement/FileDetailsModal.tsx +86 -92
  837. package/src/ui/components/fileManagement/FileViewer.tsx +362 -338
  838. package/src/ui/components/fileManagement/UploadPreview.tsx +134 -127
  839. package/src/ui/components/followButtonAccessibility.ts +6 -2
  840. package/src/ui/components/followRules.ts +6 -2
  841. package/src/ui/components/icon/FAIRWalletIcon.tsx +47 -35
  842. package/src/ui/components/logo/LogoText.tsx +6 -1
  843. package/src/ui/components/modals/DeleteAccountModal.tsx +89 -83
  844. package/src/ui/components/oauthNavigation.ts +54 -56
  845. package/src/ui/components/payment/PaymentDetailsStep.tsx +269 -201
  846. package/src/ui/components/payment/PaymentMethodStep.tsx +88 -68
  847. package/src/ui/components/payment/PaymentReviewStep.tsx +119 -102
  848. package/src/ui/components/payment/PaymentSuccessStep.tsx +48 -48
  849. package/src/ui/components/payment/PaymentSummaryStep.tsx +167 -124
  850. package/src/ui/components/payment/constants.ts +38 -23
  851. package/src/ui/components/payment/paymentStyles.ts +252 -251
  852. package/src/ui/components/payment/types.ts +24 -24
  853. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +207 -183
  854. package/src/ui/components/photogrid/__tests__/chunkPhotos.test.ts +32 -33
  855. package/src/ui/components/signIn/InlineCommonsQr.tsx +14 -6
  856. package/src/ui/components/signIn/OxyAccountPicker.tsx +19 -4
  857. package/src/ui/components/signIn/OxyAuthScreen.tsx +21 -11
  858. package/src/ui/components/signIn/OxyAuthenticatorPanel.tsx +47 -13
  859. package/src/ui/components/signIn/OxyDeleteAccountPanel.tsx +14 -4
  860. package/src/ui/components/signIn/OxyLinkCommonsPanel.tsx +69 -15
  861. package/src/ui/components/signIn/OxyPasswordPanel.tsx +19 -4
  862. package/src/ui/components/signIn/OxySignInPanel.tsx +74 -21
  863. package/src/ui/components/signIn/OxySignUpPanel.tsx +60 -12
  864. package/src/ui/components/signIn/ReauthStep.tsx +26 -5
  865. package/src/ui/components/signIn/accountFlowParts.tsx +45 -16
  866. package/src/ui/components/signIn/artwork.ts +3 -1
  867. package/src/ui/components/signIn/signInFlowStore.ts +5 -1
  868. package/src/ui/components/styles/overlay.tsx +10 -11
  869. package/src/ui/components/styles/shadow.tsx +59 -59
  870. package/src/ui/components/surfaces/ActionSheetSurface.tsx +51 -61
  871. package/src/ui/components/utils/hasTouchHandler.tsx +3 -13
  872. package/src/ui/components/utils/roundLayoutSize.ts +1 -2
  873. package/src/ui/components/utils/splitStyles.ts +2 -5
  874. package/src/ui/constants/spacing.ts +8 -9
  875. package/src/ui/context/OxyContext.tsx +324 -140
  876. package/src/ui/context/__tests__/commitSessionFlow.test.ts +8 -3
  877. package/src/ui/context/__tests__/isolatedOAuthSession.test.ts +11 -3
  878. package/src/ui/context/commitSessionFlow.ts +15 -3
  879. package/src/ui/context/hooks/useAuthOperations.ts +54 -16
  880. package/src/ui/context/isolatedOAuthSession.ts +4 -1
  881. package/src/ui/context/oxyContextTypes.ts +26 -9
  882. package/src/ui/context/useOxyAccountGraph.ts +26 -4
  883. package/src/ui/hooks/accountDialogSnapshot.ts +3 -1
  884. package/src/ui/hooks/accountQueryPersistence.ts +16 -5
  885. package/src/ui/hooks/mutations/mutationFactory.ts +11 -6
  886. package/src/ui/hooks/mutations/useAccountMutations.ts +77 -54
  887. package/src/ui/hooks/mutations/useServicesMutations.ts +10 -11
  888. package/src/ui/hooks/persistedQueryCache.ts +10 -2
  889. package/src/ui/hooks/queries/__tests__/userCache.test.ts +72 -17
  890. package/src/ui/hooks/queries/__tests__/userCacheClear.test.ts +18 -23
  891. package/src/ui/hooks/queries/paymentTypes.ts +2 -2
  892. package/src/ui/hooks/queries/queryKeys.ts +5 -5
  893. package/src/ui/hooks/queries/useAccountQueries.ts +12 -17
  894. package/src/ui/hooks/queries/useFileQueries.ts +88 -92
  895. package/src/ui/hooks/queries/usePaymentQueries.ts +13 -26
  896. package/src/ui/hooks/queries/usePersonalPlans.ts +14 -6
  897. package/src/ui/hooks/queries/useSecurityQueries.ts +16 -29
  898. package/src/ui/hooks/queries/useServicesQueries.ts +17 -23
  899. package/src/ui/hooks/queries/userCache.ts +249 -270
  900. package/src/ui/hooks/queryClient.ts +9 -5
  901. package/src/ui/hooks/useAssets.ts +166 -160
  902. package/src/ui/hooks/useAsyncAction.ts +92 -91
  903. package/src/ui/hooks/useAuth.ts +45 -42
  904. package/src/ui/hooks/useAvatarPicker.ts +2 -5
  905. package/src/ui/hooks/useDeviceManagement.ts +0 -2
  906. package/src/ui/hooks/useDeviceSwitcher.ts +73 -68
  907. package/src/ui/hooks/useFileFiltering.ts +2 -2
  908. package/src/ui/hooks/useFollow.ts +139 -67
  909. package/src/ui/hooks/useFollowTarget.ts +11 -11
  910. package/src/ui/hooks/useI18n.ts +7 -2
  911. package/src/ui/hooks/useProfileEditing.ts +167 -155
  912. package/src/ui/hooks/useQueryClient.ts +2 -3
  913. package/src/ui/hooks/useReduceMotion.ts +21 -21
  914. package/src/ui/hooks/useResolvedFileUrls.ts +5 -1
  915. package/src/ui/hooks/useSessionManagement.ts +3 -1
  916. package/src/ui/hooks/useSettingToggle.ts +122 -114
  917. package/src/ui/hooks/useStorage.ts +1 -4
  918. package/src/ui/hooks/useSurfaceFrameWidth.ts +3 -1
  919. package/src/ui/icons/Ionicons.ts +1 -5
  920. package/src/ui/icons/MaterialCommunityIcons.ts +1 -5
  921. package/src/ui/icons/__tests__/decorativeIconSet.test.tsx +3 -1
  922. package/src/ui/icons/decorativeIconSet.ts +13 -2
  923. package/src/ui/index.ts +4 -4
  924. package/src/ui/isFrontend.ts +3 -2
  925. package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +83 -83
  926. package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +8 -2
  927. package/src/ui/navigation/bottomSheetManager.ts +1 -1
  928. package/src/ui/navigation/routes.ts +90 -88
  929. package/src/ui/navigation/surfaces.ts +7 -6
  930. package/src/ui/oauth/__tests__/browserAuthTransport.test.ts +18 -11
  931. package/src/ui/oauth/__tests__/browserBridge.test.ts +108 -21
  932. package/src/ui/oauth/__tests__/completeOAuthCode.test.ts +9 -6
  933. package/src/ui/oauth/__tests__/explicitOAuthConsent.test.ts +222 -241
  934. package/src/ui/oauth/__tests__/nativeAuthTransport.test.ts +118 -35
  935. package/src/ui/oauth/__tests__/oauthBearerCommit.http.test.ts +60 -18
  936. package/src/ui/oauth/__tests__/oauthPopup.test.ts +2 -7
  937. package/src/ui/oauth/__tests__/oauthPopupMessages.test.ts +9 -7
  938. package/src/ui/oauth/__tests__/sharedCompletionPath.test.ts +4 -5
  939. package/src/ui/oauth/applicationSessionLane.ts +12 -3
  940. package/src/ui/oauth/browserAuthTransport.ts +15 -7
  941. package/src/ui/oauth/browserBridge.ts +22 -5
  942. package/src/ui/oauth/completeOAuthCode.ts +9 -6
  943. package/src/ui/oauth/nativeAuthTransport.ts +73 -20
  944. package/src/ui/oauth/oauthHandshake.ts +6 -1
  945. package/src/ui/oauth/oauthPopup.ts +6 -4
  946. package/src/ui/oauth/oauthPopupMessages.ts +3 -4
  947. package/src/ui/oauth/types.ts +1 -3
  948. package/src/ui/runtime/createOxyRuntime.ts +21 -4
  949. package/src/ui/screens/AccountMembersScreen.tsx +274 -204
  950. package/src/ui/screens/AccountSecurityPanelScreen.tsx +3 -1
  951. package/src/ui/screens/AccountSettingsScreen.tsx +133 -113
  952. package/src/ui/screens/AccountVerificationScreen.tsx +176 -161
  953. package/src/ui/screens/AppInfoScreen.tsx +360 -304
  954. package/src/ui/screens/AvatarCropScreen.tsx +977 -988
  955. package/src/ui/screens/ChangeAvatarScreen.tsx +257 -253
  956. package/src/ui/screens/ConnectedAppsScreen.tsx +117 -130
  957. package/src/ui/screens/CreateAccountScreen.tsx +173 -129
  958. package/src/ui/screens/DomainsScreen.tsx +157 -140
  959. package/src/ui/screens/EditProfileFieldScreen.tsx +693 -663
  960. package/src/ui/screens/EditProfileScreen.tsx +106 -100
  961. package/src/ui/screens/FileManagementScreen.tsx +1505 -1162
  962. package/src/ui/screens/HelpSupportScreen.tsx +88 -75
  963. package/src/ui/screens/HistoryViewScreen.tsx +143 -102
  964. package/src/ui/screens/LanguageSelectorScreen.tsx +206 -199
  965. package/src/ui/screens/LearnMoreUsernamesScreen.tsx +92 -110
  966. package/src/ui/screens/LegalDocumentsScreen.tsx +134 -193
  967. package/src/ui/screens/ManageAccountScreen.tsx +649 -871
  968. package/src/ui/screens/NotificationsScreen.tsx +111 -143
  969. package/src/ui/screens/PaymentGatewayScreen.tsx +229 -244
  970. package/src/ui/screens/PreferencesScreen.tsx +153 -196
  971. package/src/ui/screens/PrivacySettingsScreen.tsx +509 -351
  972. package/src/ui/screens/ProfileScreen.tsx +416 -319
  973. package/src/ui/screens/SearchSettingsScreen.tsx +100 -98
  974. package/src/ui/screens/UserLinksScreen.tsx +48 -43
  975. package/src/ui/screens/UserListScreen.tsx +53 -30
  976. package/src/ui/screens/WelcomeNewUserScreen.tsx +430 -334
  977. package/src/ui/screens/__tests__/linkFormat.test.ts +58 -62
  978. package/src/ui/screens/__tests__/photoGridLayout.test.ts +43 -47
  979. package/src/ui/screens/fileManagement/FileLibraryError.tsx +33 -27
  980. package/src/ui/screens/fileManagement/FileListSection.tsx +96 -87
  981. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +508 -475
  982. package/src/ui/screens/fileManagement/UploadBar.tsx +26 -16
  983. package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +514 -490
  984. package/src/ui/screens/fileManagement/photoGridLayout.ts +13 -13
  985. package/src/ui/screens/fileManagement/shared.ts +40 -38
  986. package/src/ui/screens/linkFormat.ts +14 -14
  987. package/src/ui/screens/trust/TrustAboutScreen.tsx +53 -51
  988. package/src/ui/screens/trust/TrustCenterScreen.tsx +156 -214
  989. package/src/ui/screens/trust/TrustFAQScreen.tsx +60 -67
  990. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +183 -183
  991. package/src/ui/screens/trust/TrustRewardsScreen.tsx +495 -494
  992. package/src/ui/screens/trust/TrustRulesScreen.tsx +84 -85
  993. package/src/ui/server.ts +12 -15
  994. package/src/ui/session/__tests__/authStore.test.ts +6 -1
  995. package/src/ui/session/__tests__/createSessionClient.test.ts +22 -8
  996. package/src/ui/session/__tests__/sharedDeviceCredentialStore.test.ts +24 -6
  997. package/src/ui/session/__tests__/tokenLossRecovery.test.ts +3 -1
  998. package/src/ui/session/__tests__/tokenTransport.test.ts +9 -2
  999. package/src/ui/session/authStore.ts +7 -1
  1000. package/src/ui/session/backgroundSession.ts +4 -3
  1001. package/src/ui/session/createSessionClient.ts +17 -5
  1002. package/src/ui/session/deviceCredentialTracker.ts +12 -6
  1003. package/src/ui/session/identityBinding.ts +6 -1
  1004. package/src/ui/session/sharedDeviceCredentialStore.ts +15 -4
  1005. package/src/ui/session/tokenLossRecovery.ts +24 -10
  1006. package/src/ui/session/tokenTransport.ts +5 -1
  1007. package/src/ui/stores/__tests__/followStore.test.ts +20 -7
  1008. package/src/ui/stores/assetStore.ts +209 -207
  1009. package/src/ui/stores/followStore.ts +80 -39
  1010. package/src/ui/stores/followTargetStore.ts +1 -1
  1011. package/src/ui/types/fileManagement.ts +48 -49
  1012. package/src/ui/types/navigation.ts +168 -168
  1013. package/src/ui/types/surfaceScreen.ts +4 -1
  1014. package/src/ui/utils/__tests__/deviceSessionTitle.test.ts +4 -2
  1015. package/src/ui/utils/accountDeletionHandoff.ts +14 -3
  1016. package/src/ui/utils/avatarUtils.ts +7 -6
  1017. package/src/ui/utils/clipboard.native.ts +23 -6
  1018. package/src/ui/utils/commonsStoreLinks.ts +2 -1
  1019. package/src/ui/utils/errorHandlers.ts +7 -12
  1020. package/src/ui/utils/fileManagement.ts +92 -103
  1021. package/src/ui/utils/iconNames.ts +60 -60
  1022. package/src/ui/utils/sessionHelpers.ts +1 -6
  1023. package/src/ui/utils/storageHelpers.ts +0 -1
  1024. package/src/ui/utils/userUtils.ts +2 -6
  1025. package/src/utils/hookUtils.ts +77 -65
@@ -1,7 +1,15 @@
1
1
  import { Button } from '@oxy.so/bloom/button';
2
2
  import type React from 'react';
3
3
  import { useState, useCallback, useMemo, useRef } from 'react';
4
- import { View, TouchableOpacity, StyleSheet, ScrollView, ActivityIndicator, RefreshControl, useWindowDimensions } from 'react-native';
4
+ import {
5
+ View,
6
+ TouchableOpacity,
7
+ StyleSheet,
8
+ ScrollView,
9
+ ActivityIndicator,
10
+ RefreshControl,
11
+ useWindowDimensions,
12
+ } from 'react-native';
5
13
  import { Text } from '@oxy.so/bloom/typography';
6
14
  import { useQueryClient } from '@tanstack/react-query';
7
15
  import { Image as ExpoImage } from 'expo-image';
@@ -14,7 +22,11 @@ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
14
22
  import { Pressable } from 'react-native';
15
23
  import type { FileMetadata } from '@oxy.so/core';
16
24
  import { queryKeys } from '../hooks/queries/queryKeys';
17
- import { useUserFilesInfinite, removeFileFromCache, patchFileMetadataInCache } from '../hooks/queries/useFileQueries';
25
+ import {
26
+ useUserFilesInfinite,
27
+ removeFileFromCache,
28
+ patchFileMetadataInCache,
29
+ } from '../hooks/queries/useFileQueries';
18
30
  import { useSurfaceHeader, type SurfaceHeaderContent } from '../hooks/useSurfaceHeader';
19
31
  import { SurfaceHeaderAction } from '../components/SurfaceHeaderAction';
20
32
  import JustifiedPhotoGrid from '../components/photogrid/JustifiedPhotoGrid';
@@ -23,10 +35,7 @@ import { useOxy } from '../context/OxyContext';
23
35
  import { useI18n } from '../hooks/useI18n';
24
36
  import { useReduceMotion } from '../hooks/useReduceMotion';
25
37
  import { useUploadFile } from '../hooks/mutations/useAccountMutations';
26
- import {
27
- formatFileSize,
28
- getFileIcon,
29
- } from '../utils/fileManagement';
38
+ import { formatFileSize, getFileIcon } from '../utils/fileManagement';
30
39
  import { useResolvedFileUrls, fileThumbSource } from '../hooks/useResolvedFileUrls';
31
40
  import { FileViewer } from '../components/fileManagement/FileViewer';
32
41
  import { presentFileDetails } from '../components/fileManagement/FileDetailsModal';
@@ -44,1226 +53,1560 @@ import { Search } from '@oxy.so/bloom/search';
44
53
  // Photo tiles use expo-image dimensions; controls use Bloom primitives.
45
54
  // Nav-header slot layout for this screen's action row + review-mode back button.
46
55
  const fmHeaderStyles = StyleSheet.create({
47
- actionsRow: {
48
- flexDirection: 'row',
49
- gap: 8,
50
- },
51
- backButton: {
52
- width: 36,
53
- height: 36,
54
- borderRadius: 18,
55
- alignItems: 'center',
56
- justifyContent: 'center',
57
- },
56
+ actionsRow: {
57
+ flexDirection: 'row',
58
+ gap: 8,
59
+ },
60
+ backButton: {
61
+ width: 36,
62
+ height: 36,
63
+ borderRadius: 18,
64
+ alignItems: 'center',
65
+ justifyContent: 'center',
66
+ },
58
67
  });
59
68
 
60
69
  const screenStyles = StyleSheet.create({
61
- justifiedPhotoImage: { width: '100%', height: '100%', borderRadius: 6 },
70
+ justifiedPhotoImage: { width: '100%', height: '100%', borderRadius: 6 },
62
71
  });
63
72
 
64
73
  const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
65
- onClose,
66
- goBack,
67
- navigate,
68
- dismiss,
69
- userId,
70
- containerWidth,
71
- selectMode = false,
72
- multiSelect = false,
73
- onSelect,
74
- onPicked,
75
- onConfirmSelection,
76
- initialSelectedIds = [],
77
- maxSelection,
78
- disabledMimeTypes = [],
79
- afterSelect = 'close',
80
- allowUploadInSelectMode = true,
81
- defaultVisibility = 'private',
82
- linkContext,
74
+ onClose,
75
+ goBack,
76
+ navigate,
77
+ dismiss,
78
+ userId,
79
+ containerWidth,
80
+ selectMode = false,
81
+ multiSelect = false,
82
+ onSelect,
83
+ onPicked,
84
+ onConfirmSelection,
85
+ initialSelectedIds = [],
86
+ maxSelection,
87
+ disabledMimeTypes = [],
88
+ afterSelect = 'close',
89
+ allowUploadInSelectMode = true,
90
+ defaultVisibility = 'private',
91
+ linkContext,
83
92
  }) => {
84
- // Use useOxy() hook for OxyContext values. Files are owned by the ACTIVE
85
- // account (the org/project/bot when switched, else the personal user): the
86
- // default file owner and every "is this mine?" ownership check resolve
87
- // against `user`, so switching shows/manages that account's files.
88
- const { user, oxyServices } = useOxy();
89
- const { t } = useI18n();
90
- const queryClient = useQueryClient();
91
- const uploadFileMutation = useUploadFile();
92
-
93
- // Files are owned by the ACTIVE account. `targetUserId` scopes the query key,
94
- // so switching accounts (or an explicit `userId` prop) shows that account's
95
- // library with no manual reset.
96
- const targetUserId = userId || user?.id;
97
-
98
- // The file list is React Query's, NOT a store: infinite-paginated, fetched on
99
- // mount + on owner change, edited optimistically via cache helpers. The
100
- // rendered list is the flattened pages.
101
- const filesQuery = useUserFilesInfinite(targetUserId);
102
- const files = useMemo(
103
- () => filesQuery.data?.pages.flatMap((page) => page.files) ?? [],
104
- [filesQuery.data],
105
- );
106
- // No owner resolved yet reads as "loading" (matches the old skeleton-until-
107
- // first-load behaviour); `isLoading` is the first-page fetch only.
108
- const isLoadingFiles = !targetUserId || filesQuery.isLoading;
109
- const isRefreshingFiles = filesQuery.isRefetching;
110
- const isFetchingMore = filesQuery.isFetchingNextPage;
111
- const hasMoreFiles = filesQuery.hasNextPage ?? false;
112
- // Terminal load failure with nothing cached — render a DISTINCT error state
113
- // (not the empty state) so a failed load never looks like an empty library.
114
- const hasLoadError = filesQuery.isError && files.length === 0;
115
- const invalidateFiles = useCallback(() => {
116
- queryClient.invalidateQueries({ queryKey: queryKeys.files.list(targetUserId) });
117
- }, [queryClient, targetUserId]);
118
-
119
- const { width: windowWidth } = useWindowDimensions();
120
- // Prefer an explicit sheet width from the router; fall back to the window
121
- // so photo grids never size against a hardcoded 400px default.
122
- const safeContainerWidth: number =
123
- typeof containerWidth === 'number' && containerWidth > 0
124
- ? containerWidth
125
- : windowWidth;
126
- // In-flight delete target (local UI state — was in the file store).
127
- const [deletingId, setDeletingId] = useState<string | null>(null);
128
- // In selectMode we never open the detailed viewer
129
- const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
130
- const [fileContent, setFileContent] = useState<string | null>(null);
131
- const [loadingFileContent, setLoadingFileContent] = useState(false);
132
- const [showFileDetailsInViewer, setShowFileDetailsInViewer] = useState(false);
133
- // Reduce-motion preference, read as an external store (no effect). Passed to
134
- // `PhotoPickerView` to skip cell stagger animations.
135
- const reduceMotion = useReduceMotion();
136
-
137
- // Image-only picker mode: when the consumer restricts to image MIME types
138
- // (e.g. avatar picker), photos grid is the more useful default view.
139
- const isImageOnlyPicker = useMemo(() => {
140
- if (!selectMode) return false;
141
- if (disabledMimeTypes.length === 0) return false;
142
- const blocksVideos = disabledMimeTypes.some(mt => mt === 'video/' || mt.startsWith('video/'));
143
- const blocksAudio = disabledMimeTypes.some(mt => mt === 'audio/' || mt.startsWith('audio/'));
144
- const blocksDocs = disabledMimeTypes.some(mt =>
145
- mt === 'application/pdf' ||
146
- mt === 'application/' ||
147
- mt.startsWith('application/')
148
- );
149
- return blocksVideos && blocksAudio && blocksDocs;
150
- }, [disabledMimeTypes, selectMode]);
151
-
152
- const [viewMode, setViewMode] = useState<'all' | 'photos' | 'videos' | 'documents' | 'audio'>(
153
- isImageOnlyPicker ? 'photos' : 'all',
93
+ // Use useOxy() hook for OxyContext values. Files are owned by the ACTIVE
94
+ // account (the org/project/bot when switched, else the personal user): the
95
+ // default file owner and every "is this mine?" ownership check resolve
96
+ // against `user`, so switching shows/manages that account's files.
97
+ const { user, oxyServices } = useOxy();
98
+ const { t } = useI18n();
99
+ const queryClient = useQueryClient();
100
+ const uploadFileMutation = useUploadFile();
101
+
102
+ // Files are owned by the ACTIVE account. `targetUserId` scopes the query key,
103
+ // so switching accounts (or an explicit `userId` prop) shows that account's
104
+ // library with no manual reset.
105
+ const targetUserId = userId || user?.id;
106
+
107
+ // The file list is React Query's, NOT a store: infinite-paginated, fetched on
108
+ // mount + on owner change, edited optimistically via cache helpers. The
109
+ // rendered list is the flattened pages.
110
+ const filesQuery = useUserFilesInfinite(targetUserId);
111
+ const files = useMemo(
112
+ () => filesQuery.data?.pages.flatMap((page) => page.files) ?? [],
113
+ [filesQuery.data],
114
+ );
115
+ // No owner resolved yet reads as "loading" (matches the old skeleton-until-
116
+ // first-load behaviour); `isLoading` is the first-page fetch only.
117
+ const isLoadingFiles = !targetUserId || filesQuery.isLoading;
118
+ const isRefreshingFiles = filesQuery.isRefetching;
119
+ const isFetchingMore = filesQuery.isFetchingNextPage;
120
+ const hasMoreFiles = filesQuery.hasNextPage ?? false;
121
+ // Terminal load failure with nothing cached — render a DISTINCT error state
122
+ // (not the empty state) so a failed load never looks like an empty library.
123
+ const hasLoadError = filesQuery.isError && files.length === 0;
124
+ const invalidateFiles = useCallback(() => {
125
+ queryClient.invalidateQueries({ queryKey: queryKeys.files.list(targetUserId) });
126
+ }, [queryClient, targetUserId]);
127
+
128
+ const { width: windowWidth } = useWindowDimensions();
129
+ // Prefer an explicit sheet width from the router; fall back to the window
130
+ // so photo grids never size against a hardcoded 400px default.
131
+ const safeContainerWidth: number =
132
+ typeof containerWidth === 'number' && containerWidth > 0 ? containerWidth : windowWidth;
133
+ // In-flight delete target (local UI state — was in the file store).
134
+ const [deletingId, setDeletingId] = useState<string | null>(null);
135
+ // In selectMode we never open the detailed viewer
136
+ const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
137
+ const [fileContent, setFileContent] = useState<string | null>(null);
138
+ const [loadingFileContent, setLoadingFileContent] = useState(false);
139
+ const [showFileDetailsInViewer, setShowFileDetailsInViewer] = useState(false);
140
+ // Reduce-motion preference, read as an external store (no effect). Passed to
141
+ // `PhotoPickerView` to skip cell stagger animations.
142
+ const reduceMotion = useReduceMotion();
143
+
144
+ // Image-only picker mode: when the consumer restricts to image MIME types
145
+ // (e.g. avatar picker), photos grid is the more useful default view.
146
+ const isImageOnlyPicker = useMemo(() => {
147
+ if (!selectMode) return false;
148
+ if (disabledMimeTypes.length === 0) return false;
149
+ const blocksVideos = disabledMimeTypes.some((mt) => mt === 'video/' || mt.startsWith('video/'));
150
+ const blocksAudio = disabledMimeTypes.some((mt) => mt === 'audio/' || mt.startsWith('audio/'));
151
+ const blocksDocs = disabledMimeTypes.some(
152
+ (mt) => mt === 'application/pdf' || mt === 'application/' || mt.startsWith('application/'),
154
153
  );
155
- const [searchQuery, setSearchQuery] = useState('');
156
- const [sortBy, setSortBy] = useState<'date' | 'size' | 'name' | 'type'>('date');
157
- const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
158
- // Derived filtered and sorted files (avoid setState loops)
159
- const filteredFiles = useMemo(() => {
160
- let filteredByMode = files;
161
- if (viewMode === 'photos') {
162
- filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
163
- } else if (viewMode === 'videos') {
164
- filteredByMode = files.filter(file => file.contentType.startsWith('video/'));
165
- } else if (viewMode === 'documents') {
166
- filteredByMode = files.filter(file =>
167
- file.contentType.includes('pdf') ||
168
- file.contentType.includes('document') ||
169
- file.contentType.includes('text') ||
170
- file.contentType.includes('msword') ||
171
- file.contentType.includes('excel') ||
172
- file.contentType.includes('spreadsheet') ||
173
- file.contentType.includes('presentation') ||
174
- file.contentType.includes('powerpoint')
175
- );
176
- } else if (viewMode === 'audio') {
177
- filteredByMode = files.filter(file => file.contentType.startsWith('audio/'));
178
- }
154
+ return blocksVideos && blocksAudio && blocksDocs;
155
+ }, [disabledMimeTypes, selectMode]);
156
+
157
+ const [viewMode, setViewMode] = useState<'all' | 'photos' | 'videos' | 'documents' | 'audio'>(
158
+ isImageOnlyPicker ? 'photos' : 'all',
159
+ );
160
+ const [searchQuery, setSearchQuery] = useState('');
161
+ const [sortBy, setSortBy] = useState<'date' | 'size' | 'name' | 'type'>('date');
162
+ const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
163
+ // Derived filtered and sorted files (avoid setState loops)
164
+ const filteredFiles = useMemo(() => {
165
+ let filteredByMode = files;
166
+ if (viewMode === 'photos') {
167
+ filteredByMode = files.filter((file) => file.contentType.startsWith('image/'));
168
+ } else if (viewMode === 'videos') {
169
+ filteredByMode = files.filter((file) => file.contentType.startsWith('video/'));
170
+ } else if (viewMode === 'documents') {
171
+ filteredByMode = files.filter(
172
+ (file) =>
173
+ file.contentType.includes('pdf') ||
174
+ file.contentType.includes('document') ||
175
+ file.contentType.includes('text') ||
176
+ file.contentType.includes('msword') ||
177
+ file.contentType.includes('excel') ||
178
+ file.contentType.includes('spreadsheet') ||
179
+ file.contentType.includes('presentation') ||
180
+ file.contentType.includes('powerpoint'),
181
+ );
182
+ } else if (viewMode === 'audio') {
183
+ filteredByMode = files.filter((file) => file.contentType.startsWith('audio/'));
184
+ }
179
185
 
180
- let filtered = filteredByMode;
181
- if (searchQuery.trim()) {
182
- const query = searchQuery.toLowerCase();
183
- filtered = filteredByMode.filter(file =>
184
- file.filename.toLowerCase().includes(query) ||
185
- file.contentType.toLowerCase().includes(query) ||
186
- (file.metadata?.description?.toLowerCase().includes(query))
187
- );
188
- }
186
+ let filtered = filteredByMode;
187
+ if (searchQuery.trim()) {
188
+ const query = searchQuery.toLowerCase();
189
+ filtered = filteredByMode.filter(
190
+ (file) =>
191
+ file.filename.toLowerCase().includes(query) ||
192
+ file.contentType.toLowerCase().includes(query) ||
193
+ file.metadata?.description?.toLowerCase().includes(query),
194
+ );
195
+ }
189
196
 
190
- // Sort files
191
- const sorted = [...filtered].sort((a, b) => {
192
- let comparison = 0;
193
- if (sortBy === 'date') {
194
- const dateA = new Date(a.uploadDate || 0).getTime();
195
- const dateB = new Date(b.uploadDate || 0).getTime();
196
- comparison = dateA - dateB;
197
- } else if (sortBy === 'size') {
198
- comparison = (a.length || 0) - (b.length || 0);
199
- } else if (sortBy === 'name') {
200
- comparison = (a.filename || '').localeCompare(b.filename || '');
201
- } else if (sortBy === 'type') {
202
- comparison = (a.contentType || '').localeCompare(b.contentType || '');
203
- }
204
- return sortOrder === 'asc' ? comparison : -comparison;
205
- });
197
+ // Sort files
198
+ const sorted = [...filtered].sort((a, b) => {
199
+ let comparison = 0;
200
+ if (sortBy === 'date') {
201
+ const dateA = new Date(a.uploadDate || 0).getTime();
202
+ const dateB = new Date(b.uploadDate || 0).getTime();
203
+ comparison = dateA - dateB;
204
+ } else if (sortBy === 'size') {
205
+ comparison = (a.length || 0) - (b.length || 0);
206
+ } else if (sortBy === 'name') {
207
+ comparison = (a.filename || '').localeCompare(b.filename || '');
208
+ } else if (sortBy === 'type') {
209
+ comparison = (a.contentType || '').localeCompare(b.contentType || '');
210
+ }
211
+ return sortOrder === 'asc' ? comparison : -comparison;
212
+ });
206
213
 
207
- return sorted;
208
- }, [files, searchQuery, viewMode, sortBy, sortOrder]);
209
- // Selection state. `initialSelectedIds` is the INITIAL selection only (lazy
210
- // init) — there is no prop→state sync effect, since no caller mutates it
211
- // after mount.
212
- const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set(initialSelectedIds));
213
- const scrollViewRef = useRef<ScrollView>(null);
214
- const photoScrollViewRef = useRef<ScrollView>(null);
215
-
216
- // Scroll the browse list/grid to a just-selected file. Driven directly from
217
- // the selection event handler (double-rAF so the selection re-render has
218
- // committed first) — not an effect. No-ops in the image-only picker, whose
219
- // own FlatList is not either ScrollView ref.
220
- const scrollToSelectedFile = useCallback((fileId: string) => {
221
- if (viewMode === 'all' && scrollViewRef.current) {
222
- const itemIndex = filteredFiles.findIndex(file => file.id === fileId);
223
- if (itemIndex < 0) return;
224
- // GroupedItem dense rows are ~65px, +30px when a description wraps.
225
- const baseItemHeight = 65;
226
- const descriptionHeight = 30;
227
- let scrollPosition = 0;
228
- for (let i = 0; i <= itemIndex && i < filteredFiles.length; i++) {
229
- scrollPosition += baseItemHeight;
230
- if (filteredFiles[i].metadata?.description) scrollPosition += descriptionHeight;
231
- }
232
- // Offset for the header/controls/search/stats chrome (~250px) so the
233
- // item lands near the top, not under the chrome.
234
- const finalScrollPosition = 250 + scrollPosition - 150;
235
- requestAnimationFrame(() => {
236
- requestAnimationFrame(() => {
237
- scrollViewRef.current?.scrollTo({ y: Math.max(0, finalScrollPosition), animated: true });
238
- });
214
+ return sorted;
215
+ }, [files, searchQuery, viewMode, sortBy, sortOrder]);
216
+ // Selection state. `initialSelectedIds` is the INITIAL selection only (lazy
217
+ // init) — there is no prop→state sync effect, since no caller mutates it
218
+ // after mount.
219
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set(initialSelectedIds));
220
+ const scrollViewRef = useRef<ScrollView>(null);
221
+ const photoScrollViewRef = useRef<ScrollView>(null);
222
+
223
+ // Scroll the browse list/grid to a just-selected file. Driven directly from
224
+ // the selection event handler (double-rAF so the selection re-render has
225
+ // committed first) — not an effect. No-ops in the image-only picker, whose
226
+ // own FlatList is not either ScrollView ref.
227
+ const scrollToSelectedFile = useCallback(
228
+ (fileId: string) => {
229
+ if (viewMode === 'all' && scrollViewRef.current) {
230
+ const itemIndex = filteredFiles.findIndex((file) => file.id === fileId);
231
+ if (itemIndex < 0) return;
232
+ // GroupedItem dense rows are ~65px, +30px when a description wraps.
233
+ const baseItemHeight = 65;
234
+ const descriptionHeight = 30;
235
+ let scrollPosition = 0;
236
+ for (let i = 0; i <= itemIndex && i < filteredFiles.length; i++) {
237
+ scrollPosition += baseItemHeight;
238
+ if (filteredFiles[i].metadata?.description) scrollPosition += descriptionHeight;
239
+ }
240
+ // Offset for the header/controls/search/stats chrome (~250px) so the
241
+ // item lands near the top, not under the chrome.
242
+ const finalScrollPosition = 250 + scrollPosition - 150;
243
+ requestAnimationFrame(() => {
244
+ requestAnimationFrame(() => {
245
+ scrollViewRef.current?.scrollTo({
246
+ y: Math.max(0, finalScrollPosition),
247
+ animated: true,
239
248
  });
240
- } else if (viewMode === 'photos' && photoScrollViewRef.current) {
241
- const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
242
- const photoIndex = photos.findIndex(p => p.id === fileId);
243
- if (photoIndex < 0) return;
244
- // Rough estimate for the justified grid (3 photos/row, variable heights).
245
- const row = Math.floor(photoIndex / 3);
246
- const finalScrollPosition = row * (150 + 4) - 100;
247
- requestAnimationFrame(() => {
248
- requestAnimationFrame(() => {
249
- photoScrollViewRef.current?.scrollTo({ y: Math.max(0, finalScrollPosition), animated: true });
250
- });
249
+ });
250
+ });
251
+ } else if (viewMode === 'photos' && photoScrollViewRef.current) {
252
+ const photos = filteredFiles.filter((file) => file.contentType.startsWith('image/'));
253
+ const photoIndex = photos.findIndex((p) => p.id === fileId);
254
+ if (photoIndex < 0) return;
255
+ // Rough estimate for the justified grid (3 photos/row, variable heights).
256
+ const row = Math.floor(photoIndex / 3);
257
+ const finalScrollPosition = row * (150 + 4) - 100;
258
+ requestAnimationFrame(() => {
259
+ requestAnimationFrame(() => {
260
+ photoScrollViewRef.current?.scrollTo({
261
+ y: Math.max(0, finalScrollPosition),
262
+ animated: true,
251
263
  });
264
+ });
265
+ });
266
+ }
267
+ },
268
+ [viewMode, filteredFiles],
269
+ );
270
+
271
+ const toggleSelect = useCallback(
272
+ async (file: FileMetadata) => {
273
+ // Selection is allowed in regular mode too (for bulk operations).
274
+ if (disabledMimeTypes.length) {
275
+ const blocked = disabledMimeTypes.some(
276
+ (mt) =>
277
+ file.contentType === mt ||
278
+ file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`),
279
+ );
280
+ if (blocked) {
281
+ toast.error(t('fileManagement.toasts.fileTypeBlocked'));
282
+ return;
252
283
  }
253
- }, [viewMode, filteredFiles]);
254
-
255
- const toggleSelect = useCallback(async (file: FileMetadata) => {
256
- // Selection is allowed in regular mode too (for bulk operations).
257
- if (disabledMimeTypes.length) {
258
- const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
259
- if (blocked) {
260
- toast.error(t('fileManagement.toasts.fileTypeBlocked'));
261
- return;
262
- }
263
- }
264
-
265
- // Update file visibility if it differs from defaultVisibility
266
- const fileVisibility = (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
267
- if (fileVisibility !== defaultVisibility) {
268
- try {
269
- await oxyServices.assets.setVisibility(file.id, defaultVisibility);
270
- } catch (error) {
271
- // Continue anyway - selection shouldn't fail if visibility update fails
272
- }
273
- }
284
+ }
274
285
 
275
- // Link file to entity if linkContext is provided
276
- if (linkContext) {
277
- try {
278
- await oxyServices.assets.link(
279
- file.id,
280
- { app: linkContext.app, entityType: linkContext.entityType, entityId: linkContext.entityId },
281
- { visibility: defaultVisibility, webhookUrl: linkContext.webhookUrl },
282
- );
283
- } catch (error) {
284
- // Continue anyway - selection shouldn't fail if linking fails
285
- }
286
+ // Update file visibility if it differs from defaultVisibility
287
+ const fileVisibility =
288
+ (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
289
+ if (fileVisibility !== defaultVisibility) {
290
+ try {
291
+ await oxyServices.assets.setVisibility(file.id, defaultVisibility);
292
+ } catch (error) {
293
+ // Continue anyway - selection shouldn't fail if visibility update fails
286
294
  }
295
+ }
287
296
 
288
- if (!multiSelect) {
289
- if (onPicked) {
290
- // Forward-navigation picker (e.g. the avatar flow): hand the file
291
- // to the caller, which pushes the NEXT frame (the cropper) on top
292
- // of this picker. The picker frame stays below so Back returns here
293
- // to re-pick — no dismiss, no session close.
294
- onPicked(file);
295
- } else if (onSelect) {
296
- // Legacy callback caller (inbox, WelcomeNewUser): keep the
297
- // callback + afterSelect behaviour unchanged.
298
- onSelect(file);
299
- if (afterSelect === 'back') {
300
- goBack?.();
301
- } else if (afterSelect === 'close') {
302
- onClose?.();
303
- }
304
- } else {
305
- // Promise-based picker: resolve the surface's present() promise
306
- // with the picked file.
307
- dismiss?.(file);
308
- }
309
- return;
297
+ // Link file to entity if linkContext is provided
298
+ if (linkContext) {
299
+ try {
300
+ await oxyServices.assets.link(
301
+ file.id,
302
+ {
303
+ app: linkContext.app,
304
+ entityType: linkContext.entityType,
305
+ entityId: linkContext.entityId,
306
+ },
307
+ { visibility: defaultVisibility, webhookUrl: linkContext.webhookUrl },
308
+ );
309
+ } catch (error) {
310
+ // Continue anyway - selection shouldn't fail if linking fails
310
311
  }
311
- setSelectedIds(prev => {
312
- const next = new Set(prev);
313
- const already = next.has(file.id);
314
- if (!already) {
315
- if (maxSelection && next.size >= maxSelection) {
316
- toast.error(t('fileManagement.toasts.maxSelection', { max: maxSelection }));
317
- return prev;
318
- }
319
- next.add(file.id);
320
- } else {
321
- next.delete(file.id);
322
- }
323
- return next;
324
- });
325
- scrollToSelectedFile(file.id);
326
- }, [multiSelect, onPicked, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext, t, scrollToSelectedFile]);
327
-
328
- const confirmMultiSelection = useCallback(async () => {
329
- if (!selectMode || !multiSelect) return;
330
- const map: Record<string, FileMetadata> = {};
331
- for (const f of files) { map[f.id] = f; }
332
- const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
333
-
334
- // Update visibility and link files if needed
335
- const updatePromises = chosen.map(async (file) => {
336
- // Update visibility if needed
337
- const fileVisibility = (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
338
- if (fileVisibility !== defaultVisibility) {
339
- try {
340
- await oxyServices.assets.setVisibility(file.id, defaultVisibility);
341
- } catch (error) {
342
- // Visibility update failed, continue with selection
343
- }
344
- }
312
+ }
345
313
 
346
- // Link file to entity if linkContext provided
347
- if (linkContext) {
348
- try {
349
- await oxyServices.assets.link(
350
- file.id,
351
- { app: linkContext.app, entityType: linkContext.entityType, entityId: linkContext.entityId },
352
- { visibility: defaultVisibility, webhookUrl: linkContext.webhookUrl },
353
- );
354
- } catch (error) {
355
- // File linking failed, continue with selection
356
- }
357
- }
358
- });
359
-
360
- // Wait for all updates (but don't block on failures)
361
- await Promise.allSettled(updatePromises);
362
-
363
- if (onConfirmSelection) {
364
- // Legacy callback caller (inbox): keep the callback + close.
365
- onConfirmSelection(chosen);
314
+ if (!multiSelect) {
315
+ if (onPicked) {
316
+ // Forward-navigation picker (e.g. the avatar flow): hand the file
317
+ // to the caller, which pushes the NEXT frame (the cropper) on top
318
+ // of this picker. The picker frame stays below so Back returns here
319
+ // to re-pick — no dismiss, no session close.
320
+ onPicked(file);
321
+ } else if (onSelect) {
322
+ // Legacy callback caller (inbox, WelcomeNewUser): keep the
323
+ // callback + afterSelect behaviour unchanged.
324
+ onSelect(file);
325
+ if (afterSelect === 'back') {
326
+ goBack?.();
327
+ } else if (afterSelect === 'close') {
366
328
  onClose?.();
329
+ }
367
330
  } else {
368
- // Promise-based picker: resolve the surface with the chosen files.
369
- dismiss?.(chosen);
331
+ // Promise-based picker: resolve the surface's present() promise
332
+ // with the picked file.
333
+ dismiss?.(file);
370
334
  }
371
- }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose, dismiss, defaultVisibility, oxyServices, linkContext]);
372
-
373
- // Private-safe thumbnail URLs for the currently-shown files, resolved in
374
- // ONE batch per page (not N per-tile) and cached by React Query. Uploads
375
- // default to private, so the synchronous public-CDN URL would 404 for
376
- // every private thumbnail.
377
- const resolvedThumbUrls = useResolvedFileUrls(oxyServices, filteredFiles, user?.id);
378
-
379
- // Image source for a grid tile: the resolved private-safe URL for a
380
- // persisted file, or the locally-picked preview for an in-flight optimistic
381
- // entry. Never builds an asset URL from a `temp-…`/uploading id.
382
- const thumbSourceFor = useCallback(
383
- (file: FileMetadata): string | undefined => fileThumbSource(file, resolvedThumbUrls),
384
- [resolvedThumbUrls],
385
- );
386
-
387
- const bloomTheme = useTheme();
388
- const { colors } = bloomTheme;
389
- // FileManagementScreen uses a slightly elevated page background.
390
- const backgroundColor = colors.backgroundSecondary;
391
- const borderColor = colors.border;
392
-
393
- // Self-contained document-picking + upload-preview state and handlers.
394
- // Optimistic uploads edit the file query cache directly (see
395
- // useFileQueries), so no loader is threaded in.
396
- const {
397
- isPickingDocument,
398
- pendingFiles,
399
- showUploadPreview,
400
- uploading,
401
- uploadProgress,
402
- handleFileUpload,
403
- handleConfirmUpload,
404
- handleCancelUpload,
405
- removePendingFile,
406
- } = useFileUploadState({
407
- targetUserId,
408
- uploadFileMutation,
409
- defaultVisibility,
410
- selectMode,
411
- multiSelect,
412
- afterSelect,
413
- onSelect,
414
- onPicked,
415
- goBack,
416
- onClose,
417
- imagesOnly: isImageOnlyPicker,
418
- selectedIds,
419
- setSelectedIds,
420
- t,
421
- });
422
-
423
- const confirmFileDelete = useCallback(async (fileId: string, filename: string) => {
424
- const confirmed = await surfaces.confirm({
425
- title: t('fileManagement.deleteFile') || 'Delete File',
426
- description: t('fileManagement.confirms.deleteFile', { filename }),
427
- confirmLabel: t('fileManagement.confirm') || 'Delete',
428
- cancelLabel: t('common.cancel') || 'Cancel',
429
- destructive: true,
430
- });
431
- if (!confirmed) return;
432
-
335
+ return;
336
+ }
337
+ setSelectedIds((prev) => {
338
+ const next = new Set(prev);
339
+ const already = next.has(file.id);
340
+ if (!already) {
341
+ if (maxSelection && next.size >= maxSelection) {
342
+ toast.error(t('fileManagement.toasts.maxSelection', { max: maxSelection }));
343
+ return prev;
344
+ }
345
+ next.add(file.id);
346
+ } else {
347
+ next.delete(file.id);
348
+ }
349
+ return next;
350
+ });
351
+ scrollToSelectedFile(file.id);
352
+ },
353
+ [
354
+ multiSelect,
355
+ onPicked,
356
+ onSelect,
357
+ onClose,
358
+ goBack,
359
+ dismiss,
360
+ disabledMimeTypes,
361
+ maxSelection,
362
+ afterSelect,
363
+ defaultVisibility,
364
+ oxyServices,
365
+ linkContext,
366
+ t,
367
+ scrollToSelectedFile,
368
+ ],
369
+ );
370
+
371
+ const confirmMultiSelection = useCallback(async () => {
372
+ if (!selectMode || !multiSelect) return;
373
+ const map: Record<string, FileMetadata> = {};
374
+ for (const f of files) {
375
+ map[f.id] = f;
376
+ }
377
+ const chosen = Array.from(selectedIds)
378
+ .map((id) => map[id])
379
+ .filter(Boolean);
380
+
381
+ // Update visibility and link files if needed
382
+ const updatePromises = chosen.map(async (file) => {
383
+ // Update visibility if needed
384
+ const fileVisibility =
385
+ (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
386
+ if (fileVisibility !== defaultVisibility) {
433
387
  try {
434
- setDeletingId(fileId);
435
- await oxyServices.assets.delete(fileId);
436
-
437
- toast.success(t('fileManagement.toasts.deleteSuccess'));
438
-
439
- // Remove from the query cache, then reconcile against the server.
440
- removeFileFromCache(queryClient, targetUserId, fileId);
441
- invalidateFiles();
442
- } catch (error: unknown) {
443
- // Provide specific error messages
444
- if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
445
- toast.error(t('fileManagement.toasts.fileNotFound'));
446
- // It's already gone server-side — reconcile the list.
447
- invalidateFiles();
448
- } else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
449
- toast.error(t('fileManagement.toasts.noPermission'));
450
- } else {
451
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
452
- }
453
- } finally {
454
- setDeletingId(null);
388
+ await oxyServices.assets.setVisibility(file.id, defaultVisibility);
389
+ } catch (error) {
390
+ // Visibility update failed, continue with selection
455
391
  }
456
- }, [oxyServices, queryClient, targetUserId, invalidateFiles, t]);
457
-
458
- const confirmBulkDelete = useCallback(async () => {
459
- if (selectedIds.size === 0) return;
460
-
461
- const confirmed = await surfaces.confirm({
462
- title: t('fileManagement.deleteFiles') || 'Delete Files',
463
- description: t('fileManagement.confirms.deleteFiles', { count: selectedIds.size }),
464
- confirmLabel: t('fileManagement.confirm') || 'Delete',
465
- cancelLabel: t('common.cancel') || 'Cancel',
466
- destructive: true,
467
- });
468
- if (!confirmed) return;
392
+ }
469
393
 
394
+ // Link file to entity if linkContext provided
395
+ if (linkContext) {
470
396
  try {
471
- const deletePromises = Array.from(selectedIds).map(async (fileId) => {
472
- try {
473
- await oxyServices.assets.delete(fileId);
474
- removeFileFromCache(queryClient, targetUserId, fileId);
475
- return { success: true, fileId };
476
- } catch (error: unknown) {
477
- return { success: false, fileId, error };
478
- }
479
- });
480
-
481
- const results = await Promise.allSettled(deletePromises);
482
- const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
483
- const failed = results.length - successful;
397
+ await oxyServices.assets.link(
398
+ file.id,
399
+ {
400
+ app: linkContext.app,
401
+ entityType: linkContext.entityType,
402
+ entityId: linkContext.entityId,
403
+ },
404
+ { visibility: defaultVisibility, webhookUrl: linkContext.webhookUrl },
405
+ );
406
+ } catch (error) {
407
+ // File linking failed, continue with selection
408
+ }
409
+ }
410
+ });
484
411
 
485
- if (successful > 0) {
486
- toast.success(t('fileManagement.toasts.bulkDeleteSuccess', { count: successful }));
487
- }
488
- if (failed > 0) {
489
- toast.error(t('fileManagement.toasts.bulkDeleteFailed', { count: failed }));
490
- }
412
+ // Wait for all updates (but don't block on failures)
413
+ await Promise.allSettled(updatePromises);
491
414
 
492
- setSelectedIds(new Set());
493
- invalidateFiles();
494
- } catch (error: unknown) {
495
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
415
+ if (onConfirmSelection) {
416
+ // Legacy callback caller (inbox): keep the callback + close.
417
+ onConfirmSelection(chosen);
418
+ onClose?.();
419
+ } else {
420
+ // Promise-based picker: resolve the surface with the chosen files.
421
+ dismiss?.(chosen);
422
+ }
423
+ }, [
424
+ selectMode,
425
+ multiSelect,
426
+ selectedIds,
427
+ files,
428
+ onConfirmSelection,
429
+ onClose,
430
+ dismiss,
431
+ defaultVisibility,
432
+ oxyServices,
433
+ linkContext,
434
+ ]);
435
+
436
+ // Private-safe thumbnail URLs for the currently-shown files, resolved in
437
+ // ONE batch per page (not N per-tile) and cached by React Query. Uploads
438
+ // default to private, so the synchronous public-CDN URL would 404 for
439
+ // every private thumbnail.
440
+ const resolvedThumbUrls = useResolvedFileUrls(oxyServices, filteredFiles, user?.id);
441
+
442
+ // Image source for a grid tile: the resolved private-safe URL for a
443
+ // persisted file, or the locally-picked preview for an in-flight optimistic
444
+ // entry. Never builds an asset URL from a `temp-…`/uploading id.
445
+ const thumbSourceFor = useCallback(
446
+ (file: FileMetadata): string | undefined => fileThumbSource(file, resolvedThumbUrls),
447
+ [resolvedThumbUrls],
448
+ );
449
+
450
+ const bloomTheme = useTheme();
451
+ const { colors } = bloomTheme;
452
+ // FileManagementScreen uses a slightly elevated page background.
453
+ const backgroundColor = colors.backgroundSecondary;
454
+ const borderColor = colors.border;
455
+
456
+ // Self-contained document-picking + upload-preview state and handlers.
457
+ // Optimistic uploads edit the file query cache directly (see
458
+ // useFileQueries), so no loader is threaded in.
459
+ const {
460
+ isPickingDocument,
461
+ pendingFiles,
462
+ showUploadPreview,
463
+ uploading,
464
+ uploadProgress,
465
+ handleFileUpload,
466
+ handleConfirmUpload,
467
+ handleCancelUpload,
468
+ removePendingFile,
469
+ } = useFileUploadState({
470
+ targetUserId,
471
+ uploadFileMutation,
472
+ defaultVisibility,
473
+ selectMode,
474
+ multiSelect,
475
+ afterSelect,
476
+ onSelect,
477
+ onPicked,
478
+ goBack,
479
+ onClose,
480
+ imagesOnly: isImageOnlyPicker,
481
+ selectedIds,
482
+ setSelectedIds,
483
+ t,
484
+ });
485
+
486
+ const confirmFileDelete = useCallback(
487
+ async (fileId: string, filename: string) => {
488
+ const confirmed = await surfaces.confirm({
489
+ title: t('fileManagement.deleteFile') || 'Delete File',
490
+ description: t('fileManagement.confirms.deleteFile', { filename }),
491
+ confirmLabel: t('fileManagement.confirm') || 'Delete',
492
+ cancelLabel: t('common.cancel') || 'Cancel',
493
+ destructive: true,
494
+ });
495
+ if (!confirmed) return;
496
+
497
+ try {
498
+ setDeletingId(fileId);
499
+ await oxyServices.assets.delete(fileId);
500
+
501
+ toast.success(t('fileManagement.toasts.deleteSuccess'));
502
+
503
+ // Remove from the query cache, then reconcile against the server.
504
+ removeFileFromCache(queryClient, targetUserId, fileId);
505
+ invalidateFiles();
506
+ } catch (error: unknown) {
507
+ // Provide specific error messages
508
+ if (
509
+ getErrorMessage(error)?.includes('File not found') ||
510
+ getErrorMessage(error)?.includes('404')
511
+ ) {
512
+ toast.error(t('fileManagement.toasts.fileNotFound'));
513
+ // It's already gone server-side — reconcile the list.
514
+ invalidateFiles();
515
+ } else if (
516
+ getErrorMessage(error)?.includes('permission') ||
517
+ getErrorMessage(error)?.includes('403')
518
+ ) {
519
+ toast.error(t('fileManagement.toasts.noPermission'));
520
+ } else {
521
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
496
522
  }
497
- }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
498
-
499
- const handleBulkVisibilityChange = useCallback(async (visibility: 'private' | 'public' | 'unlisted') => {
500
- if (selectedIds.size === 0) return;
523
+ } finally {
524
+ setDeletingId(null);
525
+ }
526
+ },
527
+ [oxyServices, queryClient, targetUserId, invalidateFiles, t],
528
+ );
529
+
530
+ const confirmBulkDelete = useCallback(async () => {
531
+ if (selectedIds.size === 0) return;
532
+
533
+ const confirmed = await surfaces.confirm({
534
+ title: t('fileManagement.deleteFiles') || 'Delete Files',
535
+ description: t('fileManagement.confirms.deleteFiles', { count: selectedIds.size }),
536
+ confirmLabel: t('fileManagement.confirm') || 'Delete',
537
+ cancelLabel: t('common.cancel') || 'Cancel',
538
+ destructive: true,
539
+ });
540
+ if (!confirmed) return;
501
541
 
542
+ try {
543
+ const deletePromises = Array.from(selectedIds).map(async (fileId) => {
502
544
  try {
503
- const updatePromises = Array.from(selectedIds).map(async (fileId) => {
504
- try {
505
- await oxyServices.assets.setVisibility(fileId, visibility);
506
- return { success: true, fileId };
507
- } catch (error: unknown) {
508
- return { success: false, fileId, error };
509
- }
510
- });
511
-
512
- const results = await Promise.allSettled(updatePromises);
513
- const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
514
- const failed = results.length - successful;
515
-
516
- if (successful > 0) {
517
- toast.success(t('fileManagement.toasts.visibilitySuccess', { count: successful, visibility }));
518
- // Reflect the new visibility in the cached files.
519
- for (const fileId of selectedIds) {
520
- patchFileMetadataInCache(queryClient, targetUserId, fileId, { visibility });
521
- }
522
- }
523
- if (failed > 0) {
524
- toast.error(t('fileManagement.toasts.visibilityFailed', { count: failed }));
525
- }
526
-
527
- invalidateFiles();
545
+ await oxyServices.assets.delete(fileId);
546
+ removeFileFromCache(queryClient, targetUserId, fileId);
547
+ return { success: true, fileId };
528
548
  } catch (error: unknown) {
529
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
549
+ return { success: false, fileId, error };
530
550
  }
531
- }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
532
-
533
- const handleVisibilityChange = useCallback(async () => {
534
- if (selectedIds.size === 0) return;
535
- const visibility = await presentActionSheet<'private' | 'public' | 'unlisted'>({
536
- title: t('fileManagement.changeVisibility') || 'Change Visibility',
537
- message: t('fileManagement.changeVisibilityConfirm', { count: selectedIds.size }),
538
- options: [
539
- { label: t('fileManagement.private') || 'Private', value: 'private' },
540
- { label: t('fileManagement.public') || 'Public', value: 'public' },
541
- { label: t('fileManagement.unlisted') || 'Unlisted', value: 'unlisted' },
542
- ],
543
- cancelLabel: t('common.cancel') || 'Cancel',
551
+ });
552
+
553
+ const results = await Promise.allSettled(deletePromises);
554
+ const successful = results.filter((r) => r.status === 'fulfilled' && r.value.success).length;
555
+ const failed = results.length - successful;
556
+
557
+ if (successful > 0) {
558
+ toast.success(t('fileManagement.toasts.bulkDeleteSuccess', { count: successful }));
559
+ }
560
+ if (failed > 0) {
561
+ toast.error(t('fileManagement.toasts.bulkDeleteFailed', { count: failed }));
562
+ }
563
+
564
+ setSelectedIds(new Set());
565
+ invalidateFiles();
566
+ } catch (error: unknown) {
567
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
568
+ }
569
+ }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
570
+
571
+ const handleBulkVisibilityChange = useCallback(
572
+ async (visibility: 'private' | 'public' | 'unlisted') => {
573
+ if (selectedIds.size === 0) return;
574
+
575
+ try {
576
+ const updatePromises = Array.from(selectedIds).map(async (fileId) => {
577
+ try {
578
+ await oxyServices.assets.setVisibility(fileId, visibility);
579
+ return { success: true, fileId };
580
+ } catch (error: unknown) {
581
+ return { success: false, fileId, error };
582
+ }
544
583
  });
545
- if (visibility) await handleBulkVisibilityChange(visibility);
546
- }, [selectedIds.size, handleBulkVisibilityChange, t]);
547
584
 
548
- // Unified download function - works on all platforms
549
- const handleFileDownload = useCallback(async (fileId: string, filename: string) => {
550
- try {
551
- // Resolve an authenticated, private-safe URL. The synchronous
552
- // `getFileDownloadUrl` yields the public CDN origin, which 404s for
553
- // private assets (the default visibility).
554
- const downloadUrl = await oxyServices.assets.url(fileId);
555
-
556
- // For web platforms, use link download
557
- if (typeof window !== 'undefined' && window.document) {
558
- try {
559
- // Try simple link download first
560
- const link = document.createElement('a');
561
- link.href = downloadUrl;
562
- link.download = filename;
563
- link.target = '_blank';
564
- document.body.appendChild(link);
565
- link.click();
566
- document.body.removeChild(link);
567
- toast.success(t('fileManagement.toasts.downloadStarted'));
568
- } catch (linkError) {
569
- // Fallback to authenticated download
570
- const blob = await oxyServices.assets.blob(fileId);
571
- const url = URL.createObjectURL(blob);
572
-
573
- const link = document.createElement('a');
574
- link.href = url;
575
- link.download = filename;
576
- document.body.appendChild(link);
577
- link.click();
578
- document.body.removeChild(link);
579
-
580
- // Clean up the blob URL
581
- URL.revokeObjectURL(url);
582
- toast.success(t('fileManagement.toasts.downloadSuccess'));
583
- }
584
- } else {
585
- // For mobile, open the URL (user can save from browser)
586
- // Note: This is a simplified approach - for full mobile support,
587
- // consider using expo-file-system or react-native-fs
588
- toast.info(t('fileManagement.toasts.downloadMobile'));
589
- }
590
- } catch (error: unknown) {
591
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
585
+ const results = await Promise.allSettled(updatePromises);
586
+ const successful = results.filter(
587
+ (r) => r.status === 'fulfilled' && r.value.success,
588
+ ).length;
589
+ const failed = results.length - successful;
590
+
591
+ if (successful > 0) {
592
+ toast.success(
593
+ t('fileManagement.toasts.visibilitySuccess', { count: successful, visibility }),
594
+ );
595
+ // Reflect the new visibility in the cached files.
596
+ for (const fileId of selectedIds) {
597
+ patchFileMetadataInCache(queryClient, targetUserId, fileId, { visibility });
598
+ }
599
+ }
600
+ if (failed > 0) {
601
+ toast.error(t('fileManagement.toasts.visibilityFailed', { count: failed }));
592
602
  }
593
- }, [oxyServices, t]);
594
603
 
595
- const handleFileOpen = useCallback(async (file: FileMetadata) => {
596
- if (selectMode) {
597
- toggleSelect(file);
598
- return;
604
+ invalidateFiles();
605
+ } catch (error: unknown) {
606
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
607
+ }
608
+ },
609
+ [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t],
610
+ );
611
+
612
+ const handleVisibilityChange = useCallback(async () => {
613
+ if (selectedIds.size === 0) return;
614
+ const visibility = await presentActionSheet<'private' | 'public' | 'unlisted'>({
615
+ title: t('fileManagement.changeVisibility') || 'Change Visibility',
616
+ message: t('fileManagement.changeVisibilityConfirm', { count: selectedIds.size }),
617
+ options: [
618
+ { label: t('fileManagement.private') || 'Private', value: 'private' },
619
+ { label: t('fileManagement.public') || 'Public', value: 'public' },
620
+ { label: t('fileManagement.unlisted') || 'Unlisted', value: 'unlisted' },
621
+ ],
622
+ cancelLabel: t('common.cancel') || 'Cancel',
623
+ });
624
+ if (visibility) await handleBulkVisibilityChange(visibility);
625
+ }, [selectedIds.size, handleBulkVisibilityChange, t]);
626
+
627
+ // Unified download function - works on all platforms
628
+ const handleFileDownload = useCallback(
629
+ async (fileId: string, filename: string) => {
630
+ try {
631
+ // Resolve an authenticated, private-safe URL. The synchronous
632
+ // `getFileDownloadUrl` yields the public CDN origin, which 404s for
633
+ // private assets (the default visibility).
634
+ const downloadUrl = await oxyServices.assets.url(fileId);
635
+
636
+ // For web platforms, use link download
637
+ if (typeof window !== 'undefined' && window.document) {
638
+ try {
639
+ // Try simple link download first
640
+ const link = document.createElement('a');
641
+ link.href = downloadUrl;
642
+ link.download = filename;
643
+ link.target = '_blank';
644
+ document.body.appendChild(link);
645
+ link.click();
646
+ document.body.removeChild(link);
647
+ toast.success(t('fileManagement.toasts.downloadStarted'));
648
+ } catch (linkError) {
649
+ // Fallback to authenticated download
650
+ const blob = await oxyServices.assets.blob(fileId);
651
+ const url = URL.createObjectURL(blob);
652
+
653
+ const link = document.createElement('a');
654
+ link.href = url;
655
+ link.download = filename;
656
+ document.body.appendChild(link);
657
+ link.click();
658
+ document.body.removeChild(link);
659
+
660
+ // Clean up the blob URL
661
+ URL.revokeObjectURL(url);
662
+ toast.success(t('fileManagement.toasts.downloadSuccess'));
663
+ }
664
+ } else {
665
+ // For mobile, open the URL (user can save from browser)
666
+ // Note: This is a simplified approach - for full mobile support,
667
+ // consider using expo-file-system or react-native-fs
668
+ toast.info(t('fileManagement.toasts.downloadMobile'));
599
669
  }
600
- try {
601
- setLoadingFileContent(true);
602
- setOpenedFile(file);
603
-
604
- // For text files, images, and other viewable content, try to load the content
605
- if (file.contentType.startsWith('text/') ||
606
- file.contentType.includes('json') ||
607
- file.contentType.includes('xml') ||
608
- file.contentType.includes('javascript') ||
609
- file.contentType.includes('typescript') ||
610
- file.contentType.startsWith('image/') ||
611
- file.contentType.includes('pdf') ||
612
- file.contentType.startsWith('video/') ||
613
- file.contentType.startsWith('audio/')) {
614
-
615
- try {
616
- if (file.contentType.startsWith('image/') ||
617
- file.contentType.includes('pdf') ||
618
- file.contentType.startsWith('video/') ||
619
- file.contentType.startsWith('audio/')) {
620
- // For images, PDFs, videos, and audio, we render the URL
621
- // directly. Resolve the authenticated, private-safe URL —
622
- // the synchronous public-CDN URL 404s for private assets.
623
- const downloadUrl = await oxyServices.assets.url(file.id);
624
- setFileContent(downloadUrl);
625
- } else {
626
- // For text files, get the content using authenticated request
627
- const content = await oxyServices.assets.text(file.id);
628
- setFileContent(content);
629
- }
630
- } catch (error: unknown) {
631
- if (getErrorMessage(error)?.includes('404') || getErrorMessage(error)?.includes('not found')) {
632
- toast.error(t('fileManagement.toasts.fileNotFoundContent'));
633
- } else {
634
- toast.error(t('fileManagement.toasts.loadContentFailed'));
635
- }
636
- setFileContent(null);
637
- }
670
+ } catch (error: unknown) {
671
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
672
+ }
673
+ },
674
+ [oxyServices, t],
675
+ );
676
+
677
+ const handleFileOpen = useCallback(
678
+ async (file: FileMetadata) => {
679
+ if (selectMode) {
680
+ toggleSelect(file);
681
+ return;
682
+ }
683
+ try {
684
+ setLoadingFileContent(true);
685
+ setOpenedFile(file);
686
+
687
+ // For text files, images, and other viewable content, try to load the content
688
+ if (
689
+ file.contentType.startsWith('text/') ||
690
+ file.contentType.includes('json') ||
691
+ file.contentType.includes('xml') ||
692
+ file.contentType.includes('javascript') ||
693
+ file.contentType.includes('typescript') ||
694
+ file.contentType.startsWith('image/') ||
695
+ file.contentType.includes('pdf') ||
696
+ file.contentType.startsWith('video/') ||
697
+ file.contentType.startsWith('audio/')
698
+ ) {
699
+ try {
700
+ if (
701
+ file.contentType.startsWith('image/') ||
702
+ file.contentType.includes('pdf') ||
703
+ file.contentType.startsWith('video/') ||
704
+ file.contentType.startsWith('audio/')
705
+ ) {
706
+ // For images, PDFs, videos, and audio, we render the URL
707
+ // directly. Resolve the authenticated, private-safe URL —
708
+ // the synchronous public-CDN URL 404s for private assets.
709
+ const downloadUrl = await oxyServices.assets.url(file.id);
710
+ setFileContent(downloadUrl);
638
711
  } else {
639
- // For non-viewable files, don't load content
640
- setFileContent(null);
712
+ // For text files, get the content using authenticated request
713
+ const content = await oxyServices.assets.text(file.id);
714
+ setFileContent(content);
641
715
  }
642
- } catch (error: unknown) {
643
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.openFailed'));
644
- } finally {
645
- setLoadingFileContent(false);
716
+ } catch (error: unknown) {
717
+ if (
718
+ getErrorMessage(error)?.includes('404') ||
719
+ getErrorMessage(error)?.includes('not found')
720
+ ) {
721
+ toast.error(t('fileManagement.toasts.fileNotFoundContent'));
722
+ } else {
723
+ toast.error(t('fileManagement.toasts.loadContentFailed'));
724
+ }
725
+ setFileContent(null);
726
+ }
727
+ } else {
728
+ // For non-viewable files, don't load content
729
+ setFileContent(null);
646
730
  }
647
- }, [selectMode, toggleSelect, oxyServices, t]);
648
-
649
- const handleCloseFile = useCallback(() => {
650
- setOpenedFile(null);
651
- setFileContent(null);
652
- setShowFileDetailsInViewer(false);
653
- // Don't reset view mode when closing a file
654
- }, []);
655
-
656
- const showFileDetailsModal = useCallback((file: FileMetadata) => {
657
- presentFileDetails({
658
- file,
659
- onDownload: handleFileDownload,
660
- onDelete: confirmFileDelete,
661
- isOwner: user?.id === targetUserId,
662
- });
663
- }, [handleFileDownload, confirmFileDelete, user?.id, targetUserId]);
664
-
665
- const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
666
- const downloadUrl = thumbSourceFor(photo);
667
-
668
- return (
669
- <TouchableOpacity
670
- key={photo.id}
671
- className="rounded-[6px] overflow-hidden relative"
672
- style={{
673
- width,
674
- height,
675
- ...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: colors.primary } : {}),
676
- ...(selectMode && multiSelect && selectedIds.size > 0 && !selectedIds.has(photo.id) ? { opacity: 0.4 } : {}),
731
+ } catch (error: unknown) {
732
+ toast.error(getErrorMessage(error) || t('fileManagement.toasts.openFailed'));
733
+ } finally {
734
+ setLoadingFileContent(false);
735
+ }
736
+ },
737
+ [selectMode, toggleSelect, oxyServices, t],
738
+ );
739
+
740
+ const handleCloseFile = useCallback(() => {
741
+ setOpenedFile(null);
742
+ setFileContent(null);
743
+ setShowFileDetailsInViewer(false);
744
+ // Don't reset view mode when closing a file
745
+ }, []);
746
+
747
+ const showFileDetailsModal = useCallback(
748
+ (file: FileMetadata) => {
749
+ presentFileDetails({
750
+ file,
751
+ onDownload: handleFileDownload,
752
+ onDelete: confirmFileDelete,
753
+ isOwner: user?.id === targetUserId,
754
+ });
755
+ },
756
+ [handleFileDownload, confirmFileDelete, user?.id, targetUserId],
757
+ );
758
+
759
+ const renderJustifiedPhotoItem = useCallback(
760
+ (photo: FileMetadata, width: number, height: number, isLast: boolean) => {
761
+ const downloadUrl = thumbSourceFor(photo);
762
+
763
+ return (
764
+ <TouchableOpacity
765
+ key={photo.id}
766
+ className="rounded-[6px] overflow-hidden relative"
767
+ style={{
768
+ width,
769
+ height,
770
+ ...(selectMode && selectedIds.has(photo.id)
771
+ ? { borderWidth: 2, borderColor: colors.primary }
772
+ : {}),
773
+ ...(selectMode && multiSelect && selectedIds.size > 0 && !selectedIds.has(photo.id)
774
+ ? { opacity: 0.4 }
775
+ : {}),
776
+ }}
777
+ onPress={() => handleFileOpen(photo)}
778
+ activeOpacity={0.8}
779
+ >
780
+ <View className="w-full h-full relative rounded-[6px] overflow-hidden">
781
+ <ExpoImage
782
+ source={{ uri: downloadUrl }}
783
+ style={screenStyles.justifiedPhotoImage}
784
+ contentFit="cover"
785
+ transition={120}
786
+ cachePolicy="memory-disk"
787
+ onError={() => {
788
+ // Photo failed to load, will show placeholder
789
+ }}
790
+ accessibilityLabel={photo.filename}
791
+ />
792
+ {selectMode && (
793
+ <View
794
+ className="absolute top-[4px] right-[4px] rounded-[12px] p-[2px]"
795
+ style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}
796
+ >
797
+ <Ionicons
798
+ name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'}
799
+ size={20}
800
+ color={selectedIds.has(photo.id) ? colors.primary : colors.text}
801
+ />
802
+ </View>
803
+ )}
804
+ </View>
805
+ </TouchableOpacity>
806
+ );
807
+ },
808
+ [
809
+ thumbSourceFor,
810
+ selectMode,
811
+ selectedIds,
812
+ multiSelect,
813
+ colors.primary,
814
+ colors.text,
815
+ handleFileOpen,
816
+ ],
817
+ );
818
+
819
+ // SettingsListItem-based file items (for 'all' view)
820
+ const groupedFileItems: FileListItem[] = useMemo(() => {
821
+ // filteredFiles is already sorted, so just use it directly
822
+ const sortedFiles = filteredFiles;
823
+
824
+ return sortedFiles.map((file) => {
825
+ const isImage = file.contentType.startsWith('image/');
826
+ const isPDF = file.contentType.includes('pdf');
827
+ const isVideo = file.contentType.startsWith('video/');
828
+ const hasPreview = isImage || isPDF || isVideo;
829
+ const isSelected = selectedIds.has(file.id);
830
+
831
+ // Create icon for preview thumbnails (36x36)
832
+ let fileIcon: React.ReactNode | undefined;
833
+ if (hasPreview) {
834
+ if (isImage) {
835
+ fileIcon = (
836
+ <View style={{ width: 36, height: 36, borderRadius: 18, overflow: 'hidden' }}>
837
+ <ExpoImage
838
+ source={{ uri: thumbSourceFor(file) }}
839
+ style={{ width: 36, height: 36 }}
840
+ contentFit="cover"
841
+ transition={120}
842
+ cachePolicy="memory-disk"
843
+ onError={() => {
844
+ // Image preview failed to load - will fallback to icon
677
845
  }}
678
- onPress={() => handleFileOpen(photo)}
679
- activeOpacity={0.8}
846
+ accessibilityLabel={file.filename}
847
+ />
848
+ </View>
849
+ );
850
+ } else if (isVideo) {
851
+ fileIcon = (
852
+ <View
853
+ style={{
854
+ width: 36,
855
+ height: 36,
856
+ borderRadius: 18,
857
+ overflow: 'hidden',
858
+ backgroundColor: '#000000',
859
+ position: 'relative',
860
+ }}
680
861
  >
681
- <View className="w-full h-full relative rounded-[6px] overflow-hidden">
682
- <ExpoImage
683
- source={{ uri: downloadUrl }}
684
- style={screenStyles.justifiedPhotoImage}
685
- contentFit="cover"
686
- transition={120}
687
- cachePolicy="memory-disk"
688
- onError={() => {
689
- // Photo failed to load, will show placeholder
690
- }}
691
- accessibilityLabel={photo.filename}
692
- />
693
- {selectMode && (
694
- <View className="absolute top-[4px] right-[4px] rounded-[12px] p-[2px]" style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}>
695
- <Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? colors.primary : colors.text} />
696
- </View>
697
- )}
698
- </View>
699
- </TouchableOpacity>
700
- );
701
- }, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text, handleFileOpen]);
702
-
703
- // SettingsListItem-based file items (for 'all' view)
704
- const groupedFileItems: FileListItem[] = useMemo(() => {
705
- // filteredFiles is already sorted, so just use it directly
706
- const sortedFiles = filteredFiles;
707
-
708
- return sortedFiles.map((file) => {
709
- const isImage = file.contentType.startsWith('image/');
710
- const isPDF = file.contentType.includes('pdf');
711
- const isVideo = file.contentType.startsWith('video/');
712
- const hasPreview = isImage || isPDF || isVideo;
713
- const isSelected = selectedIds.has(file.id);
714
-
715
- // Create icon for preview thumbnails (36x36)
716
- let fileIcon: React.ReactNode | undefined;
717
- if (hasPreview) {
718
- if (isImage) {
719
- fileIcon = (
720
- <View style={{ width: 36, height: 36, borderRadius: 18, overflow: 'hidden' }}>
721
- <ExpoImage
722
- source={{ uri: thumbSourceFor(file) }}
723
- style={{ width: 36, height: 36 }}
724
- contentFit="cover"
725
- transition={120}
726
- cachePolicy="memory-disk"
727
- onError={() => {
728
- // Image preview failed to load - will fallback to icon
729
- }}
730
- accessibilityLabel={file.filename}
731
- />
732
- </View>
733
- );
734
- } else if (isVideo) {
735
- fileIcon = (
736
- <View style={{ width: 36, height: 36, borderRadius: 18, overflow: 'hidden', backgroundColor: '#000000', position: 'relative' }}>
737
- <ExpoImage
738
- source={{ uri: thumbSourceFor(file) }}
739
- style={{ width: 36, height: 36 }}
740
- contentFit="cover"
741
- transition={120}
742
- cachePolicy="memory-disk"
743
- onError={(_: unknown) => {
744
- // If thumbnail not available, we still show icon overlay
745
- }}
746
- accessibilityLabel={`${file.filename} video thumbnail`}
747
- />
748
- <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0.25)' }}>
749
- <Ionicons name="play" size={16} color="#FFFFFF" />
750
- </View>
751
- </View>
752
- );
753
- } else if (isPDF) {
754
- fileIcon = (
755
- <View style={{ width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center', backgroundColor: '#FF6B6B20' }}>
756
- <Ionicons name="document" size={20} color={colors.primary} />
757
- </View>
758
- );
759
- }
760
- }
761
-
762
- return {
763
- id: file.id,
764
- icon: fileIcon ?? (!hasPreview ? <Ionicons name={getFileIcon(file.contentType)} size={20} color={colors.primary} /> : undefined),
765
- title: file.filename,
766
- description: `${formatFileSize(file.length)} • ${new Date(file.uploadDate).toLocaleDateString()}`,
767
- onPress: () => {
768
- // Support selection in regular mode with long press or if already selecting
769
- if (!selectMode && selectedIds.size > 0) {
770
- // If already in selection mode (some files selected), toggle selection
771
- toggleSelect(file);
772
- } else {
773
- handleFileOpen(file);
774
- }
775
- },
776
- // Hide action buttons when selecting (in selectMode or bulk operations mode)
777
- rightElement: (!selectMode && selectedIds.size === 0) ? (
778
- <View className="flex-row items-center gap-1.5 ml-3">
779
- {(isImage || isVideo || file.contentType.includes('pdf')) ? <Button appearance="subtle" tone="neutral" size="sm" iconOnly stopPropagation
780
- accessibilityLabel={`Preview ${file.filename}`} onPress={() => handleFileOpen(file)}
781
- icon={<Ionicons name="eye" size={18} color={colors.text} />} /> : null}
782
- <Button appearance="subtle" tone="neutral" size="sm" iconOnly stopPropagation
783
- accessibilityLabel={`Download ${file.filename}`} onPress={() => handleFileDownload(file.id, file.filename)}
784
- icon={<Ionicons name="download" size={18} color={colors.text} />} />
785
- <Button appearance="subtle" tone="danger" size="sm" iconOnly stopPropagation loading={deletingId === file.id}
786
- accessibilityLabel={`Delete ${file.filename}`} onPress={() => confirmFileDelete(file.id, file.filename)}
787
- icon={<Ionicons name="trash" size={18} color={colors.error} />} />
788
- </View>
789
- ) : undefined,
790
- };
791
- });
792
- }, [filteredFiles, colors, deletingId, toggleSelect, handleFileDownload, confirmFileDelete, handleFileOpen, thumbSourceFor, selectMode, selectedIds]);
793
-
794
- // Browse-themed terminal load-failure state — DISTINCT from the empty state.
795
- const renderLoadError = () => (
796
- <FileLibraryError
797
- title={t('fileManagement.loadError.title')}
798
- description={t('fileManagement.loadError.description')}
799
- retryLabel={t('fileManagement.retry')}
800
- onRetry={() => filesQuery.refetch()}
801
- iconColor={colors.error}
802
- titleColor={colors.text}
803
- descriptionColor={colors.textSecondary}
804
- />
805
- );
806
-
807
- // Browse "photos" view. The justified grid owns its own dimension
808
- // measurement + reflow (see JustifiedPhotoGrid); this only supplies the
809
- // photo set, the private-safe URL resolver, and the tile renderer.
810
- const renderPhotoGrid = () => {
811
- const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
812
-
813
- if (photos.length === 0) {
814
- if (hasLoadError) return renderLoadError();
815
- return (
816
- <View className="items-center py-[40px] px-[24px]">
817
- <Ionicons name="images-outline" size={64} color={colors.textTertiary} />
818
- <Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>{t('fileManagement.emptyPhotos.title')}</Text>
819
- <Text className="text-[16px] text-center leading-[24px] mb-[32px]" style={{ color: colors.textSecondary }}> {
820
- user?.id === targetUserId
821
- ? t('fileManagement.emptyPhotos.ownDescription')
822
- : t('fileManagement.emptyPhotos.otherDescription')
823
- } </Text>
824
- {user?.id === targetUserId && (
825
- <Button onPress={handleFileUpload} loading={uploading || isPickingDocument}>{t('fileManagement.uploadPhotos')}</Button>
826
- )}
827
- </View>
828
- );
829
- }
830
-
831
- return (
832
- <ScrollView
833
- ref={photoScrollViewRef}
834
- className="flex-1"
835
- contentContainerClassName="p-[10px]"
836
- refreshControl={
837
- <RefreshControl
838
- refreshing={isRefreshingFiles}
839
- onRefresh={() => filesQuery.refetch()}
840
- tintColor={colors.primary}
841
- />
842
- }
843
- showsVerticalScrollIndicator={false}
844
- onScroll={({ nativeEvent }) => {
845
- const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
846
- const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
847
- if (distanceFromBottom < 200 && !isFetchingMore && hasMoreFiles) {
848
- filesQuery.fetchNextPage();
849
- }
862
+ <ExpoImage
863
+ source={{ uri: thumbSourceFor(file) }}
864
+ style={{ width: 36, height: 36 }}
865
+ contentFit="cover"
866
+ transition={120}
867
+ cachePolicy="memory-disk"
868
+ onError={(_: unknown) => {
869
+ // If thumbnail not available, we still show icon overlay
850
870
  }}
851
- scrollEventThrottle={250}
871
+ accessibilityLabel={`${file.filename} video thumbnail`}
872
+ />
873
+ <View
874
+ style={{
875
+ position: 'absolute',
876
+ top: 0,
877
+ left: 0,
878
+ right: 0,
879
+ bottom: 0,
880
+ alignItems: 'center',
881
+ justifyContent: 'center',
882
+ backgroundColor: 'rgba(0,0,0,0.25)',
883
+ }}
884
+ >
885
+ <Ionicons name="play" size={16} color="#FFFFFF" />
886
+ </View>
887
+ </View>
888
+ );
889
+ } else if (isPDF) {
890
+ fileIcon = (
891
+ <View
892
+ style={{
893
+ width: 36,
894
+ height: 36,
895
+ borderRadius: 18,
896
+ alignItems: 'center',
897
+ justifyContent: 'center',
898
+ backgroundColor: '#FF6B6B20',
899
+ }}
852
900
  >
853
- <JustifiedPhotoGrid
854
- photos={photos}
855
- getThumbUrl={thumbSourceFor}
856
- renderJustifiedPhotoItem={renderJustifiedPhotoItem}
857
- textColor={colors.text}
858
- containerWidth={
859
- typeof containerWidth === 'number' && containerWidth > 0
860
- ? containerWidth
861
- : undefined
862
- }
901
+ <Ionicons name="document" size={20} color={colors.primary} />
902
+ </View>
903
+ );
904
+ }
905
+ }
906
+
907
+ return {
908
+ id: file.id,
909
+ icon:
910
+ fileIcon ??
911
+ (!hasPreview ? (
912
+ <Ionicons name={getFileIcon(file.contentType)} size={20} color={colors.primary} />
913
+ ) : undefined),
914
+ title: file.filename,
915
+ description: `${formatFileSize(file.length)} • ${new Date(file.uploadDate).toLocaleDateString()}`,
916
+ onPress: () => {
917
+ // Support selection in regular mode with long press or if already selecting
918
+ if (!selectMode && selectedIds.size > 0) {
919
+ // If already in selection mode (some files selected), toggle selection
920
+ toggleSelect(file);
921
+ } else {
922
+ handleFileOpen(file);
923
+ }
924
+ },
925
+ // Hide action buttons when selecting (in selectMode or bulk operations mode)
926
+ rightElement:
927
+ !selectMode && selectedIds.size === 0 ? (
928
+ <View className="flex-row items-center gap-1.5 ml-3">
929
+ {isImage || isVideo || file.contentType.includes('pdf') ? (
930
+ <Button
931
+ appearance="subtle"
932
+ tone="neutral"
933
+ size="sm"
934
+ iconOnly
935
+ stopPropagation
936
+ accessibilityLabel={`Preview ${file.filename}`}
937
+ onPress={() => handleFileOpen(file)}
938
+ icon={<Ionicons name="eye" size={18} color={colors.text} />}
863
939
  />
864
- </ScrollView>
865
- );
866
- };
867
-
868
- const renderEmptyState = () => hasLoadError ? renderLoadError() : (
940
+ ) : null}
941
+ <Button
942
+ appearance="subtle"
943
+ tone="neutral"
944
+ size="sm"
945
+ iconOnly
946
+ stopPropagation
947
+ accessibilityLabel={`Download ${file.filename}`}
948
+ onPress={() => handleFileDownload(file.id, file.filename)}
949
+ icon={<Ionicons name="download" size={18} color={colors.text} />}
950
+ />
951
+ <Button
952
+ appearance="subtle"
953
+ tone="danger"
954
+ size="sm"
955
+ iconOnly
956
+ stopPropagation
957
+ loading={deletingId === file.id}
958
+ accessibilityLabel={`Delete ${file.filename}`}
959
+ onPress={() => confirmFileDelete(file.id, file.filename)}
960
+ icon={<Ionicons name="trash" size={18} color={colors.error} />}
961
+ />
962
+ </View>
963
+ ) : undefined,
964
+ };
965
+ });
966
+ }, [
967
+ filteredFiles,
968
+ colors,
969
+ deletingId,
970
+ toggleSelect,
971
+ handleFileDownload,
972
+ confirmFileDelete,
973
+ handleFileOpen,
974
+ thumbSourceFor,
975
+ selectMode,
976
+ selectedIds,
977
+ ]);
978
+
979
+ // Browse-themed terminal load-failure state — DISTINCT from the empty state.
980
+ const renderLoadError = () => (
981
+ <FileLibraryError
982
+ title={t('fileManagement.loadError.title')}
983
+ description={t('fileManagement.loadError.description')}
984
+ retryLabel={t('fileManagement.retry')}
985
+ onRetry={() => filesQuery.refetch()}
986
+ iconColor={colors.error}
987
+ titleColor={colors.text}
988
+ descriptionColor={colors.textSecondary}
989
+ />
990
+ );
991
+
992
+ // Browse "photos" view. The justified grid owns its own dimension
993
+ // measurement + reflow (see JustifiedPhotoGrid); this only supplies the
994
+ // photo set, the private-safe URL resolver, and the tile renderer.
995
+ const renderPhotoGrid = () => {
996
+ const photos = filteredFiles.filter((file) => file.contentType.startsWith('image/'));
997
+
998
+ if (photos.length === 0) {
999
+ if (hasLoadError) return renderLoadError();
1000
+ return (
869
1001
  <View className="items-center py-[40px] px-[24px]">
870
- <Ionicons name="folder-open-outline" size={64} color={colors.textTertiary} />
871
- <Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>{t('fileManagement.emptyFiles.title')}</Text>
872
- <Text className="text-[16px] text-center leading-[24px] mb-[32px]" style={{ color: colors.textSecondary }}>
873
- {user?.id === targetUserId
874
- ? t('fileManagement.emptyFiles.ownDescription')
875
- : t('fileManagement.emptyFiles.otherDescription')
876
- }
877
- </Text>
878
- {user?.id === targetUserId && (
879
- <Button onPress={handleFileUpload} loading={uploading || isPickingDocument}>{t('fileManagement.uploadFiles')}</Button>
880
- )}
1002
+ <Ionicons name="images-outline" size={64} color={colors.textTertiary} />
1003
+ <Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>
1004
+ {t('fileManagement.emptyPhotos.title')}
1005
+ </Text>
1006
+ <Text
1007
+ className="text-[16px] text-center leading-[24px] mb-[32px]"
1008
+ style={{ color: colors.textSecondary }}
1009
+ >
1010
+ {' '}
1011
+ {user?.id === targetUserId
1012
+ ? t('fileManagement.emptyPhotos.ownDescription')
1013
+ : t('fileManagement.emptyPhotos.otherDescription')}{' '}
1014
+ </Text>
1015
+ {user?.id === targetUserId && (
1016
+ <Button onPress={handleFileUpload} loading={uploading || isPickingDocument}>
1017
+ {t('fileManagement.uploadPhotos')}
1018
+ </Button>
1019
+ )}
881
1020
  </View>
882
- );
1021
+ );
1022
+ }
883
1023
 
884
- // Loading state — Bloom's shared `Skeleton` primitives own the shimmer, so
885
- // this is pure layout: the top chrome (header / controls / search) plus a
886
- // photo grid of shimmering tiles sized with the same 3-per-row geometry the
887
- // real photo grid uses. No hand-rolled `Animated` shimmer.
888
- //
889
- // The image-only picker owns its OWN loading skeleton (rendered by
890
- // `PhotoPickerView` when its photo list is still loading), so it inherits the
891
- // picker's placement-aware container width / columns / tile size and the
892
- // Dialog-vs-bottom-sheet sizing. Skipping the browse chrome here is what keeps
893
- // the picker from flashing the wrong (file-manager) skeleton inside the Dialog.
894
- // The Dialog owns the nav header; this screen contributes its state-dependent
895
- // title/subtitle + action slots. The image-only picker mode is headerless
896
- // (PhotoPickerView owns its own bar), so this config is simply ignored there.
897
- const fmHeaderRight = useMemo<React.ReactNode>(() => {
898
- const actions = selectMode && multiSelect
899
- ? [
900
- { key: 'clear', label: t('fileManagement.clear'), onPress: () => setSelectedIds(new Set()), disabled: selectedIds.size === 0 },
901
- { key: 'confirm', label: t('fileManagement.confirm'), onPress: confirmMultiSelection, disabled: selectedIds.size === 0 },
902
- ]
903
- : !selectMode && selectedIds.size > 0
904
- ? [
905
- { key: 'clear', label: t('fileManagement.clear'), onPress: () => setSelectedIds(new Set()), disabled: false },
906
- { key: 'delete', label: t('fileManagement.delete', { count: selectedIds.size }), onPress: confirmBulkDelete, disabled: false },
907
- { key: 'visibility', label: t('fileManagement.visibility'), onPress: handleVisibilityChange, disabled: false },
908
- ]
909
- : [];
910
- if (actions.length === 0) return undefined;
911
- return (
912
- <View style={fmHeaderStyles.actionsRow}>
913
- {actions.map((a) => (
914
- <SurfaceHeaderAction key={a.key} label={a.label} onPress={a.onPress} disabled={a.disabled} />
915
- ))}
916
- </View>
917
- );
918
- }, [selectMode, multiSelect, selectedIds, t, confirmMultiSelection, confirmBulkDelete, handleVisibilityChange]);
919
-
920
- const fmHeaderBack = useMemo<React.ReactNode>(() => (
921
- <Pressable
922
- onPress={handleCancelUpload}
923
- accessibilityRole="button"
924
- accessibilityLabel={t('common.back') || 'Back'}
925
- hitSlop={8}
926
- style={fmHeaderStyles.backButton}
927
- >
928
- <Ionicons name="chevron-back" size={20} color={colors.text} />
929
- </Pressable>
930
- ), [handleCancelUpload, colors.text, t]);
931
-
932
- // --- Image-only picker: shared-header chrome -----------------------------
933
- // The flagship photo picker now rides the SAME Dialog nav header as every
934
- // screen (Cancel / "Choose Photo" / Upload) instead of painting its own bar.
935
- // Ownership + upload permission gate the Upload action, mirroring the picker's
936
- // empty-state upload button.
937
- const isOwner = user?.id === targetUserId;
938
- const allowUpload = isOwner && allowUploadInSelectMode;
939
-
940
- // Cancel = the nav bar's back affordance. A forward-nav picker (`onPicked`,
941
- // e.g. the avatar flow) pops its own frame; legacy callback callers keep their
942
- // close/back behaviour; a promise-based picker dismisses its surface.
943
- const pickerCancel = useCallback(() => {
944
- if (onPicked) {
945
- // Forward-nav picker: Cancel/back pops THIS frame, returning to the
946
- // frame that opened the picker — never closes the whole session.
947
- goBack?.();
948
- } else if (onSelect || onConfirmSelection) {
949
- if (onClose) onClose();
950
- else goBack?.();
951
- } else {
952
- dismiss?.();
953
- }
954
- }, [onPicked, onSelect, onConfirmSelection, onClose, goBack, dismiss]);
955
-
956
- // The picker's ONE trailing CTA, declared through the Dialog header's
957
- // `primaryAction` (a proper Bloom Button with loading/disabled) — the
958
- // multi-select confirm ("Done (N)") or, single-select, the Upload action whose
959
- // loading state reflects an in-flight device upload. Memoized so the header
960
- // does not thrash.
961
- const pickerPrimaryAction = useMemo<SurfaceHeaderContent['primaryAction']>(() => {
962
- if (multiSelect) {
963
- return {
964
- label: t('fileManagement.doneWithCount', { count: selectedIds.size }),
965
- onPress: confirmMultiSelection,
966
- disabled: selectedIds.size === 0,
967
- };
968
- }
969
- if (isOwner && allowUpload) {
970
- return {
971
- label: t('fileManagement.upload'),
972
- onPress: handleFileUpload,
973
- loading: uploading || isPickingDocument,
974
- };
1024
+ return (
1025
+ <ScrollView
1026
+ ref={photoScrollViewRef}
1027
+ className="flex-1"
1028
+ contentContainerClassName="p-[10px]"
1029
+ refreshControl={
1030
+ <RefreshControl
1031
+ refreshing={isRefreshingFiles}
1032
+ onRefresh={() => filesQuery.refetch()}
1033
+ tintColor={colors.primary}
1034
+ />
975
1035
  }
976
- return undefined;
977
- }, [
978
- multiSelect, selectedIds.size, confirmMultiSelection, isOwner, allowUpload,
979
- t, handleFileUpload, uploading, isPickingDocument,
980
- ]);
981
-
982
- useSurfaceHeader(
983
- showUploadPreview
984
- ? {
985
- title: t('fileManagement.reviewFiles'),
986
- subtitle: t('fileManagement.readyToUpload', { count: pendingFiles.length }),
987
- left: fmHeaderBack,
988
- largeTitle: false,
989
- }
990
- : isImageOnlyPicker
991
- ? {
992
- // ONE shared header — Cancel (back) / "Choose Photo" / Upload — in
993
- // `onImage` tone so the chrome stays legible over the black grid.
994
- title: t('fileManagement.choosePhoto'),
995
- largeTitle: false,
996
- onBack: pickerCancel,
997
- primaryAction: pickerPrimaryAction,
998
- tone: 'onImage',
999
- }
1000
- : {
1001
- title: selectMode
1002
- ? (multiSelect ? (maxSelection ? t('fileManagement.selectedWithMax', { count: selectedIds.size, max: maxSelection }) : t('fileManagement.selected', { count: selectedIds.size })) : t('fileManagement.selectFile'))
1003
- : (viewMode === 'photos' ? t('fileManagement.photos') : t('fileManagement.title')),
1004
- subtitle: selectMode
1005
- ? (multiSelect ? t('fileManagement.available', { count: filteredFiles.length }) : t('fileManagement.tapToSelect'))
1006
- : (filteredFiles.length === 1 ? t('fileManagement.itemCount', { count: filteredFiles.length }) : t('fileManagement.itemCount_plural', { count: filteredFiles.length })),
1007
- right: fmHeaderRight,
1008
- largeTitle: false,
1009
- },
1036
+ showsVerticalScrollIndicator={false}
1037
+ onScroll={({ nativeEvent }) => {
1038
+ const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
1039
+ const distanceFromBottom =
1040
+ contentSize.height - (contentOffset.y + layoutMeasurement.height);
1041
+ if (distanceFromBottom < 200 && !isFetchingMore && hasMoreFiles) {
1042
+ filesQuery.fetchNextPage();
1043
+ }
1044
+ }}
1045
+ scrollEventThrottle={250}
1046
+ >
1047
+ <JustifiedPhotoGrid
1048
+ photos={photos}
1049
+ getThumbUrl={thumbSourceFor}
1050
+ renderJustifiedPhotoItem={renderJustifiedPhotoItem}
1051
+ textColor={colors.text}
1052
+ containerWidth={
1053
+ typeof containerWidth === 'number' && containerWidth > 0 ? containerWidth : undefined
1054
+ }
1055
+ />
1056
+ </ScrollView>
1057
+ );
1058
+ };
1059
+
1060
+ const renderEmptyState = () =>
1061
+ hasLoadError ? (
1062
+ renderLoadError()
1063
+ ) : (
1064
+ <View className="items-center py-[40px] px-[24px]">
1065
+ <Ionicons name="folder-open-outline" size={64} color={colors.textTertiary} />
1066
+ <Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>
1067
+ {t('fileManagement.emptyFiles.title')}
1068
+ </Text>
1069
+ <Text
1070
+ className="text-[16px] text-center leading-[24px] mb-[32px]"
1071
+ style={{ color: colors.textSecondary }}
1072
+ >
1073
+ {user?.id === targetUserId
1074
+ ? t('fileManagement.emptyFiles.ownDescription')
1075
+ : t('fileManagement.emptyFiles.otherDescription')}
1076
+ </Text>
1077
+ {user?.id === targetUserId && (
1078
+ <Button onPress={handleFileUpload} loading={uploading || isPickingDocument}>
1079
+ {t('fileManagement.uploadFiles')}
1080
+ </Button>
1081
+ )}
1082
+ </View>
1010
1083
  );
1011
1084
 
1012
- if (isLoadingFiles && !isImageOnlyPicker) {
1013
- const GRID_PADDING = 10;
1014
- const TILE_GAP = 4;
1015
- const GRID_COLUMNS = 3;
1016
- const GRID_ROWS = 6;
1017
- const tileSize = Math.floor(
1018
- (safeContainerWidth - GRID_PADDING * 2 - TILE_GAP * (GRID_COLUMNS - 1)) / GRID_COLUMNS,
1019
- );
1020
-
1021
- return (
1022
- <View className="flex-1" style={{ backgroundColor }}>
1023
- {/* Header */}
1024
- <View
1025
- className="flex-row items-center justify-between px-[16px] py-[12px]"
1026
- style={{ borderBottomColor: colors.border, borderBottomWidth: StyleSheet.hairlineWidth }}
1027
- >
1028
- <Skeleton.Box width={44} height={44} borderRadius={12} />
1029
- <Skeleton.Col style={{ alignItems: 'center', marginHorizontal: 16, gap: 6 }}>
1030
- <Skeleton.Box width={140} height={20} />
1031
- <Skeleton.Box width={100} height={14} />
1032
- </Skeleton.Col>
1033
- <Skeleton.Box width={44} height={44} borderRadius={12} />
1034
- </View>
1035
-
1036
- {/* Controls bar */}
1037
- <Skeleton.Row style={{ alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 12, paddingVertical: 12 }}>
1038
- <Skeleton.Box width={100} height={36} borderRadius={18} />
1039
- <Skeleton.Box width={44} height={44} borderRadius={22} />
1040
- </Skeleton.Row>
1041
-
1042
- {/* Search bar */}
1043
- <View className="mx-[12px] mb-[12px]">
1044
- <Skeleton.Box width="100%" height={44} borderRadius={22} />
1045
- </View>
1046
-
1047
- {/* Photo grid — static skeleton placeholders; index keys are stable */}
1048
- <View style={{ padding: GRID_PADDING, gap: TILE_GAP }}>
1049
- {Array.from({ length: GRID_ROWS }, (_, row) => (
1050
- // biome-ignore lint/suspicious/noArrayIndexKey: fixed-size skeleton grid, order never changes
1051
- <Skeleton.Row key={`skeleton-row-${row}`} style={{ gap: TILE_GAP }}>
1052
- {Array.from({ length: GRID_COLUMNS }, (_, col) => (
1053
- // biome-ignore lint/suspicious/noArrayIndexKey: fixed-size skeleton grid, order never changes
1054
- <Skeleton.Box key={`skeleton-${row}-${col}`} width={tileSize} height={tileSize} borderRadius={6} />
1055
- ))}
1056
- </Skeleton.Row>
1057
- ))}
1058
- </View>
1059
- </View>
1060
- );
1061
- }
1062
-
1063
- // Dedicated flagship-style photo picker view used in the avatar-picker
1064
- // context (selectMode + image-only filter). The picker is intentionally
1065
- // minimal: black backdrop, edge-to-edge grid, translucent header. The
1066
- // standalone file manager (non-picker browse) keeps the existing UI.
1067
- if (isImageOnlyPicker && !showUploadPreview) {
1068
- const photosOnly = filteredFiles.filter(
1069
- (file) => file.contentType.startsWith('image/'),
1070
- );
1071
-
1072
- // Chrome (Cancel / "Choose Photo" / Upload) lives on the shared Dialog nav
1073
- // header (see the `useSurfaceHeader` call above); the picker owns only the
1074
- // full-bleed photo grid as content.
1075
- return (
1076
- <PhotoPickerView
1077
- photos={photosOnly}
1078
- selectedIds={selectedIds}
1079
- multiSelect={multiSelect}
1080
- maxSelection={maxSelection}
1081
- allowUpload={allowUpload}
1082
- refreshing={isRefreshingFiles}
1083
- uploading={uploading}
1084
- isPickingDocument={isPickingDocument}
1085
- hasMore={hasMoreFiles}
1086
- loadingMore={isFetchingMore}
1087
- loading={isLoadingFiles}
1088
- loadError={hasLoadError}
1089
- onRetry={() => filesQuery.refetch()}
1090
- reduceMotion={reduceMotion}
1091
- getThumbUrl={thumbSourceFor}
1092
- primaryColor={colors.primary}
1093
- isOwner={isOwner}
1094
- onTogglePhoto={toggleSelect}
1095
- // Long-press preview surfaces the file-detail panel on the stack.
1096
- onPreviewPhoto={(file) => showFileDetailsModal(file)}
1097
- onUpload={handleFileUpload}
1098
- onRefresh={() => filesQuery.refetch()}
1099
- onLoadMore={() => filesQuery.fetchNextPage()}
1100
- t={t}
1101
- />
1102
- );
1085
+ // Loading state — Bloom's shared `Skeleton` primitives own the shimmer, so
1086
+ // this is pure layout: the top chrome (header / controls / search) plus a
1087
+ // photo grid of shimmering tiles sized with the same 3-per-row geometry the
1088
+ // real photo grid uses. No hand-rolled `Animated` shimmer.
1089
+ //
1090
+ // The image-only picker owns its OWN loading skeleton (rendered by
1091
+ // `PhotoPickerView` when its photo list is still loading), so it inherits the
1092
+ // picker's placement-aware container width / columns / tile size and the
1093
+ // Dialog-vs-bottom-sheet sizing. Skipping the browse chrome here is what keeps
1094
+ // the picker from flashing the wrong (file-manager) skeleton inside the Dialog.
1095
+ // The Dialog owns the nav header; this screen contributes its state-dependent
1096
+ // title/subtitle + action slots. The image-only picker mode is headerless
1097
+ // (PhotoPickerView owns its own bar), so this config is simply ignored there.
1098
+ const fmHeaderRight = useMemo<React.ReactNode>(() => {
1099
+ const actions =
1100
+ selectMode && multiSelect
1101
+ ? [
1102
+ {
1103
+ key: 'clear',
1104
+ label: t('fileManagement.clear'),
1105
+ onPress: () => setSelectedIds(new Set()),
1106
+ disabled: selectedIds.size === 0,
1107
+ },
1108
+ {
1109
+ key: 'confirm',
1110
+ label: t('fileManagement.confirm'),
1111
+ onPress: confirmMultiSelection,
1112
+ disabled: selectedIds.size === 0,
1113
+ },
1114
+ ]
1115
+ : !selectMode && selectedIds.size > 0
1116
+ ? [
1117
+ {
1118
+ key: 'clear',
1119
+ label: t('fileManagement.clear'),
1120
+ onPress: () => setSelectedIds(new Set()),
1121
+ disabled: false,
1122
+ },
1123
+ {
1124
+ key: 'delete',
1125
+ label: t('fileManagement.delete', { count: selectedIds.size }),
1126
+ onPress: confirmBulkDelete,
1127
+ disabled: false,
1128
+ },
1129
+ {
1130
+ key: 'visibility',
1131
+ label: t('fileManagement.visibility'),
1132
+ onPress: handleVisibilityChange,
1133
+ disabled: false,
1134
+ },
1135
+ ]
1136
+ : [];
1137
+ if (actions.length === 0) return undefined;
1138
+ return (
1139
+ <View style={fmHeaderStyles.actionsRow}>
1140
+ {actions.map((a) => (
1141
+ <SurfaceHeaderAction
1142
+ key={a.key}
1143
+ label={a.label}
1144
+ onPress={a.onPress}
1145
+ disabled={a.disabled}
1146
+ />
1147
+ ))}
1148
+ </View>
1149
+ );
1150
+ }, [
1151
+ selectMode,
1152
+ multiSelect,
1153
+ selectedIds,
1154
+ t,
1155
+ confirmMultiSelection,
1156
+ confirmBulkDelete,
1157
+ handleVisibilityChange,
1158
+ ]);
1159
+
1160
+ const fmHeaderBack = useMemo<React.ReactNode>(
1161
+ () => (
1162
+ <Pressable
1163
+ onPress={handleCancelUpload}
1164
+ accessibilityRole="button"
1165
+ accessibilityLabel={t('common.back') || 'Back'}
1166
+ hitSlop={8}
1167
+ style={fmHeaderStyles.backButton}
1168
+ >
1169
+ <Ionicons name="chevron-back" size={20} color={colors.text} />
1170
+ </Pressable>
1171
+ ),
1172
+ [handleCancelUpload, colors.text, t],
1173
+ );
1174
+
1175
+ // --- Image-only picker: shared-header chrome -----------------------------
1176
+ // The flagship photo picker now rides the SAME Dialog nav header as every
1177
+ // screen (Cancel / "Choose Photo" / Upload) instead of painting its own bar.
1178
+ // Ownership + upload permission gate the Upload action, mirroring the picker's
1179
+ // empty-state upload button.
1180
+ const isOwner = user?.id === targetUserId;
1181
+ const allowUpload = isOwner && allowUploadInSelectMode;
1182
+
1183
+ // Cancel = the nav bar's back affordance. A forward-nav picker (`onPicked`,
1184
+ // e.g. the avatar flow) pops its own frame; legacy callback callers keep their
1185
+ // close/back behaviour; a promise-based picker dismisses its surface.
1186
+ const pickerCancel = useCallback(() => {
1187
+ if (onPicked) {
1188
+ // Forward-nav picker: Cancel/back pops THIS frame, returning to the
1189
+ // frame that opened the picker — never closes the whole session.
1190
+ goBack?.();
1191
+ } else if (onSelect || onConfirmSelection) {
1192
+ if (onClose) onClose();
1193
+ else goBack?.();
1194
+ } else {
1195
+ dismiss?.();
1103
1196
  }
1104
-
1105
- // If a file is opened, show the file viewer
1106
- if (!selectMode && openedFile) {
1107
- return (
1108
- <FileViewer
1109
- file={openedFile}
1110
- fileContent={fileContent}
1111
- loadingFileContent={loadingFileContent}
1112
- showFileDetailsInViewer={showFileDetailsInViewer}
1113
- onToggleDetails={() => setShowFileDetailsInViewer(!showFileDetailsInViewer)}
1114
- onClose={handleCloseFile}
1115
- onDownload={handleFileDownload}
1116
- onDelete={confirmFileDelete}
1117
- isOwner={user?.id === targetUserId}
1118
- />
1119
- );
1197
+ }, [onPicked, onSelect, onConfirmSelection, onClose, goBack, dismiss]);
1198
+
1199
+ // The picker's ONE trailing CTA, declared through the Dialog header's
1200
+ // `primaryAction` (a proper Bloom Button with loading/disabled) — the
1201
+ // multi-select confirm ("Done (N)") or, single-select, the Upload action whose
1202
+ // loading state reflects an in-flight device upload. Memoized so the header
1203
+ // does not thrash.
1204
+ const pickerPrimaryAction = useMemo<SurfaceHeaderContent['primaryAction']>(() => {
1205
+ if (multiSelect) {
1206
+ return {
1207
+ label: t('fileManagement.doneWithCount', { count: selectedIds.size }),
1208
+ onPress: confirmMultiSelection,
1209
+ disabled: selectedIds.size === 0,
1210
+ };
1120
1211
  }
1121
-
1122
- // If upload preview is showing, render it inline instead of the file list
1123
- if (showUploadPreview) {
1124
- return (
1125
- <View className="flex-1">
1126
- <UploadPreview
1127
- pendingFiles={pendingFiles}
1128
- onConfirm={handleConfirmUpload}
1129
- onCancel={handleCancelUpload}
1130
- onRemoveFile={removePendingFile}
1131
- inline={true}
1132
- />
1133
- </View>
1134
- );
1212
+ if (isOwner && allowUpload) {
1213
+ return {
1214
+ label: t('fileManagement.upload'),
1215
+ onPress: handleFileUpload,
1216
+ loading: uploading || isPickingDocument,
1217
+ };
1135
1218
  }
1219
+ return undefined;
1220
+ }, [
1221
+ multiSelect,
1222
+ selectedIds.size,
1223
+ confirmMultiSelection,
1224
+ isOwner,
1225
+ allowUpload,
1226
+ t,
1227
+ handleFileUpload,
1228
+ uploading,
1229
+ isPickingDocument,
1230
+ ]);
1231
+
1232
+ useSurfaceHeader(
1233
+ showUploadPreview
1234
+ ? {
1235
+ title: t('fileManagement.reviewFiles'),
1236
+ subtitle: t('fileManagement.readyToUpload', { count: pendingFiles.length }),
1237
+ left: fmHeaderBack,
1238
+ largeTitle: false,
1239
+ }
1240
+ : isImageOnlyPicker
1241
+ ? {
1242
+ // ONE shared header — Cancel (back) / "Choose Photo" / Upload — in
1243
+ // `onImage` tone so the chrome stays legible over the black grid.
1244
+ title: t('fileManagement.choosePhoto'),
1245
+ largeTitle: false,
1246
+ onBack: pickerCancel,
1247
+ primaryAction: pickerPrimaryAction,
1248
+ tone: 'onImage',
1249
+ }
1250
+ : {
1251
+ title: selectMode
1252
+ ? multiSelect
1253
+ ? maxSelection
1254
+ ? t('fileManagement.selectedWithMax', {
1255
+ count: selectedIds.size,
1256
+ max: maxSelection,
1257
+ })
1258
+ : t('fileManagement.selected', { count: selectedIds.size })
1259
+ : t('fileManagement.selectFile')
1260
+ : viewMode === 'photos'
1261
+ ? t('fileManagement.photos')
1262
+ : t('fileManagement.title'),
1263
+ subtitle: selectMode
1264
+ ? multiSelect
1265
+ ? t('fileManagement.available', { count: filteredFiles.length })
1266
+ : t('fileManagement.tapToSelect')
1267
+ : filteredFiles.length === 1
1268
+ ? t('fileManagement.itemCount', { count: filteredFiles.length })
1269
+ : t('fileManagement.itemCount_plural', { count: filteredFiles.length }),
1270
+ right: fmHeaderRight,
1271
+ largeTitle: false,
1272
+ },
1273
+ );
1274
+
1275
+ if (isLoadingFiles && !isImageOnlyPicker) {
1276
+ const GRID_PADDING = 10;
1277
+ const TILE_GAP = 4;
1278
+ const GRID_COLUMNS = 3;
1279
+ const GRID_ROWS = 6;
1280
+ const tileSize = Math.floor(
1281
+ (safeContainerWidth - GRID_PADDING * 2 - TILE_GAP * (GRID_COLUMNS - 1)) / GRID_COLUMNS,
1282
+ );
1136
1283
 
1137
1284
  return (
1138
- <View className="flex-1">
1139
- <View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
1140
- <ScrollView
1141
- horizontal
1142
- showsHorizontalScrollIndicator={false}
1143
- className="flex-1"
1144
- style={{ maxWidth: '80%' }}
1145
- >
1146
- <SegmentedControl type="radio" label={t('fileManagement.viewMode') || 'File type'}
1147
- value={viewMode} onValueChange={setViewMode} size="sm">
1148
- {(['all', 'photos', 'videos', 'documents', 'audio'] as const).map(mode => (
1149
- <SegmentedControlItem key={mode} value={mode} accessibilityLabel={mode}>
1150
- <MaterialCommunityIcons
1151
- name={({ all: 'folder-outline', photos: 'image-multiple-outline', videos: 'video-outline', documents: 'file-document-outline', audio: 'music-note-outline' } as const)[mode]}
1152
- size={18} color={colors.text} />
1153
- </SegmentedControlItem>
1154
- ))}
1155
- </SegmentedControl>
1156
- </ScrollView>
1157
- <Button appearance="subtle" tone="neutral" size="sm"
1158
- accessibilityRole="button"
1159
- accessibilityLabel={t('fileManagement.a11y.sortBy', {
1160
- field: sortBy,
1161
- order: sortOrder === 'asc' ? 'ascending' : 'descending',
1162
- }) || `Sort by ${sortBy}, ${sortOrder === 'asc' ? 'ascending' : 'descending'}`}
1163
- onPress={() => {
1164
- // Cycle through sort options: date -> size -> name -> type -> date
1165
- const sortOrder: Array<'date' | 'size' | 'name' | 'type'> = ['date', 'size', 'name', 'type'];
1166
- const currentIndex = sortOrder.indexOf(sortBy);
1167
- const nextIndex = (currentIndex + 1) % sortOrder.length;
1168
- setSortBy(sortOrder[nextIndex]);
1169
- // Toggle order when cycling back to date
1170
- if (nextIndex === 0) {
1171
- setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
1172
- }
1173
- }}
1174
- trailing={<View className="flex-row items-center gap-1">
1175
- <MaterialCommunityIcons
1176
- name={
1177
- sortBy === 'date' ? 'calendar' :
1178
- sortBy === 'size' ? 'sort-numeric-variant' :
1179
- sortBy === 'name' ? 'sort-alphabetical-variant' : 'file-document-outline'
1180
- }
1181
- size={16}
1182
- color={colors.text}
1183
- />
1184
- <MaterialCommunityIcons
1185
- name={sortOrder === 'asc' ? 'arrow-up' : 'arrow-down'}
1186
- size={14}
1187
- color={colors.textSecondary}
1188
- />
1189
- </View>} />
1190
- {user?.id === targetUserId && (!selectMode || allowUploadInSelectMode) && (
1191
- <Button iconOnly onPress={handleFileUpload} loading={uploading || isPickingDocument}
1192
- accessibilityLabel={t('fileManagement.a11y.uploadFile') || 'Upload file'}
1193
- icon={<Ionicons name="add" size={22} color={colors.primaryForeground} />} />
1194
- )}
1195
- </View>
1285
+ <View className="flex-1" style={{ backgroundColor }}>
1286
+ {/* Header */}
1287
+ <View
1288
+ className="flex-row items-center justify-between px-[16px] py-[12px]"
1289
+ style={{ borderBottomColor: colors.border, borderBottomWidth: StyleSheet.hairlineWidth }}
1290
+ >
1291
+ <Skeleton.Box width={44} height={44} borderRadius={12} />
1292
+ <Skeleton.Col style={{ alignItems: 'center', marginHorizontal: 16, gap: 6 }}>
1293
+ <Skeleton.Box width={140} height={20} />
1294
+ <Skeleton.Box width={100} height={14} />
1295
+ </Skeleton.Col>
1296
+ <Skeleton.Box width={44} height={44} borderRadius={12} />
1297
+ </View>
1196
1298
 
1197
- {/* Search Bar */}
1198
- {files.length > 0 && (viewMode === 'all' || files.some(f => f.contentType.startsWith('image/'))) && (
1199
- <View className="mx-[12px] mb-[12px]">
1200
- <Search
1201
- label={viewMode === 'photos' ? t('fileManagement.searchPhotos') : t('fileManagement.searchFiles')}
1202
- value={searchQuery} onValueChange={setSearchQuery} onClearText={() => setSearchQuery('')}
1203
- />
1204
- </View>
1205
- )}
1299
+ {/* Controls bar */}
1300
+ <Skeleton.Row
1301
+ style={{
1302
+ alignItems: 'center',
1303
+ justifyContent: 'space-between',
1304
+ paddingHorizontal: 12,
1305
+ paddingVertical: 12,
1306
+ }}
1307
+ >
1308
+ <Skeleton.Box width={100} height={36} borderRadius={18} />
1309
+ <Skeleton.Box width={44} height={44} borderRadius={22} />
1310
+ </Skeleton.Row>
1206
1311
 
1207
- {/* File Stats */}
1208
- {files.length > 0 && (
1209
- <View
1210
- className="flex-row px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-[18px]"
1211
- style={{ backgroundColor: colors.card }}
1212
- >
1213
- <View className="flex-1 items-center py-[4px]">
1214
- <Text className="text-[20px] font-extrabold leading-[24px]" style={{ color: colors.text, letterSpacing: -0.5 }}>{filteredFiles.length}</Text>
1215
- <Text className="text-[12px] font-medium mt-[2px]" style={{ color: colors.textSecondary, letterSpacing: 0.2 }}>
1216
- {searchQuery.length > 0 ? t('fileManagement.found') : (filteredFiles.length === 1 ? (viewMode === 'photos' ? t('fileManagement.photo') : t('fileManagement.file')) : (viewMode === 'photos' ? t('fileManagement.photos_stat') : t('fileManagement.files')))}
1217
- </Text>
1218
- </View>
1219
- <View className="flex-1 items-center py-[4px]">
1220
- <Text className="text-[20px] font-extrabold leading-[24px]" style={{ color: colors.text, letterSpacing: -0.5 }}>
1221
- {formatFileSize(filteredFiles.reduce((total, file) => total + file.length, 0))}
1222
- </Text>
1223
- <Text className="text-[12px] font-medium mt-[2px]" style={{ color: colors.textSecondary, letterSpacing: 0.2 }}>
1224
- {searchQuery.length > 0 ? t('fileManagement.size') : t('fileManagement.totalSize')}
1225
- </Text>
1226
- </View>
1227
- {searchQuery.length > 0 && (
1228
- <View className="flex-1 items-center py-[4px]">
1229
- <Text className="text-[20px] font-extrabold leading-[24px]" style={{ color: colors.text, letterSpacing: -0.5 }}>{files.length}</Text>
1230
- <Text className="text-[12px] font-medium mt-[2px]" style={{ color: colors.textSecondary, letterSpacing: 0.2 }}>
1231
- {t('fileManagement.total')}
1232
- </Text>
1233
- </View>
1234
- )}
1235
- </View>
1236
- )}
1312
+ {/* Search bar */}
1313
+ <View className="mx-[12px] mb-[12px]">
1314
+ <Skeleton.Box width="100%" height={44} borderRadius={22} />
1315
+ </View>
1237
1316
 
1238
- {/* File List */}
1239
- {viewMode === 'photos' ? (
1240
- renderPhotoGrid()
1241
- ) : (
1242
- <FileListSection
1243
- scrollViewRef={scrollViewRef}
1244
- filteredFiles={filteredFiles}
1245
- searchQuery={searchQuery}
1246
- items={groupedFileItems}
1247
- paging={{ loadingMore: isFetchingMore, hasMore: hasMoreFiles }}
1248
- refreshing={isRefreshingFiles}
1249
- colors={colors}
1250
- t={t}
1251
- onRefresh={() => filesQuery.refetch()}
1252
- onLoadMore={() => filesQuery.fetchNextPage()}
1253
- onClearSearch={() => setSearchQuery('')}
1254
- renderEmptyState={renderEmptyState}
1317
+ {/* Photo grid — static skeleton placeholders; index keys are stable */}
1318
+ <View style={{ padding: GRID_PADDING, gap: TILE_GAP }}>
1319
+ {Array.from({ length: GRID_ROWS }, (_, row) => (
1320
+ // biome-ignore lint/suspicious/noArrayIndexKey: fixed-size skeleton grid, order never changes
1321
+ <Skeleton.Row key={`skeleton-row-${row}`} style={{ gap: TILE_GAP }}>
1322
+ {Array.from({ length: GRID_COLUMNS }, (_, col) => (
1323
+ <Skeleton.Box
1324
+ // biome-ignore lint/suspicious/noArrayIndexKey: fixed-size skeleton grid, order never changes
1325
+ key={`skeleton-${row}-${col}`}
1326
+ width={tileSize}
1327
+ height={tileSize}
1328
+ borderRadius={6}
1255
1329
  />
1256
- )}
1330
+ ))}
1331
+ </Skeleton.Row>
1332
+ ))}
1333
+ </View>
1334
+ </View>
1335
+ );
1336
+ }
1337
+
1338
+ // Dedicated flagship-style photo picker view used in the avatar-picker
1339
+ // context (selectMode + image-only filter). The picker is intentionally
1340
+ // minimal: black backdrop, edge-to-edge grid, translucent header. The
1341
+ // standalone file manager (non-picker browse) keeps the existing UI.
1342
+ if (isImageOnlyPicker && !showUploadPreview) {
1343
+ const photosOnly = filteredFiles.filter((file) => file.contentType.startsWith('image/'));
1344
+
1345
+ // Chrome (Cancel / "Choose Photo" / Upload) lives on the shared Dialog nav
1346
+ // header (see the `useSurfaceHeader` call above); the picker owns only the
1347
+ // full-bleed photo grid as content.
1348
+ return (
1349
+ <PhotoPickerView
1350
+ photos={photosOnly}
1351
+ selectedIds={selectedIds}
1352
+ multiSelect={multiSelect}
1353
+ maxSelection={maxSelection}
1354
+ allowUpload={allowUpload}
1355
+ refreshing={isRefreshingFiles}
1356
+ uploading={uploading}
1357
+ isPickingDocument={isPickingDocument}
1358
+ hasMore={hasMoreFiles}
1359
+ loadingMore={isFetchingMore}
1360
+ loading={isLoadingFiles}
1361
+ loadError={hasLoadError}
1362
+ onRetry={() => filesQuery.refetch()}
1363
+ reduceMotion={reduceMotion}
1364
+ getThumbUrl={thumbSourceFor}
1365
+ primaryColor={colors.primary}
1366
+ isOwner={isOwner}
1367
+ onTogglePhoto={toggleSelect}
1368
+ // Long-press preview surfaces the file-detail panel on the stack.
1369
+ onPreviewPhoto={(file) => showFileDetailsModal(file)}
1370
+ onUpload={handleFileUpload}
1371
+ onRefresh={() => filesQuery.refetch()}
1372
+ onLoadMore={() => filesQuery.fetchNextPage()}
1373
+ t={t}
1374
+ />
1375
+ );
1376
+ }
1257
1377
 
1258
- {/* Uploading banner overlay with progress */}
1259
- {!selectMode && uploading && (
1260
- <UploadBar
1261
- uploadProgress={uploadProgress}
1262
- t={t}
1378
+ // If a file is opened, show the file viewer
1379
+ if (!selectMode && openedFile) {
1380
+ return (
1381
+ <FileViewer
1382
+ file={openedFile}
1383
+ fileContent={fileContent}
1384
+ loadingFileContent={loadingFileContent}
1385
+ showFileDetailsInViewer={showFileDetailsInViewer}
1386
+ onToggleDetails={() => setShowFileDetailsInViewer(!showFileDetailsInViewer)}
1387
+ onClose={handleCloseFile}
1388
+ onDownload={handleFileDownload}
1389
+ onDelete={confirmFileDelete}
1390
+ isOwner={user?.id === targetUserId}
1391
+ />
1392
+ );
1393
+ }
1394
+
1395
+ // If upload preview is showing, render it inline instead of the file list
1396
+ if (showUploadPreview) {
1397
+ return (
1398
+ <View className="flex-1">
1399
+ <UploadPreview
1400
+ pendingFiles={pendingFiles}
1401
+ onConfirm={handleConfirmUpload}
1402
+ onCancel={handleCancelUpload}
1403
+ onRemoveFile={removePendingFile}
1404
+ inline={true}
1405
+ />
1406
+ </View>
1407
+ );
1408
+ }
1409
+
1410
+ return (
1411
+ <View className="flex-1">
1412
+ <View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
1413
+ <ScrollView
1414
+ horizontal
1415
+ showsHorizontalScrollIndicator={false}
1416
+ className="flex-1"
1417
+ style={{ maxWidth: '80%' }}
1418
+ >
1419
+ <SegmentedControl
1420
+ type="radio"
1421
+ label={t('fileManagement.viewMode') || 'File type'}
1422
+ value={viewMode}
1423
+ onValueChange={setViewMode}
1424
+ size="sm"
1425
+ >
1426
+ {(['all', 'photos', 'videos', 'documents', 'audio'] as const).map((mode) => (
1427
+ <SegmentedControlItem key={mode} value={mode} accessibilityLabel={mode}>
1428
+ <MaterialCommunityIcons
1429
+ name={
1430
+ (
1431
+ {
1432
+ all: 'folder-outline',
1433
+ photos: 'image-multiple-outline',
1434
+ videos: 'video-outline',
1435
+ documents: 'file-document-outline',
1436
+ audio: 'music-note-outline',
1437
+ } as const
1438
+ )[mode]
1439
+ }
1440
+ size={18}
1441
+ color={colors.text}
1263
1442
  />
1264
- )}
1443
+ </SegmentedControlItem>
1444
+ ))}
1445
+ </SegmentedControl>
1446
+ </ScrollView>
1447
+ <Button
1448
+ appearance="subtle"
1449
+ tone="neutral"
1450
+ size="sm"
1451
+ accessibilityRole="button"
1452
+ accessibilityLabel={
1453
+ t('fileManagement.a11y.sortBy', {
1454
+ field: sortBy,
1455
+ order: sortOrder === 'asc' ? 'ascending' : 'descending',
1456
+ }) || `Sort by ${sortBy}, ${sortOrder === 'asc' ? 'ascending' : 'descending'}`
1457
+ }
1458
+ onPress={() => {
1459
+ // Cycle through sort options: date -> size -> name -> type -> date
1460
+ const sortOrder: Array<'date' | 'size' | 'name' | 'type'> = [
1461
+ 'date',
1462
+ 'size',
1463
+ 'name',
1464
+ 'type',
1465
+ ];
1466
+ const currentIndex = sortOrder.indexOf(sortBy);
1467
+ const nextIndex = (currentIndex + 1) % sortOrder.length;
1468
+ setSortBy(sortOrder[nextIndex]);
1469
+ // Toggle order when cycling back to date
1470
+ if (nextIndex === 0) {
1471
+ setSortOrder((prev) => (prev === 'asc' ? 'desc' : 'asc'));
1472
+ }
1473
+ }}
1474
+ trailing={
1475
+ <View className="flex-row items-center gap-1">
1476
+ <MaterialCommunityIcons
1477
+ name={
1478
+ sortBy === 'date'
1479
+ ? 'calendar'
1480
+ : sortBy === 'size'
1481
+ ? 'sort-numeric-variant'
1482
+ : sortBy === 'name'
1483
+ ? 'sort-alphabetical-variant'
1484
+ : 'file-document-outline'
1485
+ }
1486
+ size={16}
1487
+ color={colors.text}
1488
+ />
1489
+ <MaterialCommunityIcons
1490
+ name={sortOrder === 'asc' ? 'arrow-up' : 'arrow-down'}
1491
+ size={14}
1492
+ color={colors.textSecondary}
1493
+ />
1494
+ </View>
1495
+ }
1496
+ />
1497
+ {user?.id === targetUserId && (!selectMode || allowUploadInSelectMode) && (
1498
+ <Button
1499
+ iconOnly
1500
+ onPress={handleFileUpload}
1501
+ loading={uploading || isPickingDocument}
1502
+ accessibilityLabel={t('fileManagement.a11y.uploadFile') || 'Upload file'}
1503
+ icon={<Ionicons name="add" size={22} color={colors.primaryForeground} />}
1504
+ />
1505
+ )}
1506
+ </View>
1507
+
1508
+ {/* Search Bar */}
1509
+ {files.length > 0 &&
1510
+ (viewMode === 'all' || files.some((f) => f.contentType.startsWith('image/'))) && (
1511
+ <View className="mx-[12px] mb-[12px]">
1512
+ <Search
1513
+ label={
1514
+ viewMode === 'photos'
1515
+ ? t('fileManagement.searchPhotos')
1516
+ : t('fileManagement.searchFiles')
1517
+ }
1518
+ value={searchQuery}
1519
+ onValueChange={setSearchQuery}
1520
+ onClearText={() => setSearchQuery('')}
1521
+ />
1522
+ </View>
1523
+ )}
1524
+
1525
+ {/* File Stats */}
1526
+ {files.length > 0 && (
1527
+ <View
1528
+ className="flex-row px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-[18px]"
1529
+ style={{ backgroundColor: colors.card }}
1530
+ >
1531
+ <View className="flex-1 items-center py-[4px]">
1532
+ <Text
1533
+ className="text-[20px] font-extrabold leading-[24px]"
1534
+ style={{ color: colors.text, letterSpacing: -0.5 }}
1535
+ >
1536
+ {filteredFiles.length}
1537
+ </Text>
1538
+ <Text
1539
+ className="text-[12px] font-medium mt-[2px]"
1540
+ style={{ color: colors.textSecondary, letterSpacing: 0.2 }}
1541
+ >
1542
+ {searchQuery.length > 0
1543
+ ? t('fileManagement.found')
1544
+ : filteredFiles.length === 1
1545
+ ? viewMode === 'photos'
1546
+ ? t('fileManagement.photo')
1547
+ : t('fileManagement.file')
1548
+ : viewMode === 'photos'
1549
+ ? t('fileManagement.photos_stat')
1550
+ : t('fileManagement.files')}
1551
+ </Text>
1552
+ </View>
1553
+ <View className="flex-1 items-center py-[4px]">
1554
+ <Text
1555
+ className="text-[20px] font-extrabold leading-[24px]"
1556
+ style={{ color: colors.text, letterSpacing: -0.5 }}
1557
+ >
1558
+ {formatFileSize(filteredFiles.reduce((total, file) => total + file.length, 0))}
1559
+ </Text>
1560
+ <Text
1561
+ className="text-[12px] font-medium mt-[2px]"
1562
+ style={{ color: colors.textSecondary, letterSpacing: 0.2 }}
1563
+ >
1564
+ {searchQuery.length > 0 ? t('fileManagement.size') : t('fileManagement.totalSize')}
1565
+ </Text>
1566
+ </View>
1567
+ {searchQuery.length > 0 && (
1568
+ <View className="flex-1 items-center py-[4px]">
1569
+ <Text
1570
+ className="text-[20px] font-extrabold leading-[24px]"
1571
+ style={{ color: colors.text, letterSpacing: -0.5 }}
1572
+ >
1573
+ {files.length}
1574
+ </Text>
1575
+ <Text
1576
+ className="text-[12px] font-medium mt-[2px]"
1577
+ style={{ color: colors.textSecondary, letterSpacing: 0.2 }}
1578
+ >
1579
+ {t('fileManagement.total')}
1580
+ </Text>
1581
+ </View>
1582
+ )}
1265
1583
  </View>
1266
- );
1584
+ )}
1585
+
1586
+ {/* File List */}
1587
+ {viewMode === 'photos' ? (
1588
+ renderPhotoGrid()
1589
+ ) : (
1590
+ <FileListSection
1591
+ scrollViewRef={scrollViewRef}
1592
+ filteredFiles={filteredFiles}
1593
+ searchQuery={searchQuery}
1594
+ items={groupedFileItems}
1595
+ paging={{ loadingMore: isFetchingMore, hasMore: hasMoreFiles }}
1596
+ refreshing={isRefreshingFiles}
1597
+ colors={colors}
1598
+ t={t}
1599
+ onRefresh={() => filesQuery.refetch()}
1600
+ onLoadMore={() => filesQuery.fetchNextPage()}
1601
+ onClearSearch={() => setSearchQuery('')}
1602
+ renderEmptyState={renderEmptyState}
1603
+ />
1604
+ )}
1605
+
1606
+ {/* Uploading banner overlay with progress */}
1607
+ {!selectMode && uploading && <UploadBar uploadProgress={uploadProgress} t={t} />}
1608
+ </View>
1609
+ );
1267
1610
  };
1268
1611
 
1269
1612
  export default FileManagementScreen;