@oxyhq/services 22.10.0 → 22.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/lib/commonjs/ui/components/AvatarCameraBadge.js +42 -0
  2. package/lib/commonjs/ui/components/AvatarCameraBadge.js.map +1 -0
  3. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +41 -19
  4. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
  5. package/lib/commonjs/ui/components/OxyAuthChooser.js +640 -168
  6. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  7. package/lib/commonjs/ui/components/ProfileButton.js +14 -0
  8. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  9. package/lib/commonjs/ui/components/ProfileSummaryCard.js +3 -23
  10. package/lib/commonjs/ui/components/ProfileSummaryCard.js.map +1 -1
  11. package/lib/commonjs/ui/components/SurfaceScreen.js +41 -5
  12. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
  13. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +78 -26
  14. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -1
  15. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +83 -23
  16. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  17. package/lib/commonjs/ui/context/OxyContext.js +73 -37
  18. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/queries/queryKeys.js +18 -1
  20. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
  21. package/lib/commonjs/ui/hooks/queries/useFileQueries.js +129 -0
  22. package/lib/commonjs/ui/hooks/queries/useFileQueries.js.map +1 -0
  23. package/lib/commonjs/ui/hooks/useAvatarPicker.js +36 -48
  24. package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
  25. package/lib/commonjs/ui/hooks/useReduceMotion.js +57 -0
  26. package/lib/commonjs/ui/hooks/useReduceMotion.js.map +1 -0
  27. package/lib/commonjs/ui/hooks/useSurfaceHeader.js +1 -1
  28. package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
  29. package/lib/commonjs/ui/navigation/accountDialogManager.js +22 -0
  30. package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -1
  31. package/lib/commonjs/ui/navigation/bottomSheetManager.js +5 -6
  32. package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
  33. package/lib/commonjs/ui/navigation/routes.js +1 -0
  34. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  35. package/lib/commonjs/ui/navigation/surfaceNavStack.js +62 -15
  36. package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
  37. package/lib/commonjs/ui/navigation/surfaceRegistry.js +38 -17
  38. package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
  39. package/lib/commonjs/ui/navigation/surfaces.js +98 -21
  40. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  41. package/lib/commonjs/ui/screens/AvatarCropScreen.js +281 -279
  42. package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +301 -0
  44. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -0
  45. package/lib/commonjs/ui/screens/FileManagementScreen.js +194 -431
  46. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +16 -48
  48. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js +63 -0
  50. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
  51. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +54 -41
  52. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  53. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +44 -81
  54. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  55. package/lib/commonjs/ui/stores/resetSessionScopedStores.js +8 -3
  56. package/lib/commonjs/ui/stores/resetSessionScopedStores.js.map +1 -1
  57. package/lib/commonjs/ui/utils/avatarUtils.js +11 -2
  58. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  59. package/lib/commonjs/ui/utils/fileManagement.js +0 -79
  60. package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
  61. package/lib/module/ui/components/AvatarCameraBadge.js +40 -0
  62. package/lib/module/ui/components/AvatarCameraBadge.js.map +1 -0
  63. package/lib/module/ui/components/OxyAccountDialogScreen.js +41 -19
  64. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
  65. package/lib/module/ui/components/OxyAuthChooser.js +642 -171
  66. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  67. package/lib/module/ui/components/ProfileButton.js +15 -1
  68. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  69. package/lib/module/ui/components/ProfileSummaryCard.js +2 -23
  70. package/lib/module/ui/components/ProfileSummaryCard.js.map +1 -1
  71. package/lib/module/ui/components/SurfaceScreen.js +43 -7
  72. package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
  73. package/lib/module/ui/components/oxyAuthChooserStyles.js +78 -26
  74. package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -1
  75. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +83 -25
  76. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  77. package/lib/module/ui/context/OxyContext.js +74 -38
  78. package/lib/module/ui/context/OxyContext.js.map +1 -1
  79. package/lib/module/ui/hooks/queries/queryKeys.js +16 -0
  80. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
  81. package/lib/module/ui/hooks/queries/useFileQueries.js +120 -0
  82. package/lib/module/ui/hooks/queries/useFileQueries.js.map +1 -0
  83. package/lib/module/ui/hooks/useAvatarPicker.js +37 -49
  84. package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
  85. package/lib/module/ui/hooks/useReduceMotion.js +53 -0
  86. package/lib/module/ui/hooks/useReduceMotion.js.map +1 -0
  87. package/lib/module/ui/hooks/useSurfaceHeader.js +1 -1
  88. package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
  89. package/lib/module/ui/navigation/accountDialogManager.js +20 -0
  90. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -1
  91. package/lib/module/ui/navigation/bottomSheetManager.js +6 -7
  92. package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
  93. package/lib/module/ui/navigation/routes.js +1 -0
  94. package/lib/module/ui/navigation/routes.js.map +1 -1
  95. package/lib/module/ui/navigation/surfaceNavStack.js +62 -15
  96. package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
  97. package/lib/module/ui/navigation/surfaceRegistry.js +38 -17
  98. package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
  99. package/lib/module/ui/navigation/surfaces.js +97 -22
  100. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  101. package/lib/module/ui/screens/AvatarCropScreen.js +283 -281
  102. package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
  103. package/lib/module/ui/screens/ChangeAvatarScreen.js +298 -0
  104. package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -0
  105. package/lib/module/ui/screens/FileManagementScreen.js +196 -433
  106. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  107. package/lib/module/ui/screens/WelcomeNewUserScreen.js +17 -49
  108. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  109. package/lib/module/ui/screens/fileManagement/FileLibraryError.js +59 -0
  110. package/lib/module/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
  111. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +54 -42
  112. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  113. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +45 -81
  114. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  115. package/lib/module/ui/stores/resetSessionScopedStores.js +8 -3
  116. package/lib/module/ui/stores/resetSessionScopedStores.js.map +1 -1
  117. package/lib/module/ui/utils/avatarUtils.js +11 -2
  118. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  119. package/lib/module/ui/utils/fileManagement.js +0 -78
  120. package/lib/module/ui/utils/fileManagement.js.map +1 -1
  121. package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts +4 -0
  122. package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts +2 -1
  124. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  126. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/ui/components/ProfileSummaryCard.d.ts.map +1 -1
  128. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +1 -4
  129. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +71 -19
  131. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
  133. package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  134. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts +9 -0
  136. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts +50 -0
  138. package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
  139. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts +15 -10
  140. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts +14 -0
  142. package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +1 -1
  144. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +13 -0
  146. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -1
  147. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +4 -5
  148. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  150. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  151. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +21 -0
  152. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +73 -13
  154. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
  155. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts +42 -11
  156. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  157. package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts +18 -5
  158. package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  159. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +34 -0
  160. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
  161. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  162. package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  163. package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
  164. package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
  166. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
  168. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts +8 -1
  170. package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
  171. package/lib/typescript/commonjs/ui/types/navigation.d.ts +2 -0
  172. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  174. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +0 -48
  175. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
  176. package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts +4 -0
  177. package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts.map +1 -0
  178. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts +2 -1
  179. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  180. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  181. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  182. package/lib/typescript/module/ui/components/ProfileSummaryCard.d.ts.map +1 -1
  183. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +1 -4
  184. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
  185. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +71 -19
  186. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
  187. package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
  188. package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  189. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  190. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts +9 -0
  191. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  192. package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts +50 -0
  193. package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
  194. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts +15 -10
  195. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  196. package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts +14 -0
  197. package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts.map +1 -0
  198. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +1 -1
  199. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  200. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +13 -0
  201. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -1
  202. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +4 -5
  203. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  204. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  205. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  206. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +21 -0
  207. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
  208. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +73 -13
  209. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
  210. package/lib/typescript/module/ui/navigation/surfaces.d.ts +42 -11
  211. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  212. package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts +18 -5
  213. package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  214. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +34 -0
  215. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
  216. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  217. package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  218. package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
  219. package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
  220. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
  221. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  222. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
  223. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  224. package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts +8 -1
  225. package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
  226. package/lib/typescript/module/ui/types/navigation.d.ts +2 -0
  227. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  228. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  229. package/lib/typescript/module/ui/utils/fileManagement.d.ts +0 -48
  230. package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
  231. package/package.json +8 -4
  232. package/src/ui/components/AvatarCameraBadge.tsx +35 -0
  233. package/src/ui/components/OxyAccountDialogScreen.tsx +38 -15
  234. package/src/ui/components/OxyAuthChooser.tsx +722 -185
  235. package/src/ui/components/ProfileButton.tsx +16 -1
  236. package/src/ui/components/ProfileSummaryCard.tsx +2 -25
  237. package/src/ui/components/SurfaceScreen.tsx +48 -14
  238. package/src/ui/components/oxyAuthChooserStyles.ts +75 -23
  239. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -84
  240. package/src/ui/components/photogrid/__tests__/chunkPhotos.test.ts +51 -0
  241. package/src/ui/context/OxyContext.tsx +74 -34
  242. package/src/ui/hooks/queries/queryKeys.ts +15 -0
  243. package/src/ui/hooks/queries/useFileQueries.ts +162 -0
  244. package/src/ui/hooks/useAvatarPicker.ts +38 -53
  245. package/src/ui/hooks/useReduceMotion.ts +58 -0
  246. package/src/ui/hooks/useSurfaceHeader.ts +2 -1
  247. package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +55 -7
  248. package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +81 -0
  249. package/src/ui/navigation/accountDialogManager.ts +28 -0
  250. package/src/ui/navigation/bottomSheetManager.ts +6 -7
  251. package/src/ui/navigation/routes.ts +3 -1
  252. package/src/ui/navigation/surfaceNavStack.ts +72 -9
  253. package/src/ui/navigation/surfaceRegistry.ts +91 -17
  254. package/src/ui/navigation/surfaces.ts +108 -29
  255. package/src/ui/screens/AvatarCropScreen.tsx +336 -298
  256. package/src/ui/screens/ChangeAvatarScreen.tsx +359 -0
  257. package/src/ui/screens/FileManagementScreen.tsx +189 -432
  258. package/src/ui/screens/WelcomeNewUserScreen.tsx +18 -48
  259. package/src/ui/screens/fileManagement/FileLibraryError.tsx +56 -0
  260. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +56 -46
  261. package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +55 -71
  262. package/src/ui/stores/resetSessionScopedStores.ts +8 -3
  263. package/src/ui/types/navigation.ts +2 -0
  264. package/src/ui/utils/avatarUtils.ts +11 -2
  265. package/src/ui/utils/fileManagement.ts +1 -101
  266. package/lib/commonjs/ui/stores/fileStore.js +0 -153
  267. package/lib/commonjs/ui/stores/fileStore.js.map +0 -1
  268. package/lib/module/ui/stores/fileStore.js +0 -145
  269. package/lib/module/ui/stores/fileStore.js.map +0 -1
  270. package/lib/typescript/commonjs/ui/stores/fileStore.d.ts +0 -31
  271. package/lib/typescript/commonjs/ui/stores/fileStore.d.ts.map +0 -1
  272. package/lib/typescript/module/ui/stores/fileStore.d.ts +0 -31
  273. package/lib/typescript/module/ui/stores/fileStore.d.ts.map +0 -1
  274. package/src/ui/stores/fileStore.ts +0 -118
@@ -1,20 +1,23 @@
1
1
  "use strict";
2
2
 
3
- import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
4
- import { View, Text, TouchableOpacity, StyleSheet, ScrollView, ActivityIndicator, RefreshControl, TextInput, Image, AccessibilityInfo, useWindowDimensions } from 'react-native';
3
+ import React, { useState, useCallback, useMemo, useRef } from 'react';
4
+ import { View, Text, TouchableOpacity, StyleSheet, ScrollView, ActivityIndicator, RefreshControl, TextInput, useWindowDimensions } from 'react-native';
5
+ import { useQueryClient } from '@tanstack/react-query';
5
6
  import { Image as ExpoImage } from 'expo-image';
6
7
  import { toast } from '@oxyhq/bloom';
7
8
  import { surfaces } from '@oxyhq/bloom/surfaces';
8
9
  import * as Skeleton from '@oxyhq/bloom/skeleton';
9
10
  import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
10
11
  import { Pressable } from 'react-native';
11
- import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from "../stores/fileStore.js";
12
+ import { queryKeys } from "../hooks/queries/queryKeys.js";
13
+ import { useUserFilesInfinite, removeFileFromCache, patchFileMetadataInCache } from "../hooks/queries/useFileQueries.js";
12
14
  import { useSurfaceHeader } from "../hooks/useSurfaceHeader.js";
13
15
  import { SurfaceHeaderAction } from "../components/SurfaceHeaderAction.js";
14
16
  import JustifiedPhotoGrid from "../components/photogrid/JustifiedPhotoGrid.js";
15
17
  import { useTheme } from '@oxyhq/bloom/theme';
16
18
  import { useOxy } from "../context/OxyContext.js";
17
19
  import { useI18n } from "../hooks/useI18n.js";
20
+ import { useReduceMotion } from "../hooks/useReduceMotion.js";
18
21
  import { useUploadFile } from "../hooks/mutations/useAccountMutations.js";
19
22
  import { formatFileSize, getFileIcon } from "../utils/fileManagement.js";
20
23
  import { useResolvedFileUrls, fileThumbSource } from "../hooks/useResolvedFileUrls.js";
@@ -26,6 +29,7 @@ import { getErrorMessage } from "./fileManagement/shared.js";
26
29
  import { useFileUploadState } from "./fileManagement/hooks/useFileUploadState.js";
27
30
  import PhotoPickerView from "./fileManagement/PhotoPickerSection.js";
28
31
  import FileListSection from "./fileManagement/FileListSection.js";
32
+ import FileLibraryError from "./fileManagement/FileLibraryError.js";
29
33
  import UploadBar from "./fileManagement/UploadBar.js";
30
34
  import { AnimatedButton } from "../components/fileManagement/AnimatedButton.js";
31
35
 
@@ -57,11 +61,6 @@ const screenStyles = StyleSheet.create({
57
61
  justifyContent: 'center',
58
62
  marginHorizontal: 1
59
63
  },
60
- photoImage: {
61
- width: '100%',
62
- height: '100%',
63
- borderRadius: 8
64
- },
65
64
  justifiedPhotoImage: {
66
65
  width: '100%',
67
66
  height: '100%',
@@ -70,7 +69,6 @@ const screenStyles = StyleSheet.create({
70
69
  });
71
70
  const FileManagementScreen = ({
72
71
  onClose,
73
- theme,
74
72
  goBack,
75
73
  navigate,
76
74
  dismiss,
@@ -99,50 +97,49 @@ const FileManagementScreen = ({
99
97
  const {
100
98
  t
101
99
  } = useI18n();
100
+ const queryClient = useQueryClient();
102
101
  const uploadFileMutation = useUploadFile();
103
- const files = useFiles();
102
+
103
+ // Files are owned by the ACTIVE account. `targetUserId` scopes the query key,
104
+ // so switching accounts (or an explicit `userId` prop) shows that account's
105
+ // library with no manual reset.
106
+ const targetUserId = userId || user?.id;
107
+
108
+ // The file list is React Query's, NOT a store: infinite-paginated, fetched on
109
+ // mount + on owner change, edited optimistically via cache helpers. The
110
+ // rendered list is the flattened pages.
111
+ const filesQuery = useUserFilesInfinite(targetUserId);
112
+ const files = useMemo(() => filesQuery.data?.pages.flatMap(page => page.files) ?? [], [filesQuery.data]);
113
+ // No owner resolved yet reads as "loading" (matches the old skeleton-until-
114
+ // first-load behaviour); `isLoading` is the first-page fetch only.
115
+ const isLoadingFiles = !targetUserId || filesQuery.isLoading;
116
+ const isRefreshingFiles = filesQuery.isRefetching;
117
+ const isFetchingMore = filesQuery.isFetchingNextPage;
118
+ const hasMoreFiles = filesQuery.hasNextPage ?? false;
119
+ // Terminal load failure with nothing cached — render a DISTINCT error state
120
+ // (not the empty state) so a failed load never looks like an empty library.
121
+ const hasLoadError = filesQuery.isError && files.length === 0;
122
+ const invalidateFiles = useCallback(() => {
123
+ queryClient.invalidateQueries({
124
+ queryKey: queryKeys.files.list(targetUserId)
125
+ });
126
+ }, [queryClient, targetUserId]);
104
127
  const {
105
128
  width: windowWidth
106
129
  } = useWindowDimensions();
107
130
  // Prefer an explicit sheet width from the router; fall back to the window
108
131
  // so photo grids never size against a hardcoded 400px default.
109
132
  const safeContainerWidth = typeof containerWidth === 'number' && containerWidth > 0 ? containerWidth : windowWidth;
110
- const uploading = useUploadingStore();
111
- const uploadProgress = useUploadAggregateProgress();
112
- const deleting = useDeletingStore();
113
- const [loading, setLoading] = useState(true);
114
- const [refreshing, setRefreshing] = useState(false);
115
- const [paging, setPaging] = useState({
116
- offset: 0,
117
- limit: 40,
118
- total: 0,
119
- hasMore: true,
120
- loadingMore: false
121
- });
133
+ // In-flight delete target (local UI state — was in the file store).
134
+ const [deletingId, setDeletingId] = useState(null);
122
135
  // In selectMode we never open the detailed viewer
123
136
  const [openedFile, setOpenedFile] = useState(null);
124
137
  const [fileContent, setFileContent] = useState(null);
125
138
  const [loadingFileContent, setLoadingFileContent] = useState(false);
126
139
  const [showFileDetailsInViewer, setShowFileDetailsInViewer] = useState(false);
127
- const [reduceMotion, setReduceMotion] = useState(false);
128
-
129
- // Detect reduce-motion preference once on mount + subscribe to changes.
130
- // Used by `PhotoPickerView` to skip cell stagger animations.
131
- useEffect(() => {
132
- let cancelled = false;
133
- AccessibilityInfo.isReduceMotionEnabled().then(enabled => {
134
- if (!cancelled) setReduceMotion(enabled);
135
- }).catch(() => {
136
- // Defaults to false; no action needed.
137
- });
138
- const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', enabled => {
139
- setReduceMotion(enabled);
140
- });
141
- return () => {
142
- cancelled = true;
143
- sub.remove();
144
- };
145
- }, []);
140
+ // Reduce-motion preference, read as an external store (no effect). Passed to
141
+ // `PhotoPickerView` to skip cell stagger animations.
142
+ const reduceMotion = useReduceMotion();
146
143
 
147
144
  // Image-only picker mode: when the consumer restricts to image MIME types
148
145
  // (e.g. avatar picker), photos grid is the more useful default view.
@@ -194,24 +191,59 @@ const FileManagementScreen = ({
194
191
  });
195
192
  return sorted;
196
193
  }, [files, searchQuery, viewMode, sortBy, sortOrder]);
197
- const [photoDimensions, setPhotoDimensions] = useState({});
198
- const photoDimensionsRef = useRef(photoDimensions);
199
- photoDimensionsRef.current = photoDimensions;
200
- const [loadingDimensions, setLoadingDimensions] = useState(false);
201
- // Selection state
202
- const [selectedIds, setSelectedIds] = useState(new Set(initialSelectedIds));
203
- const [lastSelectedFileId, setLastSelectedFileId] = useState(null);
194
+ // Selection state. `initialSelectedIds` is the INITIAL selection only (lazy
195
+ // init) there is no prop→state sync effect, since no caller mutates it
196
+ // after mount.
197
+ const [selectedIds, setSelectedIds] = useState(() => new Set(initialSelectedIds));
204
198
  const scrollViewRef = useRef(null);
205
199
  const photoScrollViewRef = useRef(null);
206
- const containerRef = useRef(null);
207
- useEffect(() => {
208
- if (initialSelectedIds?.length) {
209
- setSelectedIds(new Set(initialSelectedIds));
200
+
201
+ // Scroll the browse list/grid to a just-selected file. Driven directly from
202
+ // the selection event handler (double-rAF so the selection re-render has
203
+ // committed first) — not an effect. No-ops in the image-only picker, whose
204
+ // own FlatList is not either ScrollView ref.
205
+ const scrollToSelectedFile = useCallback(fileId => {
206
+ if (viewMode === 'all' && scrollViewRef.current) {
207
+ const itemIndex = filteredFiles.findIndex(file => file.id === fileId);
208
+ if (itemIndex < 0) return;
209
+ // GroupedItem dense rows are ~65px, +30px when a description wraps.
210
+ const baseItemHeight = 65;
211
+ const descriptionHeight = 30;
212
+ let scrollPosition = 0;
213
+ for (let i = 0; i <= itemIndex && i < filteredFiles.length; i++) {
214
+ scrollPosition += baseItemHeight;
215
+ if (filteredFiles[i].metadata?.description) scrollPosition += descriptionHeight;
216
+ }
217
+ // Offset for the header/controls/search/stats chrome (~250px) so the
218
+ // item lands near the top, not under the chrome.
219
+ const finalScrollPosition = 250 + scrollPosition - 150;
220
+ requestAnimationFrame(() => {
221
+ requestAnimationFrame(() => {
222
+ scrollViewRef.current?.scrollTo({
223
+ y: Math.max(0, finalScrollPosition),
224
+ animated: true
225
+ });
226
+ });
227
+ });
228
+ } else if (viewMode === 'photos' && photoScrollViewRef.current) {
229
+ const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
230
+ const photoIndex = photos.findIndex(p => p.id === fileId);
231
+ if (photoIndex < 0) return;
232
+ // Rough estimate for the justified grid (3 photos/row, variable heights).
233
+ const row = Math.floor(photoIndex / 3);
234
+ const finalScrollPosition = row * (150 + 4) - 100;
235
+ requestAnimationFrame(() => {
236
+ requestAnimationFrame(() => {
237
+ photoScrollViewRef.current?.scrollTo({
238
+ y: Math.max(0, finalScrollPosition),
239
+ animated: true
240
+ });
241
+ });
242
+ });
210
243
  }
211
- }, [initialSelectedIds]);
244
+ }, [viewMode, filteredFiles]);
212
245
  const toggleSelect = useCallback(async file => {
213
- // Allow selection in regular mode for bulk operations
214
- // if (!selectMode) return;
246
+ // Selection is allowed in regular mode too (for bulk operations).
215
247
  if (disabledMimeTypes.length) {
216
248
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
217
249
  if (blocked) {
@@ -230,9 +262,6 @@ const FileManagementScreen = ({
230
262
  }
231
263
  }
232
264
 
233
- // Track the selected file for scrolling
234
- setLastSelectedFileId(file.id);
235
-
236
265
  // Link file to entity if linkContext is provided
237
266
  if (linkContext) {
238
267
  try {
@@ -274,13 +303,14 @@ const FileManagementScreen = ({
274
303
  }
275
304
  return next;
276
305
  });
277
- }, [selectMode, multiSelect, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext]);
306
+ scrollToSelectedFile(file.id);
307
+ }, [multiSelect, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext, t, scrollToSelectedFile]);
278
308
  const confirmMultiSelection = useCallback(async () => {
279
309
  if (!selectMode || !multiSelect) return;
280
310
  const map = {};
281
- files.forEach(f => {
311
+ for (const f of files) {
282
312
  map[f.id] = f;
283
- });
313
+ }
284
314
  const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
285
315
 
286
316
  // Update visibility and link files if needed
@@ -334,81 +364,16 @@ const FileManagementScreen = ({
334
364
  // FileManagementScreen uses a slightly elevated page background.
335
365
  const backgroundColor = colors.backgroundSecondary;
336
366
  const borderColor = colors.border;
337
- const targetUserId = userId || user?.id;
338
- const storeSetDeleting = useFileStore(s => s.setDeleting);
339
- const loadFiles = useCallback(async (mode = 'initial') => {
340
- if (!targetUserId) return;
341
- try {
342
- if (mode === 'refresh') {
343
- setRefreshing(true);
344
- } else if (mode === 'initial') {
345
- setLoading(true);
346
- setPaging(p => ({
347
- ...p,
348
- offset: 0,
349
- hasMore: true
350
- }));
351
- } else if (mode === 'more') {
352
- // Prevent duplicate fetches
353
- setPaging(p => ({
354
- ...p,
355
- loadingMore: true
356
- }));
357
- }
358
- const currentPaging = mode === 'more' ? prevPagingRef.current ?? paging : paging;
359
- const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
360
- const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
361
- const assets = (response.files || []).map(f => ({
362
- id: f.id,
363
- filename: f.originalName ?? f.sha256 ?? '',
364
- contentType: f.mime ?? '',
365
- length: f.size ?? 0,
366
- chunkSize: 0,
367
- uploadDate: f.createdAt ?? '',
368
- metadata: f.metadata ?? {},
369
- variants: f.variants ?? []
370
- }));
371
- if (mode === 'more') {
372
- // append
373
- useFileStore.getState().setFiles(assets, {
374
- merge: true
375
- });
376
- setPaging(p => ({
377
- ...p,
378
- offset: effectiveOffset,
379
- total: response.total || effectiveOffset + assets.length,
380
- hasMore: response.hasMore,
381
- loadingMore: false
382
- }));
383
- } else {
384
- useFileStore.getState().setFiles(assets, {
385
- merge: false
386
- });
387
- setPaging(p => ({
388
- ...p,
389
- offset: 0,
390
- total: response.total || assets.length,
391
- hasMore: response.hasMore,
392
- loadingMore: false
393
- }));
394
- }
395
- } catch (error) {
396
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.loadFailed'));
397
- } finally {
398
- setLoading(false);
399
- setRefreshing(false);
400
- setPaging(p => ({
401
- ...p,
402
- loadingMore: false
403
- }));
404
- }
405
- }, [targetUserId, oxyServices, paging]);
406
367
 
407
368
  // Self-contained document-picking + upload-preview state and handlers.
369
+ // Optimistic uploads edit the file query cache directly (see
370
+ // useFileQueries), so no loader is threaded in.
408
371
  const {
409
372
  isPickingDocument,
410
373
  pendingFiles,
411
374
  showUploadPreview,
375
+ uploading,
376
+ uploadProgress,
412
377
  handleFileUpload,
413
378
  handleConfirmUpload,
414
379
  handleCancelUpload,
@@ -425,105 +390,8 @@ const FileManagementScreen = ({
425
390
  onClose,
426
391
  selectedIds,
427
392
  setSelectedIds,
428
- loadFiles,
429
393
  t
430
394
  });
431
-
432
- // Keep a ref to avoid stale closure when calculating next offset
433
- const prevPagingRef = useRef(paging);
434
- useEffect(() => {
435
- prevPagingRef.current = paging;
436
- }, [paging]);
437
-
438
- // (removed effect; filteredFiles is memoized)
439
-
440
- // Load photo dimensions for justified grid - unified approach using Image.getSize
441
- const loadPhotoDimensions = useCallback(async photos => {
442
- if (photos.length === 0) return;
443
- setLoadingDimensions(true);
444
-
445
- // Snapshot ids missing from the cache at kick-off; concurrent runs may
446
- // overlap but each merge is applied via a functional updater.
447
- const photosToLoad = photos.filter(photo => !photoDimensionsRef.current[photo.id]);
448
- if (photosToLoad.length === 0) {
449
- setLoadingDimensions(false);
450
- return;
451
- }
452
- try {
453
- const measured = {};
454
- await Promise.all(photosToLoad.map(async photo => {
455
- try {
456
- const downloadUrl = thumbSourceFor(photo);
457
- // URL not resolved yet — skip (do NOT record fallback
458
- // dims) so this photo is re-measured once its private-safe
459
- // URL resolves. Measuring the broken public URL is exactly
460
- // the bug we are fixing.
461
- if (!downloadUrl) {
462
- return;
463
- }
464
-
465
- // Unified approach using Image.getSize (works on all platforms)
466
- await new Promise(resolve => {
467
- Image.getSize(downloadUrl, (width, height) => {
468
- measured[photo.id] = {
469
- width,
470
- height
471
- };
472
- resolve();
473
- }, () => {
474
- // Fallback dimensions
475
- measured[photo.id] = {
476
- width: 1,
477
- height: 1
478
- };
479
- resolve();
480
- });
481
- });
482
- } catch (error) {
483
- // Fallback dimensions for any errors
484
- measured[photo.id] = {
485
- width: 1,
486
- height: 1
487
- };
488
- }
489
- }));
490
- if (Object.keys(measured).length > 0) {
491
- setPhotoDimensions(prev => ({
492
- ...prev,
493
- ...measured
494
- }));
495
- }
496
- } catch (error) {
497
- // Photo dimensions loading failed, continue without dimensions
498
- } finally {
499
- setLoadingDimensions(false);
500
- }
501
- }, [thumbSourceFor]);
502
-
503
- // Re-measure photo dimensions when their private-safe URLs resolve. The
504
- // justified grid's own trigger fires on the photo SET, not on URL
505
- // availability, so newly-resolved private tiles would otherwise keep their
506
- // skipped (unmeasured) state and render with fallback geometry.
507
- useEffect(() => {
508
- if (viewMode !== 'photos') return;
509
- const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
510
- if (photos.length > 0) {
511
- loadPhotoDimensions(photos);
512
- }
513
- }, [resolvedThumbUrls, viewMode, filteredFiles, loadPhotoDimensions]);
514
-
515
- // Create justified rows from photos with responsive algorithm
516
- const createJustifiedRows = useCallback((photos, containerWidth) => {
517
- if (photos.length === 0) return [];
518
- const rows = [];
519
- const photosPerRow = 3; // Fixed 3 photos per row for consistency
520
-
521
- for (let i = 0; i < photos.length; i += photosPerRow) {
522
- const rowPhotos = photos.slice(i, i + photosPerRow);
523
- rows.push(rowPhotos);
524
- }
525
- return rows;
526
- }, []);
527
395
  const confirmFileDelete = useCallback(async (fileId, filename) => {
528
396
  const confirmed = await surfaces.confirm({
529
397
  title: t('fileManagement.deleteFile') || 'Delete File',
@@ -536,30 +404,28 @@ const FileManagementScreen = ({
536
404
  });
537
405
  if (!confirmed) return;
538
406
  try {
539
- storeSetDeleting(fileId);
407
+ setDeletingId(fileId);
540
408
  await oxyServices.deleteFile(fileId);
541
409
  toast.success(t('fileManagement.toasts.deleteSuccess'));
542
410
 
543
- // Reload files after successful deletion
544
- // Optimistic remove
545
- useFileStore.getState().removeFile(fileId);
546
- // Silent background reconcile
547
- setTimeout(() => loadFiles('silent'), 800);
411
+ // Remove from the query cache, then reconcile against the server.
412
+ removeFileFromCache(queryClient, targetUserId, fileId);
413
+ invalidateFiles();
548
414
  } catch (error) {
549
415
  // Provide specific error messages
550
416
  if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
551
417
  toast.error(t('fileManagement.toasts.fileNotFound'));
552
- // Still reload files to refresh the list
553
- setTimeout(() => loadFiles('silent'), 800);
418
+ // It's already gone server-side reconcile the list.
419
+ invalidateFiles();
554
420
  } else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
555
421
  toast.error(t('fileManagement.toasts.noPermission'));
556
422
  } else {
557
423
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
558
424
  }
559
425
  } finally {
560
- storeSetDeleting(null);
426
+ setDeletingId(null);
561
427
  }
562
- }, [storeSetDeleting, oxyServices, loadFiles, t]);
428
+ }, [oxyServices, queryClient, targetUserId, invalidateFiles, t]);
563
429
  const confirmBulkDelete = useCallback(async () => {
564
430
  if (selectedIds.size === 0) return;
565
431
  const confirmed = await surfaces.confirm({
@@ -576,7 +442,7 @@ const FileManagementScreen = ({
576
442
  const deletePromises = Array.from(selectedIds).map(async fileId => {
577
443
  try {
578
444
  await oxyServices.deleteFile(fileId);
579
- useFileStore.getState().removeFile(fileId);
445
+ removeFileFromCache(queryClient, targetUserId, fileId);
580
446
  return {
581
447
  success: true,
582
448
  fileId
@@ -603,11 +469,11 @@ const FileManagementScreen = ({
603
469
  }));
604
470
  }
605
471
  setSelectedIds(new Set());
606
- setTimeout(() => loadFiles('silent'), 800);
472
+ invalidateFiles();
607
473
  } catch (error) {
608
474
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
609
475
  }
610
- }, [selectedIds, oxyServices, loadFiles, t]);
476
+ }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
611
477
  const handleBulkVisibilityChange = useCallback(async visibility => {
612
478
  if (selectedIds.size === 0) return;
613
479
  try {
@@ -634,26 +500,23 @@ const FileManagementScreen = ({
634
500
  count: successful,
635
501
  visibility
636
502
  }));
637
- // Update file metadata in store
638
- Array.from(selectedIds).forEach(fileId => {
639
- useFileStore.getState().updateFile(fileId, {
640
- metadata: {
641
- ...files.find(f => f.id === fileId)?.metadata,
642
- visibility
643
- }
503
+ // Reflect the new visibility in the cached files.
504
+ for (const fileId of selectedIds) {
505
+ patchFileMetadataInCache(queryClient, targetUserId, fileId, {
506
+ visibility
644
507
  });
645
- });
508
+ }
646
509
  }
647
510
  if (failed > 0) {
648
511
  toast.error(t('fileManagement.toasts.visibilityFailed', {
649
512
  count: failed
650
513
  }));
651
514
  }
652
- setTimeout(() => loadFiles('silent'), 800);
515
+ invalidateFiles();
653
516
  } catch (error) {
654
517
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
655
518
  }
656
- }, [selectedIds, oxyServices, files, loadFiles, t]);
519
+ }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
657
520
  const handleVisibilityChange = useCallback(async () => {
658
521
  if (selectedIds.size === 0) return;
659
522
  const visibility = await presentActionSheet({
@@ -677,7 +540,7 @@ const FileManagementScreen = ({
677
540
  }, [selectedIds.size, handleBulkVisibilityChange, t]);
678
541
 
679
542
  // Unified download function - works on all platforms
680
- const handleFileDownload = async (fileId, filename) => {
543
+ const handleFileDownload = useCallback(async (fileId, filename) => {
681
544
  try {
682
545
  // Resolve an authenticated, private-safe URL. The synchronous
683
546
  // `getFileDownloadUrl` yields the public CDN origin, which 404s for
@@ -720,8 +583,8 @@ const FileManagementScreen = ({
720
583
  } catch (error) {
721
584
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
722
585
  }
723
- };
724
- const handleFileOpen = async file => {
586
+ }, [oxyServices, t]);
587
+ const handleFileOpen = useCallback(async file => {
725
588
  if (selectMode) {
726
589
  toggleSelect(file);
727
590
  return;
@@ -761,21 +624,21 @@ const FileManagementScreen = ({
761
624
  } finally {
762
625
  setLoadingFileContent(false);
763
626
  }
764
- };
765
- const handleCloseFile = () => {
627
+ }, [selectMode, toggleSelect, oxyServices, t]);
628
+ const handleCloseFile = useCallback(() => {
766
629
  setOpenedFile(null);
767
630
  setFileContent(null);
768
631
  setShowFileDetailsInViewer(false);
769
632
  // Don't reset view mode when closing a file
770
- };
771
- const showFileDetailsModal = file => {
633
+ }, []);
634
+ const showFileDetailsModal = useCallback(file => {
772
635
  presentFileDetails({
773
636
  file,
774
637
  onDownload: handleFileDownload,
775
638
  onDelete: confirmFileDelete,
776
639
  isOwner: user?.id === targetUserId
777
640
  });
778
- };
641
+ }, [handleFileDownload, confirmFileDelete, user?.id, targetUserId]);
779
642
  const renderJustifiedPhotoItem = useCallback((photo, width, height, isLast) => {
780
643
  const downloadUrl = thumbSourceFor(photo);
781
644
  return /*#__PURE__*/_jsx(TouchableOpacity, {
@@ -820,18 +683,7 @@ const FileManagementScreen = ({
820
683
  })]
821
684
  })
822
685
  }, photo.id);
823
- }, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text]);
824
-
825
- // Run initial load once per targetUserId change to avoid accidental loops
826
- const lastLoadedFor = useRef(undefined);
827
- useEffect(() => {
828
- const key = targetUserId || 'anonymous';
829
- if (lastLoadedFor.current !== key) {
830
- lastLoadedFor.current = key;
831
- loadFiles('initial');
832
- }
833
- // eslint-disable-next-line react-hooks/exhaustive-deps
834
- }, [targetUserId]);
686
+ }, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text, handleFileOpen]);
835
687
 
836
688
  // SettingsListItem-based file items (for 'all' view)
837
689
  const groupedFileItems = useMemo(() => {
@@ -982,8 +834,8 @@ const FileManagementScreen = ({
982
834
  backgroundColor: colors.negativeSubtle
983
835
  },
984
836
  onPress: () => confirmFileDelete(file.id, file.filename),
985
- disabled: deleting === file.id,
986
- children: deleting === file.id ? /*#__PURE__*/_jsx(ActivityIndicator, {
837
+ disabled: deletingId === file.id,
838
+ children: deletingId === file.id ? /*#__PURE__*/_jsx(ActivityIndicator, {
987
839
  size: "small",
988
840
  color: colors.error
989
841
  }) : /*#__PURE__*/_jsx(Ionicons, {
@@ -995,85 +847,27 @@ const FileManagementScreen = ({
995
847
  }) : undefined
996
848
  };
997
849
  });
998
- }, [filteredFiles, theme, deleting, handleFileDownload, confirmFileDelete, handleFileOpen, thumbSourceFor, selectMode, selectedIds]);
999
-
1000
- // Scroll to selected file after selection
1001
- useEffect(() => {
1002
- if (lastSelectedFileId && selectMode) {
1003
- if (viewMode === 'all' && scrollViewRef.current) {
1004
- // Find the index of the selected file (filteredFiles is already sorted)
1005
- const itemIndex = filteredFiles.findIndex(file => file.id === lastSelectedFileId);
1006
- if (itemIndex >= 0) {
1007
- // Estimate item height (GroupedItem with dense mode is approximately 60-70px)
1008
- // Account for description rows which add extra height
1009
- const baseItemHeight = 65;
1010
- const descriptionHeight = 30; // Approximate height for description
1011
- // Use filteredFiles which is already sorted according to user's selection
1012
- const sortedFiles = filteredFiles;
1013
-
1014
- // Calculate total height up to this item
1015
- let scrollPosition = 0;
1016
- for (let i = 0; i <= itemIndex && i < sortedFiles.length; i++) {
1017
- const file = sortedFiles[i];
1018
- scrollPosition += baseItemHeight;
1019
- if (file.metadata?.description) {
1020
- scrollPosition += descriptionHeight;
1021
- }
1022
- }
1023
-
1024
- // Add header, controls, search, and stats height (approximately 250px)
1025
- const headerHeight = 250;
1026
- const finalScrollPosition = headerHeight + scrollPosition - 150; // Offset to show item near top
1027
-
1028
- // Use requestAnimationFrame to ensure DOM is updated before scrolling
1029
- requestAnimationFrame(() => {
1030
- requestAnimationFrame(() => {
1031
- scrollViewRef.current?.scrollTo({
1032
- y: Math.max(0, finalScrollPosition),
1033
- animated: true
1034
- });
1035
- });
1036
- });
1037
- }
1038
- } else if (viewMode === 'photos' && photoScrollViewRef.current) {
1039
- // For photo grid, find the photo index
1040
- const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
1041
- const photoIndex = photos.findIndex(p => p.id === lastSelectedFileId);
1042
- if (photoIndex >= 0) {
1043
- // Rough scroll estimate for the justified grid (3 photos/row; variable row heights).
1044
- const itemsPerRow = 3;
1045
- const estimatedRowHeight = 150;
1046
- const rowGap = 4;
1047
- const row = Math.floor(photoIndex / itemsPerRow);
1048
- const finalScrollPosition = row * (estimatedRowHeight + rowGap) - 100;
850
+ }, [filteredFiles, colors, deletingId, toggleSelect, handleFileDownload, confirmFileDelete, handleFileOpen, thumbSourceFor, selectMode, selectedIds]);
1049
851
 
1050
- // Use requestAnimationFrame to ensure DOM is updated before scrolling
1051
- requestAnimationFrame(() => {
1052
- requestAnimationFrame(() => {
1053
- photoScrollViewRef.current?.scrollTo({
1054
- y: Math.max(0, finalScrollPosition),
1055
- animated: true
1056
- });
1057
- });
1058
- });
1059
- }
1060
- }
1061
- }
1062
- }, [lastSelectedFileId, selectMode, viewMode, filteredFiles, safeContainerWidth]);
1063
-
1064
- // Clear selected file ID after scroll animation completes
1065
- useEffect(() => {
1066
- if (lastSelectedFileId && scrollViewRef.current) {
1067
- const timeoutId = setTimeout(() => {
1068
- setLastSelectedFileId(null);
1069
- }, 600); // Allow time for scroll animation to complete
852
+ // Browse-themed terminal load-failure state DISTINCT from the empty state.
853
+ const renderLoadError = () => /*#__PURE__*/_jsx(FileLibraryError, {
854
+ title: t('fileManagement.loadError.title'),
855
+ description: t('fileManagement.loadError.description'),
856
+ retryLabel: t('fileManagement.retry'),
857
+ onRetry: () => filesQuery.refetch(),
858
+ iconColor: colors.error,
859
+ titleColor: colors.text,
860
+ descriptionColor: colors.textSecondary,
861
+ buttonColor: colors.primary
862
+ });
1070
863
 
1071
- return () => clearTimeout(timeoutId);
1072
- }
1073
- }, [lastSelectedFileId]);
1074
- const renderPhotoGrid = useCallback(() => {
864
+ // Browse "photos" view. The justified grid owns its own dimension
865
+ // measurement + reflow (see JustifiedPhotoGrid); this only supplies the
866
+ // photo set, the private-safe URL resolver, and the tile renderer.
867
+ const renderPhotoGrid = () => {
1075
868
  const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
1076
869
  if (photos.length === 0) {
870
+ if (hasLoadError) return renderLoadError();
1077
871
  return /*#__PURE__*/_jsxs(View, {
1078
872
  className: "items-center py-[40px] px-[24px]",
1079
873
  children: [/*#__PURE__*/_jsx(Ionicons, {
@@ -1099,10 +893,7 @@ const FileManagementScreen = ({
1099
893
  },
1100
894
  onPress: handleFileUpload,
1101
895
  disabled: uploading || isPickingDocument,
1102
- children: uploading ? /*#__PURE__*/_jsx(ActivityIndicator, {
1103
- size: "small",
1104
- color: "#FFFFFF"
1105
- }) : isPickingDocument ? /*#__PURE__*/_jsx(ActivityIndicator, {
896
+ children: uploading || isPickingDocument ? /*#__PURE__*/_jsx(ActivityIndicator, {
1106
897
  size: "small",
1107
898
  color: "#FFFFFF"
1108
899
  }) : /*#__PURE__*/_jsxs(_Fragment, {
@@ -1118,13 +909,13 @@ const FileManagementScreen = ({
1118
909
  })]
1119
910
  });
1120
911
  }
1121
- return /*#__PURE__*/_jsxs(ScrollView, {
912
+ return /*#__PURE__*/_jsx(ScrollView, {
1122
913
  ref: photoScrollViewRef,
1123
914
  className: "flex-1",
1124
915
  contentContainerClassName: "p-[10px]",
1125
916
  refreshControl: /*#__PURE__*/_jsx(RefreshControl, {
1126
- refreshing: refreshing,
1127
- onRefresh: () => loadFiles('refresh'),
917
+ refreshing: isRefreshingFiles,
918
+ onRefresh: () => filesQuery.refetch(),
1128
919
  tintColor: colors.primary
1129
920
  }),
1130
921
  showsVerticalScrollIndicator: false,
@@ -1137,38 +928,21 @@ const FileManagementScreen = ({
1137
928
  contentSize
1138
929
  } = nativeEvent;
1139
930
  const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
1140
- if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
1141
- loadFiles('more');
931
+ if (distanceFromBottom < 200 && !isFetchingMore && hasMoreFiles) {
932
+ filesQuery.fetchNextPage();
1142
933
  }
1143
934
  },
1144
935
  scrollEventThrottle: 250,
1145
- children: [loadingDimensions && /*#__PURE__*/_jsxs(View, {
1146
- className: "flex-row items-center justify-center py-[16px] gap-[8px]",
1147
- children: [/*#__PURE__*/_jsx(ActivityIndicator, {
1148
- size: "small",
1149
- color: colors.primary
1150
- }), /*#__PURE__*/_jsx(Text, {
1151
- className: "text-[14px] italic",
1152
- style: {
1153
- color: colors.textSecondary
1154
- },
1155
- children: t('fileManagement.loadingPhotoLayout')
1156
- })]
1157
- }), /*#__PURE__*/_jsx(JustifiedPhotoGrid, {
936
+ children: /*#__PURE__*/_jsx(JustifiedPhotoGrid, {
1158
937
  photos: photos,
1159
- photoDimensions: photoDimensions,
1160
- loadPhotoDimensions: loadPhotoDimensions,
1161
- createJustifiedRows: createJustifiedRows,
938
+ getThumbUrl: thumbSourceFor,
1162
939
  renderJustifiedPhotoItem: renderJustifiedPhotoItem,
1163
940
  textColor: colors.text,
1164
941
  containerWidth: typeof containerWidth === 'number' && containerWidth > 0 ? containerWidth : undefined
1165
- })]
942
+ })
1166
943
  });
1167
- }, [filteredFiles, colors, user?.id, targetUserId, uploading, handleFileUpload, refreshing, loadFiles, loadingDimensions, photoDimensions, loadPhotoDimensions, createJustifiedRows, renderJustifiedPhotoItem, containerWidth]);
1168
-
1169
- // Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
1170
-
1171
- const renderEmptyState = () => /*#__PURE__*/_jsxs(View, {
944
+ };
945
+ const renderEmptyState = () => hasLoadError ? renderLoadError() : /*#__PURE__*/_jsxs(View, {
1172
946
  className: "items-center py-[40px] px-[24px]",
1173
947
  children: [/*#__PURE__*/_jsx(Ionicons, {
1174
948
  name: "folder-open-outline",
@@ -1193,10 +967,7 @@ const FileManagementScreen = ({
1193
967
  },
1194
968
  onPress: handleFileUpload,
1195
969
  disabled: uploading || isPickingDocument,
1196
- children: uploading ? /*#__PURE__*/_jsx(ActivityIndicator, {
1197
- size: "small",
1198
- color: "#FFFFFF"
1199
- }) : isPickingDocument ? /*#__PURE__*/_jsx(ActivityIndicator, {
970
+ children: uploading || isPickingDocument ? /*#__PURE__*/_jsx(ActivityIndicator, {
1200
971
  size: "small",
1201
972
  color: "#FFFFFF"
1202
973
  }) : /*#__PURE__*/_jsxs(_Fragment, {
@@ -1300,7 +1071,7 @@ const FileManagementScreen = ({
1300
1071
  right: fmHeaderRight,
1301
1072
  largeTitle: false
1302
1073
  });
1303
- if (loading && !isImageOnlyPicker) {
1074
+ if (isLoadingFiles && !isImageOnlyPicker) {
1304
1075
  const GRID_PADDING = 10;
1305
1076
  const TILE_GAP = 4;
1306
1077
  const GRID_COLUMNS = 3;
@@ -1399,13 +1170,15 @@ const FileManagementScreen = ({
1399
1170
  multiSelect: multiSelect,
1400
1171
  maxSelection: maxSelection,
1401
1172
  allowUpload: allowUpload,
1402
- refreshing: refreshing,
1173
+ refreshing: isRefreshingFiles,
1403
1174
  uploading: uploading,
1404
1175
  isPickingDocument: isPickingDocument,
1405
1176
  uploadProgress: uploadProgress,
1406
- hasMore: paging.hasMore,
1407
- loadingMore: paging.loadingMore,
1408
- loading: loading,
1177
+ hasMore: hasMoreFiles,
1178
+ loadingMore: isFetchingMore,
1179
+ loading: isLoadingFiles,
1180
+ loadError: hasLoadError,
1181
+ onRetry: () => filesQuery.refetch(),
1409
1182
  reduceMotion: reduceMotion,
1410
1183
  getThumbUrl: thumbSourceFor,
1411
1184
  primaryColor: colors.primary,
@@ -1415,8 +1188,8 @@ const FileManagementScreen = ({
1415
1188
  ,
1416
1189
  onPreviewPhoto: file => showFileDetailsModal(file),
1417
1190
  onUpload: handleFileUpload,
1418
- onRefresh: () => loadFiles('refresh'),
1419
- onLoadMore: () => loadFiles('more'),
1191
+ onRefresh: () => filesQuery.refetch(),
1192
+ onLoadMore: () => filesQuery.fetchNextPage(),
1420
1193
  onCancel: () => {
1421
1194
  if (onSelect || onConfirmSelection) {
1422
1195
  // Legacy callback caller: preserve close/back behaviour.
@@ -1491,32 +1264,30 @@ const FileManagementScreen = ({
1491
1264
  textColor: colors.text,
1492
1265
  style: screenStyles.viewModeButton,
1493
1266
  accessibilityLabel: t('fileManagement.a11y.viewPhotos') || 'Show photos only'
1494
- }), !isImageOnlyPicker && /*#__PURE__*/_jsxs(_Fragment, {
1495
- children: [/*#__PURE__*/_jsx(AnimatedButton, {
1496
- isSelected: viewMode === 'videos',
1497
- onPress: () => setViewMode('videos'),
1498
- icon: viewMode === 'videos' ? 'video' : 'video-outline',
1499
- primaryColor: colors.primary,
1500
- textColor: colors.text,
1501
- style: screenStyles.viewModeButton,
1502
- accessibilityLabel: t('fileManagement.a11y.viewVideos') || 'Show videos only'
1503
- }), /*#__PURE__*/_jsx(AnimatedButton, {
1504
- isSelected: viewMode === 'documents',
1505
- onPress: () => setViewMode('documents'),
1506
- icon: viewMode === 'documents' ? 'file-document' : 'file-document-outline',
1507
- primaryColor: colors.primary,
1508
- textColor: colors.text,
1509
- style: screenStyles.viewModeButton,
1510
- accessibilityLabel: t('fileManagement.a11y.viewDocuments') || 'Show documents only'
1511
- }), /*#__PURE__*/_jsx(AnimatedButton, {
1512
- isSelected: viewMode === 'audio',
1513
- onPress: () => setViewMode('audio'),
1514
- icon: viewMode === 'audio' ? 'music-note' : 'music-note-outline',
1515
- primaryColor: colors.primary,
1516
- textColor: colors.text,
1517
- style: screenStyles.viewModeButton,
1518
- accessibilityLabel: t('fileManagement.a11y.viewAudio') || 'Show audio only'
1519
- })]
1267
+ }), /*#__PURE__*/_jsx(AnimatedButton, {
1268
+ isSelected: viewMode === 'videos',
1269
+ onPress: () => setViewMode('videos'),
1270
+ icon: viewMode === 'videos' ? 'video' : 'video-outline',
1271
+ primaryColor: colors.primary,
1272
+ textColor: colors.text,
1273
+ style: screenStyles.viewModeButton,
1274
+ accessibilityLabel: t('fileManagement.a11y.viewVideos') || 'Show videos only'
1275
+ }), /*#__PURE__*/_jsx(AnimatedButton, {
1276
+ isSelected: viewMode === 'documents',
1277
+ onPress: () => setViewMode('documents'),
1278
+ icon: viewMode === 'documents' ? 'file-document' : 'file-document-outline',
1279
+ primaryColor: colors.primary,
1280
+ textColor: colors.text,
1281
+ style: screenStyles.viewModeButton,
1282
+ accessibilityLabel: t('fileManagement.a11y.viewDocuments') || 'Show documents only'
1283
+ }), /*#__PURE__*/_jsx(AnimatedButton, {
1284
+ isSelected: viewMode === 'audio',
1285
+ onPress: () => setViewMode('audio'),
1286
+ icon: viewMode === 'audio' ? 'music-note' : 'music-note-outline',
1287
+ primaryColor: colors.primary,
1288
+ textColor: colors.text,
1289
+ style: screenStyles.viewModeButton,
1290
+ accessibilityLabel: t('fileManagement.a11y.viewAudio') || 'Show audio only'
1520
1291
  })]
1521
1292
  })
1522
1293
  }), /*#__PURE__*/_jsxs(TouchableOpacity, {
@@ -1549,15 +1320,15 @@ const FileManagementScreen = ({
1549
1320
  size: 14,
1550
1321
  color: colors.textSecondary
1551
1322
  })]
1552
- }), user?.id === targetUserId && (!selectMode || selectMode && allowUploadInSelectMode) && /*#__PURE__*/_jsx(TouchableOpacity, {
1553
- className: `h-[44px] rounded-[22px] items-center justify-center ${isImageOnlyPicker ? 'w-auto min-w-[44px] px-[14px]' : 'w-[44px]'}`,
1323
+ }), user?.id === targetUserId && (!selectMode || allowUploadInSelectMode) && /*#__PURE__*/_jsx(TouchableOpacity, {
1324
+ className: "h-[44px] w-[44px] rounded-[22px] items-center justify-center",
1554
1325
  style: {
1555
1326
  backgroundColor: colors.primary
1556
1327
  },
1557
1328
  onPress: handleFileUpload,
1558
1329
  disabled: uploading || isPickingDocument,
1559
1330
  accessibilityRole: "button",
1560
- accessibilityLabel: isImageOnlyPicker ? t('fileManagement.a11y.uploadFromDevice') || 'Upload photo from device' : t('fileManagement.a11y.uploadFile') || 'Upload file',
1331
+ accessibilityLabel: t('fileManagement.a11y.uploadFile') || 'Upload file',
1561
1332
  accessibilityState: {
1562
1333
  busy: uploading || isPickingDocument
1563
1334
  },
@@ -1573,17 +1344,6 @@ const FileManagementScreen = ({
1573
1344
  }) : isPickingDocument ? /*#__PURE__*/_jsx(ActivityIndicator, {
1574
1345
  size: "small",
1575
1346
  color: "#FFFFFF"
1576
- }) : isImageOnlyPicker ? /*#__PURE__*/_jsxs(View, {
1577
- className: "flex-row items-center gap-[6px]",
1578
- children: [/*#__PURE__*/_jsx(Ionicons, {
1579
- name: "cloud-upload",
1580
- size: 18,
1581
- color: "#FFFFFF"
1582
- }), /*#__PURE__*/_jsx(Text, {
1583
- className: "text-white text-[14px] font-semibold",
1584
- numberOfLines: 1,
1585
- children: t('fileManagement.upload') || 'Upload'
1586
- })]
1587
1347
  }) : /*#__PURE__*/_jsx(Ionicons, {
1588
1348
  name: "add",
1589
1349
  size: 22,
@@ -1679,12 +1439,15 @@ const FileManagementScreen = ({
1679
1439
  filteredFiles: filteredFiles,
1680
1440
  searchQuery: searchQuery,
1681
1441
  items: groupedFileItems,
1682
- paging: paging,
1683
- refreshing: refreshing,
1442
+ paging: {
1443
+ loadingMore: isFetchingMore,
1444
+ hasMore: hasMoreFiles
1445
+ },
1446
+ refreshing: isRefreshingFiles,
1684
1447
  colors: colors,
1685
1448
  t: t,
1686
- onRefresh: () => loadFiles('refresh'),
1687
- onLoadMore: () => loadFiles('more'),
1449
+ onRefresh: () => filesQuery.refetch(),
1450
+ onLoadMore: () => filesQuery.fetchNextPage(),
1688
1451
  onClearSearch: () => setSearchQuery(''),
1689
1452
  renderEmptyState: renderEmptyState
1690
1453
  }), !selectMode && uploading && /*#__PURE__*/_jsx(UploadBar, {