@oxyhq/services 22.9.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 (584) hide show
  1. package/lib/commonjs/index.js +24 -5
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/AvatarCameraBadge.js +42 -0
  4. package/lib/commonjs/ui/components/AvatarCameraBadge.js.map +1 -0
  5. package/lib/commonjs/ui/components/{OxyAccountDialog.js → OxyAccountDialogScreen.js} +55 -133
  6. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -0
  7. package/lib/commonjs/ui/components/OxyAuthChooser.js +657 -88
  8. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxyConsentScreen.js +1 -1
  10. package/lib/commonjs/ui/components/ProfileButton.js +15 -1
  11. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  12. package/lib/commonjs/ui/components/ProfileSummaryCard.js +3 -23
  13. package/lib/commonjs/ui/components/ProfileSummaryCard.js.map +1 -1
  14. package/lib/commonjs/ui/components/RequireOxyAuth.js +1 -1
  15. package/lib/commonjs/ui/components/SurfaceHeaderAction.js +63 -0
  16. package/lib/commonjs/ui/components/SurfaceHeaderAction.js.map +1 -0
  17. package/lib/commonjs/ui/components/SurfaceScreen.js +91 -8
  18. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
  19. package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js +46 -29
  20. package/lib/commonjs/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
  21. package/lib/commonjs/ui/components/modals/DeleteAccountModal.js +47 -23
  22. package/lib/commonjs/ui/components/modals/DeleteAccountModal.js.map +1 -1
  23. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +81 -19
  24. package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -1
  25. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +83 -23
  26. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  27. package/lib/commonjs/ui/components/surfaces/ActionSheetSurface.js +83 -0
  28. package/lib/commonjs/ui/components/surfaces/ActionSheetSurface.js.map +1 -0
  29. package/lib/commonjs/ui/context/OxyContext.js +79 -29
  30. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  31. package/lib/commonjs/ui/hooks/queries/queryKeys.js +18 -1
  32. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
  33. package/lib/commonjs/ui/hooks/queries/useFileQueries.js +129 -0
  34. package/lib/commonjs/ui/hooks/queries/useFileQueries.js.map +1 -0
  35. package/lib/commonjs/ui/hooks/useAvatarPicker.js +38 -55
  36. package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
  37. package/lib/commonjs/ui/hooks/useReduceMotion.js +57 -0
  38. package/lib/commonjs/ui/hooks/useReduceMotion.js.map +1 -0
  39. package/lib/commonjs/ui/hooks/useSurfaceHeader.js +46 -0
  40. package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -0
  41. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +1 -1
  42. package/lib/commonjs/ui/navigation/accountDialogManager.js +36 -2
  43. package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -1
  44. package/lib/commonjs/ui/navigation/bottomSheetManager.js +5 -6
  45. package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
  46. package/lib/commonjs/ui/navigation/routes.js +4 -3
  47. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  48. package/lib/commonjs/ui/navigation/surfaceBackBridge.js +68 -0
  49. package/lib/commonjs/ui/navigation/surfaceBackBridge.js.map +1 -0
  50. package/lib/commonjs/ui/navigation/surfaceNavStack.js +62 -15
  51. package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
  52. package/lib/commonjs/ui/navigation/surfaceRegistry.js +84 -19
  53. package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
  54. package/lib/commonjs/ui/navigation/surfaces.js +139 -25
  55. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  56. package/lib/commonjs/ui/screens/AccountMembersScreen.js +214 -283
  57. package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
  58. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +88 -118
  59. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  60. package/lib/commonjs/ui/screens/AccountVerificationScreen.js +22 -36
  61. package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -1
  62. package/lib/commonjs/ui/screens/AppInfoScreen.js +219 -226
  63. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  64. package/lib/commonjs/ui/screens/AvatarCropScreen.js +287 -289
  65. package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
  66. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +301 -0
  67. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -0
  68. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js +24 -39
  69. package/lib/commonjs/ui/screens/ConnectedAppsScreen.js.map +1 -1
  70. package/lib/commonjs/ui/screens/CreateAccountScreen.js +136 -158
  71. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  72. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +29 -36
  73. package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
  74. package/lib/commonjs/ui/screens/EditProfileScreen.js +101 -109
  75. package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -1
  76. package/lib/commonjs/ui/screens/FAQScreen.js +10 -15
  77. package/lib/commonjs/ui/screens/FAQScreen.js.map +1 -1
  78. package/lib/commonjs/ui/screens/FeedbackScreen.js +8 -16
  79. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  80. package/lib/commonjs/ui/screens/FileManagementScreen.js +403 -627
  81. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  82. package/lib/commonjs/ui/screens/HelpSupportScreen.js +62 -69
  83. package/lib/commonjs/ui/screens/HelpSupportScreen.js.map +1 -1
  84. package/lib/commonjs/ui/screens/HistoryViewScreen.js +63 -79
  85. package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -1
  86. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +69 -98
  87. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -1
  88. package/lib/commonjs/ui/screens/LearnMoreUsernamesScreen.js +8 -14
  89. package/lib/commonjs/ui/screens/LearnMoreUsernamesScreen.js.map +1 -1
  90. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js +77 -94
  91. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -1
  92. package/lib/commonjs/ui/screens/ManageAccountScreen.js +99 -134
  93. package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
  94. package/lib/commonjs/ui/screens/NotificationsScreen.js +69 -76
  95. package/lib/commonjs/ui/screens/NotificationsScreen.js.map +1 -1
  96. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +3 -8
  97. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  98. package/lib/commonjs/ui/screens/PreferencesScreen.js +67 -74
  99. package/lib/commonjs/ui/screens/PreferencesScreen.js.map +1 -1
  100. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +55 -91
  101. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  102. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +291 -304
  103. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  104. package/lib/commonjs/ui/screens/ProfileScreen.js +100 -114
  105. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  106. package/lib/commonjs/ui/screens/SavesCollectionsScreen.js +38 -45
  107. package/lib/commonjs/ui/screens/SavesCollectionsScreen.js.map +1 -1
  108. package/lib/commonjs/ui/screens/SearchSettingsScreen.js +44 -57
  109. package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -1
  110. package/lib/commonjs/ui/screens/UserLinksScreen.js +24 -31
  111. package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -1
  112. package/lib/commonjs/ui/screens/UserListScreen.js +14 -24
  113. package/lib/commonjs/ui/screens/UserListScreen.js.map +1 -1
  114. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +16 -48
  115. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  116. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js +63 -0
  117. package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
  118. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +54 -41
  119. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  120. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +44 -81
  121. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  122. package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js +66 -75
  123. package/lib/commonjs/ui/screens/trust/TrustAboutScreen.js.map +1 -1
  124. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js +111 -139
  125. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  126. package/lib/commonjs/ui/screens/trust/TrustFAQScreen.js +13 -20
  127. package/lib/commonjs/ui/screens/trust/TrustFAQScreen.js.map +1 -1
  128. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js +14 -27
  129. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  130. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js +68 -85
  131. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
  132. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js +28 -36
  133. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  134. package/lib/commonjs/ui/stores/resetSessionScopedStores.js +8 -3
  135. package/lib/commonjs/ui/stores/resetSessionScopedStores.js.map +1 -1
  136. package/lib/commonjs/ui/utils/avatarUtils.js +11 -2
  137. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  138. package/lib/commonjs/ui/utils/fileManagement.js +0 -79
  139. package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
  140. package/lib/module/index.js +12 -5
  141. package/lib/module/index.js.map +1 -1
  142. package/lib/module/ui/components/AvatarCameraBadge.js +40 -0
  143. package/lib/module/ui/components/AvatarCameraBadge.js.map +1 -0
  144. package/lib/module/ui/components/{OxyAccountDialog.js → OxyAccountDialogScreen.js} +57 -135
  145. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -0
  146. package/lib/module/ui/components/OxyAuthChooser.js +659 -91
  147. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  148. package/lib/module/ui/components/OxyConsentScreen.js +1 -1
  149. package/lib/module/ui/components/ProfileButton.js +16 -2
  150. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  151. package/lib/module/ui/components/ProfileSummaryCard.js +2 -23
  152. package/lib/module/ui/components/ProfileSummaryCard.js.map +1 -1
  153. package/lib/module/ui/components/RequireOxyAuth.js +1 -1
  154. package/lib/module/ui/components/SurfaceHeaderAction.js +58 -0
  155. package/lib/module/ui/components/SurfaceHeaderAction.js.map +1 -0
  156. package/lib/module/ui/components/SurfaceScreen.js +93 -10
  157. package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
  158. package/lib/module/ui/components/fileManagement/FileDetailsModal.js +46 -29
  159. package/lib/module/ui/components/fileManagement/FileDetailsModal.js.map +1 -1
  160. package/lib/module/ui/components/modals/DeleteAccountModal.js +46 -23
  161. package/lib/module/ui/components/modals/DeleteAccountModal.js.map +1 -1
  162. package/lib/module/ui/components/oxyAuthChooserStyles.js +81 -19
  163. package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -1
  164. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +83 -25
  165. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
  166. package/lib/module/ui/components/surfaces/ActionSheetSurface.js +78 -0
  167. package/lib/module/ui/components/surfaces/ActionSheetSurface.js.map +1 -0
  168. package/lib/module/ui/context/OxyContext.js +80 -30
  169. package/lib/module/ui/context/OxyContext.js.map +1 -1
  170. package/lib/module/ui/hooks/queries/queryKeys.js +16 -0
  171. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
  172. package/lib/module/ui/hooks/queries/useFileQueries.js +120 -0
  173. package/lib/module/ui/hooks/queries/useFileQueries.js.map +1 -0
  174. package/lib/module/ui/hooks/useAvatarPicker.js +38 -55
  175. package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
  176. package/lib/module/ui/hooks/useReduceMotion.js +53 -0
  177. package/lib/module/ui/hooks/useReduceMotion.js.map +1 -0
  178. package/lib/module/ui/hooks/useSurfaceHeader.js +42 -0
  179. package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -0
  180. package/lib/module/ui/hooks/useSwitchableAccounts.js +1 -1
  181. package/lib/module/ui/navigation/accountDialogManager.js +34 -2
  182. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -1
  183. package/lib/module/ui/navigation/bottomSheetManager.js +6 -7
  184. package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
  185. package/lib/module/ui/navigation/routes.js +4 -3
  186. package/lib/module/ui/navigation/routes.js.map +1 -1
  187. package/lib/module/ui/navigation/surfaceBackBridge.js +63 -0
  188. package/lib/module/ui/navigation/surfaceBackBridge.js.map +1 -0
  189. package/lib/module/ui/navigation/surfaceNavStack.js +62 -15
  190. package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
  191. package/lib/module/ui/navigation/surfaceRegistry.js +84 -19
  192. package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
  193. package/lib/module/ui/navigation/surfaces.js +138 -26
  194. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  195. package/lib/module/ui/screens/AccountMembersScreen.js +218 -286
  196. package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
  197. package/lib/module/ui/screens/AccountSettingsScreen.js +92 -121
  198. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  199. package/lib/module/ui/screens/AccountVerificationScreen.js +25 -38
  200. package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -1
  201. package/lib/module/ui/screens/AppInfoScreen.js +220 -227
  202. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  203. package/lib/module/ui/screens/AvatarCropScreen.js +289 -291
  204. package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
  205. package/lib/module/ui/screens/ChangeAvatarScreen.js +298 -0
  206. package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -0
  207. package/lib/module/ui/screens/ConnectedAppsScreen.js +26 -40
  208. package/lib/module/ui/screens/ConnectedAppsScreen.js.map +1 -1
  209. package/lib/module/ui/screens/CreateAccountScreen.js +139 -160
  210. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  211. package/lib/module/ui/screens/EditProfileFieldScreen.js +32 -38
  212. package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
  213. package/lib/module/ui/screens/EditProfileScreen.js +102 -110
  214. package/lib/module/ui/screens/EditProfileScreen.js.map +1 -1
  215. package/lib/module/ui/screens/FAQScreen.js +11 -15
  216. package/lib/module/ui/screens/FAQScreen.js.map +1 -1
  217. package/lib/module/ui/screens/FeedbackScreen.js +10 -17
  218. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  219. package/lib/module/ui/screens/FileManagementScreen.js +408 -631
  220. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  221. package/lib/module/ui/screens/HelpSupportScreen.js +64 -70
  222. package/lib/module/ui/screens/HelpSupportScreen.js.map +1 -1
  223. package/lib/module/ui/screens/HistoryViewScreen.js +65 -81
  224. package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -1
  225. package/lib/module/ui/screens/LanguageSelectorScreen.js +71 -99
  226. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -1
  227. package/lib/module/ui/screens/LearnMoreUsernamesScreen.js +10 -15
  228. package/lib/module/ui/screens/LearnMoreUsernamesScreen.js.map +1 -1
  229. package/lib/module/ui/screens/LegalDocumentsScreen.js +78 -94
  230. package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -1
  231. package/lib/module/ui/screens/ManageAccountScreen.js +102 -137
  232. package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
  233. package/lib/module/ui/screens/NotificationsScreen.js +71 -77
  234. package/lib/module/ui/screens/NotificationsScreen.js.map +1 -1
  235. package/lib/module/ui/screens/PaymentGatewayScreen.js +4 -9
  236. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  237. package/lib/module/ui/screens/PreferencesScreen.js +69 -75
  238. package/lib/module/ui/screens/PreferencesScreen.js.map +1 -1
  239. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +58 -93
  240. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  241. package/lib/module/ui/screens/PrivacySettingsScreen.js +293 -305
  242. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  243. package/lib/module/ui/screens/ProfileScreen.js +102 -116
  244. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  245. package/lib/module/ui/screens/SavesCollectionsScreen.js +40 -46
  246. package/lib/module/ui/screens/SavesCollectionsScreen.js.map +1 -1
  247. package/lib/module/ui/screens/SearchSettingsScreen.js +46 -58
  248. package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -1
  249. package/lib/module/ui/screens/UserLinksScreen.js +26 -32
  250. package/lib/module/ui/screens/UserLinksScreen.js.map +1 -1
  251. package/lib/module/ui/screens/UserListScreen.js +14 -24
  252. package/lib/module/ui/screens/UserListScreen.js.map +1 -1
  253. package/lib/module/ui/screens/WelcomeNewUserScreen.js +17 -49
  254. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  255. package/lib/module/ui/screens/fileManagement/FileLibraryError.js +59 -0
  256. package/lib/module/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
  257. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +54 -42
  258. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  259. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +45 -81
  260. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  261. package/lib/module/ui/screens/trust/TrustAboutScreen.js +67 -75
  262. package/lib/module/ui/screens/trust/TrustAboutScreen.js.map +1 -1
  263. package/lib/module/ui/screens/trust/TrustCenterScreen.js +112 -139
  264. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  265. package/lib/module/ui/screens/trust/TrustFAQScreen.js +15 -21
  266. package/lib/module/ui/screens/trust/TrustFAQScreen.js.map +1 -1
  267. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js +14 -26
  268. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  269. package/lib/module/ui/screens/trust/TrustRewardsScreen.js +69 -85
  270. package/lib/module/ui/screens/trust/TrustRewardsScreen.js.map +1 -1
  271. package/lib/module/ui/screens/trust/TrustRulesScreen.js +30 -37
  272. package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  273. package/lib/module/ui/stores/resetSessionScopedStores.js +8 -3
  274. package/lib/module/ui/stores/resetSessionScopedStores.js.map +1 -1
  275. package/lib/module/ui/utils/avatarUtils.js +11 -2
  276. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  277. package/lib/module/ui/utils/fileManagement.js +0 -78
  278. package/lib/module/ui/utils/fileManagement.js.map +1 -1
  279. package/lib/typescript/commonjs/index.d.ts +2 -0
  280. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  281. package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts +4 -0
  282. package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts.map +1 -0
  283. package/lib/typescript/{module/ui/components/OxyAccountDialog.d.ts → commonjs/ui/components/OxyAccountDialogScreen.d.ts} +5 -4
  284. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -0
  285. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts +3 -3
  286. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  287. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +2 -2
  288. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  289. package/lib/typescript/commonjs/ui/components/ProfileSummaryCard.d.ts.map +1 -1
  290. package/lib/typescript/commonjs/ui/components/RequireOxyAuth.d.ts +1 -1
  291. package/lib/typescript/commonjs/ui/components/SurfaceHeaderAction.d.ts +16 -0
  292. package/lib/typescript/commonjs/ui/components/SurfaceHeaderAction.d.ts.map +1 -0
  293. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +3 -4
  294. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
  295. package/lib/typescript/commonjs/ui/components/fileManagement/FileDetailsModal.d.ts +14 -4
  296. package/lib/typescript/commonjs/ui/components/fileManagement/FileDetailsModal.d.ts.map +1 -1
  297. package/lib/typescript/commonjs/ui/components/modals/DeleteAccountModal.d.ts +17 -2
  298. package/lib/typescript/commonjs/ui/components/modals/DeleteAccountModal.d.ts.map +1 -1
  299. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +75 -13
  300. package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
  301. package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
  302. package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  303. package/lib/typescript/commonjs/ui/components/surfaces/ActionSheetSurface.d.ts +38 -0
  304. package/lib/typescript/commonjs/ui/components/surfaces/ActionSheetSurface.d.ts.map +1 -0
  305. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  306. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts +9 -0
  307. package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  308. package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts +50 -0
  309. package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
  310. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts +17 -16
  311. package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  312. package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts +14 -0
  313. package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts.map +1 -0
  314. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +31 -0
  315. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -0
  316. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +1 -1
  317. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +27 -2
  318. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -1
  319. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +4 -5
  320. package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  321. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
  322. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  323. package/lib/typescript/commonjs/ui/navigation/surfaceBackBridge.d.ts +16 -0
  324. package/lib/typescript/commonjs/ui/navigation/surfaceBackBridge.d.ts.map +1 -0
  325. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +21 -0
  326. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
  327. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +101 -23
  328. package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
  329. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts +42 -11
  330. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  331. package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  332. package/lib/typescript/commonjs/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  333. package/lib/typescript/commonjs/ui/screens/AccountVerificationScreen.d.ts.map +1 -1
  334. package/lib/typescript/commonjs/ui/screens/AppInfoScreen.d.ts.map +1 -1
  335. package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts +18 -9
  336. package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  337. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +34 -0
  338. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
  339. package/lib/typescript/commonjs/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  340. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  341. package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  342. package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -1
  343. package/lib/typescript/commonjs/ui/screens/FAQScreen.d.ts.map +1 -1
  344. package/lib/typescript/commonjs/ui/screens/FeedbackScreen.d.ts.map +1 -1
  345. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  346. package/lib/typescript/commonjs/ui/screens/HelpSupportScreen.d.ts.map +1 -1
  347. package/lib/typescript/commonjs/ui/screens/HistoryViewScreen.d.ts.map +1 -1
  348. package/lib/typescript/commonjs/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  349. package/lib/typescript/commonjs/ui/screens/LearnMoreUsernamesScreen.d.ts.map +1 -1
  350. package/lib/typescript/commonjs/ui/screens/LegalDocumentsScreen.d.ts.map +1 -1
  351. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
  352. package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  353. package/lib/typescript/commonjs/ui/screens/NotificationsScreen.d.ts.map +1 -1
  354. package/lib/typescript/commonjs/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  355. package/lib/typescript/commonjs/ui/screens/PreferencesScreen.d.ts.map +1 -1
  356. package/lib/typescript/commonjs/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
  357. package/lib/typescript/commonjs/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  358. package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
  359. package/lib/typescript/commonjs/ui/screens/SavesCollectionsScreen.d.ts.map +1 -1
  360. package/lib/typescript/commonjs/ui/screens/SearchSettingsScreen.d.ts.map +1 -1
  361. package/lib/typescript/commonjs/ui/screens/UserLinksScreen.d.ts.map +1 -1
  362. package/lib/typescript/commonjs/ui/screens/UserListScreen.d.ts.map +1 -1
  363. package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  364. package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
  365. package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
  366. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
  367. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  368. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
  369. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  370. package/lib/typescript/commonjs/ui/screens/trust/TrustAboutScreen.d.ts.map +1 -1
  371. package/lib/typescript/commonjs/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -1
  372. package/lib/typescript/commonjs/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -1
  373. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  374. package/lib/typescript/commonjs/ui/screens/trust/TrustRewardsScreen.d.ts.map +1 -1
  375. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  376. package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts +8 -1
  377. package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
  378. package/lib/typescript/commonjs/ui/types/navigation.d.ts +10 -0
  379. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  380. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  381. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +0 -48
  382. package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
  383. package/lib/typescript/module/index.d.ts +2 -0
  384. package/lib/typescript/module/index.d.ts.map +1 -1
  385. package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts +4 -0
  386. package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts.map +1 -0
  387. package/lib/typescript/{commonjs/ui/components/OxyAccountDialog.d.ts → module/ui/components/OxyAccountDialogScreen.d.ts} +5 -4
  388. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -0
  389. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts +3 -3
  390. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  391. package/lib/typescript/module/ui/components/ProfileButton.d.ts +2 -2
  392. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  393. package/lib/typescript/module/ui/components/ProfileSummaryCard.d.ts.map +1 -1
  394. package/lib/typescript/module/ui/components/RequireOxyAuth.d.ts +1 -1
  395. package/lib/typescript/module/ui/components/SurfaceHeaderAction.d.ts +16 -0
  396. package/lib/typescript/module/ui/components/SurfaceHeaderAction.d.ts.map +1 -0
  397. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +3 -4
  398. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
  399. package/lib/typescript/module/ui/components/fileManagement/FileDetailsModal.d.ts +14 -4
  400. package/lib/typescript/module/ui/components/fileManagement/FileDetailsModal.d.ts.map +1 -1
  401. package/lib/typescript/module/ui/components/modals/DeleteAccountModal.d.ts +17 -2
  402. package/lib/typescript/module/ui/components/modals/DeleteAccountModal.d.ts.map +1 -1
  403. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +75 -13
  404. package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
  405. package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
  406. package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
  407. package/lib/typescript/module/ui/components/surfaces/ActionSheetSurface.d.ts +38 -0
  408. package/lib/typescript/module/ui/components/surfaces/ActionSheetSurface.d.ts.map +1 -0
  409. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  410. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts +9 -0
  411. package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  412. package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts +50 -0
  413. package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
  414. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts +17 -16
  415. package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
  416. package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts +14 -0
  417. package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts.map +1 -0
  418. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +31 -0
  419. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -0
  420. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +1 -1
  421. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +27 -2
  422. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -1
  423. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +4 -5
  424. package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
  425. package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
  426. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  427. package/lib/typescript/module/ui/navigation/surfaceBackBridge.d.ts +16 -0
  428. package/lib/typescript/module/ui/navigation/surfaceBackBridge.d.ts.map +1 -0
  429. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +21 -0
  430. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
  431. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +101 -23
  432. package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
  433. package/lib/typescript/module/ui/navigation/surfaces.d.ts +42 -11
  434. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  435. package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  436. package/lib/typescript/module/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  437. package/lib/typescript/module/ui/screens/AccountVerificationScreen.d.ts.map +1 -1
  438. package/lib/typescript/module/ui/screens/AppInfoScreen.d.ts.map +1 -1
  439. package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts +18 -9
  440. package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
  441. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +34 -0
  442. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
  443. package/lib/typescript/module/ui/screens/ConnectedAppsScreen.d.ts.map +1 -1
  444. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  445. package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
  446. package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -1
  447. package/lib/typescript/module/ui/screens/FAQScreen.d.ts.map +1 -1
  448. package/lib/typescript/module/ui/screens/FeedbackScreen.d.ts.map +1 -1
  449. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  450. package/lib/typescript/module/ui/screens/HelpSupportScreen.d.ts.map +1 -1
  451. package/lib/typescript/module/ui/screens/HistoryViewScreen.d.ts.map +1 -1
  452. package/lib/typescript/module/ui/screens/LanguageSelectorScreen.d.ts.map +1 -1
  453. package/lib/typescript/module/ui/screens/LearnMoreUsernamesScreen.d.ts.map +1 -1
  454. package/lib/typescript/module/ui/screens/LegalDocumentsScreen.d.ts.map +1 -1
  455. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
  456. package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
  457. package/lib/typescript/module/ui/screens/NotificationsScreen.d.ts.map +1 -1
  458. package/lib/typescript/module/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  459. package/lib/typescript/module/ui/screens/PreferencesScreen.d.ts.map +1 -1
  460. package/lib/typescript/module/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
  461. package/lib/typescript/module/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  462. package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
  463. package/lib/typescript/module/ui/screens/SavesCollectionsScreen.d.ts.map +1 -1
  464. package/lib/typescript/module/ui/screens/SearchSettingsScreen.d.ts.map +1 -1
  465. package/lib/typescript/module/ui/screens/UserLinksScreen.d.ts.map +1 -1
  466. package/lib/typescript/module/ui/screens/UserListScreen.d.ts.map +1 -1
  467. package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  468. package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
  469. package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
  470. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
  471. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  472. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
  473. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  474. package/lib/typescript/module/ui/screens/trust/TrustAboutScreen.d.ts.map +1 -1
  475. package/lib/typescript/module/ui/screens/trust/TrustCenterScreen.d.ts.map +1 -1
  476. package/lib/typescript/module/ui/screens/trust/TrustFAQScreen.d.ts.map +1 -1
  477. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  478. package/lib/typescript/module/ui/screens/trust/TrustRewardsScreen.d.ts.map +1 -1
  479. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  480. package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts +8 -1
  481. package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
  482. package/lib/typescript/module/ui/types/navigation.d.ts +10 -0
  483. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  484. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  485. package/lib/typescript/module/ui/utils/fileManagement.d.ts +0 -48
  486. package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
  487. package/package.json +9 -5
  488. package/src/index.ts +12 -5
  489. package/src/ui/components/AvatarCameraBadge.tsx +35 -0
  490. package/src/ui/components/{OxyAccountDialog.tsx → OxyAccountDialogScreen.tsx} +50 -116
  491. package/src/ui/components/OxyAuthChooser.tsx +743 -86
  492. package/src/ui/components/OxyConsentScreen.tsx +1 -1
  493. package/src/ui/components/ProfileButton.tsx +18 -3
  494. package/src/ui/components/ProfileSummaryCard.tsx +2 -25
  495. package/src/ui/components/RequireOxyAuth.tsx +1 -1
  496. package/src/ui/components/SurfaceHeaderAction.tsx +60 -0
  497. package/src/ui/components/SurfaceScreen.tsx +108 -14
  498. package/src/ui/components/fileManagement/FileDetailsModal.tsx +50 -36
  499. package/src/ui/components/modals/DeleteAccountModal.tsx +47 -26
  500. package/src/ui/components/oxyAuthChooserStyles.ts +78 -16
  501. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -84
  502. package/src/ui/components/photogrid/__tests__/chunkPhotos.test.ts +51 -0
  503. package/src/ui/components/surfaces/ActionSheetSurface.tsx +98 -0
  504. package/src/ui/context/OxyContext.tsx +79 -25
  505. package/src/ui/hooks/queries/queryKeys.ts +15 -0
  506. package/src/ui/hooks/queries/useFileQueries.ts +162 -0
  507. package/src/ui/hooks/useAvatarPicker.ts +46 -74
  508. package/src/ui/hooks/useReduceMotion.ts +58 -0
  509. package/src/ui/hooks/useSurfaceHeader.ts +57 -0
  510. package/src/ui/hooks/useSwitchableAccounts.ts +1 -1
  511. package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +55 -7
  512. package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +149 -0
  513. package/src/ui/navigation/accountDialogManager.ts +42 -2
  514. package/src/ui/navigation/bottomSheetManager.ts +6 -7
  515. package/src/ui/navigation/routes.ts +6 -4
  516. package/src/ui/navigation/surfaceBackBridge.ts +69 -0
  517. package/src/ui/navigation/surfaceNavStack.ts +72 -9
  518. package/src/ui/navigation/surfaceRegistry.ts +164 -24
  519. package/src/ui/navigation/surfaces.ts +137 -30
  520. package/src/ui/screens/AccountMembersScreen.tsx +39 -100
  521. package/src/ui/screens/AccountSettingsScreen.tsx +32 -54
  522. package/src/ui/screens/AccountVerificationScreen.tsx +23 -51
  523. package/src/ui/screens/AppInfoScreen.tsx +6 -13
  524. package/src/ui/screens/AvatarCropScreen.tsx +340 -311
  525. package/src/ui/screens/ChangeAvatarScreen.tsx +359 -0
  526. package/src/ui/screens/ConnectedAppsScreen.tsx +39 -60
  527. package/src/ui/screens/CreateAccountScreen.tsx +13 -39
  528. package/src/ui/screens/EditProfileFieldScreen.tsx +30 -48
  529. package/src/ui/screens/EditProfileScreen.tsx +7 -16
  530. package/src/ui/screens/FAQScreen.tsx +9 -13
  531. package/src/ui/screens/FeedbackScreen.tsx +8 -22
  532. package/src/ui/screens/FileManagementScreen.tsx +370 -610
  533. package/src/ui/screens/HelpSupportScreen.tsx +7 -13
  534. package/src/ui/screens/HistoryViewScreen.tsx +26 -31
  535. package/src/ui/screens/LanguageSelectorScreen.tsx +12 -46
  536. package/src/ui/screens/LearnMoreUsernamesScreen.tsx +8 -16
  537. package/src/ui/screens/LegalDocumentsScreen.tsx +7 -22
  538. package/src/ui/screens/ManageAccountScreen.tsx +96 -165
  539. package/src/ui/screens/NotificationsScreen.tsx +7 -13
  540. package/src/ui/screens/PaymentGatewayScreen.tsx +3 -10
  541. package/src/ui/screens/PreferencesScreen.tsx +7 -13
  542. package/src/ui/screens/PremiumSubscriptionScreen.tsx +52 -90
  543. package/src/ui/screens/PrivacySettingsScreen.tsx +9 -21
  544. package/src/ui/screens/ProfileScreen.tsx +9 -11
  545. package/src/ui/screens/SavesCollectionsScreen.tsx +7 -13
  546. package/src/ui/screens/SearchSettingsScreen.tsx +9 -21
  547. package/src/ui/screens/UserLinksScreen.tsx +7 -14
  548. package/src/ui/screens/UserListScreen.tsx +2 -5
  549. package/src/ui/screens/WelcomeNewUserScreen.tsx +18 -48
  550. package/src/ui/screens/fileManagement/FileLibraryError.tsx +56 -0
  551. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +56 -46
  552. package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +55 -71
  553. package/src/ui/screens/trust/TrustAboutScreen.tsx +10 -17
  554. package/src/ui/screens/trust/TrustCenterScreen.tsx +9 -28
  555. package/src/ui/screens/trust/TrustFAQScreen.tsx +15 -18
  556. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +3 -5
  557. package/src/ui/screens/trust/TrustRewardsScreen.tsx +11 -26
  558. package/src/ui/screens/trust/TrustRulesScreen.tsx +10 -13
  559. package/src/ui/stores/resetSessionScopedStores.ts +8 -3
  560. package/src/ui/types/navigation.ts +10 -0
  561. package/src/ui/utils/avatarUtils.ts +11 -2
  562. package/src/ui/utils/fileManagement.ts +1 -101
  563. package/lib/commonjs/ui/components/Header.js +0 -305
  564. package/lib/commonjs/ui/components/Header.js.map +0 -1
  565. package/lib/commonjs/ui/components/OxyAccountDialog.js.map +0 -1
  566. package/lib/commonjs/ui/stores/fileStore.js +0 -153
  567. package/lib/commonjs/ui/stores/fileStore.js.map +0 -1
  568. package/lib/module/ui/components/Header.js +0 -299
  569. package/lib/module/ui/components/Header.js.map +0 -1
  570. package/lib/module/ui/components/OxyAccountDialog.js.map +0 -1
  571. package/lib/module/ui/stores/fileStore.js +0 -145
  572. package/lib/module/ui/stores/fileStore.js.map +0 -1
  573. package/lib/typescript/commonjs/ui/components/Header.d.ts +0 -33
  574. package/lib/typescript/commonjs/ui/components/Header.d.ts.map +0 -1
  575. package/lib/typescript/commonjs/ui/components/OxyAccountDialog.d.ts.map +0 -1
  576. package/lib/typescript/commonjs/ui/stores/fileStore.d.ts +0 -31
  577. package/lib/typescript/commonjs/ui/stores/fileStore.d.ts.map +0 -1
  578. package/lib/typescript/module/ui/components/Header.d.ts +0 -33
  579. package/lib/typescript/module/ui/components/Header.d.ts.map +0 -1
  580. package/lib/typescript/module/ui/components/OxyAccountDialog.d.ts.map +0 -1
  581. package/lib/typescript/module/ui/stores/fileStore.d.ts +0 -31
  582. package/lib/typescript/module/ui/stores/fileStore.d.ts.map +0 -1
  583. package/src/ui/components/Header.tsx +0 -224
  584. package/src/ui/stores/fileStore.ts +0 -118
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
1
+ import React, { useState, useCallback, useMemo, useRef } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -8,22 +8,26 @@ import {
8
8
  ActivityIndicator,
9
9
  RefreshControl,
10
10
  TextInput,
11
- Image,
12
- AccessibilityInfo,
13
11
  useWindowDimensions,
14
12
  } from 'react-native';
13
+ import { useQueryClient } from '@tanstack/react-query';
15
14
  import { Image as ExpoImage } from 'expo-image';
16
15
  import type { FileManagementScreenProps } from '../types/fileManagement';
17
- import { Dialog, toast, useDialogControl } from '@oxyhq/bloom';
16
+ import { toast } from '@oxyhq/bloom';
17
+ import { surfaces } from '@oxyhq/bloom/surfaces';
18
18
  import * as Skeleton from '@oxyhq/bloom/skeleton';
19
19
  import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
20
+ import { Pressable } from 'react-native';
20
21
  import type { FileMetadata } from '@oxyhq/core';
21
- import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
22
- import Header from '../components/Header';
22
+ import { queryKeys } from '../hooks/queries/queryKeys';
23
+ import { useUserFilesInfinite, removeFileFromCache, patchFileMetadataInCache } from '../hooks/queries/useFileQueries';
24
+ import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
25
+ import { SurfaceHeaderAction } from '../components/SurfaceHeaderAction';
23
26
  import JustifiedPhotoGrid from '../components/photogrid/JustifiedPhotoGrid';
24
27
  import { useTheme } from '@oxyhq/bloom/theme';
25
28
  import { useOxy } from '../context/OxyContext';
26
29
  import { useI18n } from '../hooks/useI18n';
30
+ import { useReduceMotion } from '../hooks/useReduceMotion';
27
31
  import { useUploadFile } from '../hooks/mutations/useAccountMutations';
28
32
  import {
29
33
  formatFileSize,
@@ -31,18 +35,35 @@ import {
31
35
  } from '../utils/fileManagement';
32
36
  import { useResolvedFileUrls, fileThumbSource } from '../hooks/useResolvedFileUrls';
33
37
  import { FileViewer } from '../components/fileManagement/FileViewer';
34
- import { FileDetailsModal } from '../components/fileManagement/FileDetailsModal';
38
+ import { presentFileDetails } from '../components/fileManagement/FileDetailsModal';
35
39
  import { UploadPreview } from '../components/fileManagement/UploadPreview';
40
+ import { presentActionSheet } from '../components/surfaces/ActionSheetSurface';
36
41
  import { getErrorMessage } from './fileManagement/shared';
37
42
  import { useFileUploadState } from './fileManagement/hooks/useFileUploadState';
38
43
  import PhotoPickerView from './fileManagement/PhotoPickerSection';
39
44
  import FileListSection, { type FileListItem } from './fileManagement/FileListSection';
45
+ import FileLibraryError from './fileManagement/FileLibraryError';
40
46
  import UploadBar from './fileManagement/UploadBar';
41
47
  import { AnimatedButton } from '../components/fileManagement/AnimatedButton';
42
48
 
43
49
  // Genuinely-inline-only styles: `viewModeButton` is spread into an Animated.View
44
50
  // style array (interpolated backgroundColor), and the photo tiles are
45
51
  // `expo-image` (no className remap). Everything else uses NativeWind classNames.
52
+ // Nav-header slot layout for this screen's action row + review-mode back button.
53
+ const fmHeaderStyles = StyleSheet.create({
54
+ actionsRow: {
55
+ flexDirection: 'row',
56
+ gap: 8,
57
+ },
58
+ backButton: {
59
+ width: 36,
60
+ height: 36,
61
+ borderRadius: 18,
62
+ alignItems: 'center',
63
+ justifyContent: 'center',
64
+ },
65
+ });
66
+
46
67
  const screenStyles = StyleSheet.create({
47
68
  viewModeButton: {
48
69
  paddingHorizontal: 10,
@@ -53,15 +74,14 @@ const screenStyles = StyleSheet.create({
53
74
  justifyContent: 'center',
54
75
  marginHorizontal: 1,
55
76
  },
56
- photoImage: { width: '100%', height: '100%', borderRadius: 8 },
57
77
  justifiedPhotoImage: { width: '100%', height: '100%', borderRadius: 6 },
58
78
  });
59
79
 
60
80
  const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
61
81
  onClose,
62
- theme,
63
82
  goBack,
64
83
  navigate,
84
+ dismiss,
65
85
  userId,
66
86
  containerWidth,
67
87
  selectMode = false,
@@ -82,13 +102,35 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
82
102
  // against `user`, so switching shows/manages that account's files.
83
103
  const { user, oxyServices } = useOxy();
84
104
  const { t } = useI18n();
105
+ const queryClient = useQueryClient();
85
106
  const uploadFileMutation = useUploadFile();
86
- // Prompt controls
87
- const fileDeleteDialog = useDialogControl();
88
- const bulkDeleteDialog = useDialogControl();
89
- const visibilityChangeDialog = useDialogControl();
90
- const [pendingDeleteFile, setPendingDeleteFile] = useState<{ id: string; name: string } | null>(null);
91
- const files = useFiles();
107
+
108
+ // Files are owned by the ACTIVE account. `targetUserId` scopes the query key,
109
+ // so switching accounts (or an explicit `userId` prop) shows that account's
110
+ // library with no manual reset.
111
+ const targetUserId = userId || user?.id;
112
+
113
+ // The file list is React Query's, NOT a store: infinite-paginated, fetched on
114
+ // mount + on owner change, edited optimistically via cache helpers. The
115
+ // rendered list is the flattened pages.
116
+ const filesQuery = useUserFilesInfinite(targetUserId);
117
+ const files = useMemo(
118
+ () => filesQuery.data?.pages.flatMap((page) => page.files) ?? [],
119
+ [filesQuery.data],
120
+ );
121
+ // No owner resolved yet reads as "loading" (matches the old skeleton-until-
122
+ // first-load behaviour); `isLoading` is the first-page fetch only.
123
+ const isLoadingFiles = !targetUserId || filesQuery.isLoading;
124
+ const isRefreshingFiles = filesQuery.isRefetching;
125
+ const isFetchingMore = filesQuery.isFetchingNextPage;
126
+ const hasMoreFiles = filesQuery.hasNextPage ?? false;
127
+ // Terminal load failure with nothing cached — render a DISTINCT error state
128
+ // (not the empty state) so a failed load never looks like an empty library.
129
+ const hasLoadError = filesQuery.isError && files.length === 0;
130
+ const invalidateFiles = useCallback(() => {
131
+ queryClient.invalidateQueries({ queryKey: queryKeys.files.list(targetUserId) });
132
+ }, [queryClient, targetUserId]);
133
+
92
134
  const { width: windowWidth } = useWindowDimensions();
93
135
  // Prefer an explicit sheet width from the router; fall back to the window
94
136
  // so photo grids never size against a hardcoded 400px default.
@@ -96,40 +138,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
96
138
  typeof containerWidth === 'number' && containerWidth > 0
97
139
  ? containerWidth
98
140
  : windowWidth;
99
- const uploading = useUploadingStore();
100
- const uploadProgress = useUploadAggregateProgress();
101
- const deleting = useDeletingStore();
102
- const [loading, setLoading] = useState(true);
103
- const [refreshing, setRefreshing] = useState(false);
104
- const [paging, setPaging] = useState({ offset: 0, limit: 40, total: 0, hasMore: true, loadingMore: false });
105
- const [selectedFile, setSelectedFile] = useState<FileMetadata | null>(null);
106
- const fileDetailsControl = useDialogControl();
141
+ // In-flight delete target (local UI state — was in the file store).
142
+ const [deletingId, setDeletingId] = useState<string | null>(null);
107
143
  // In selectMode we never open the detailed viewer
108
144
  const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
109
145
  const [fileContent, setFileContent] = useState<string | null>(null);
110
146
  const [loadingFileContent, setLoadingFileContent] = useState(false);
111
147
  const [showFileDetailsInViewer, setShowFileDetailsInViewer] = useState(false);
112
- const [reduceMotion, setReduceMotion] = useState(false);
113
-
114
- // Detect reduce-motion preference once on mount + subscribe to changes.
115
- // Used by `PhotoPickerView` to skip cell stagger animations.
116
- useEffect(() => {
117
- let cancelled = false;
118
- AccessibilityInfo.isReduceMotionEnabled()
119
- .then((enabled) => {
120
- if (!cancelled) setReduceMotion(enabled);
121
- })
122
- .catch(() => {
123
- // Defaults to false; no action needed.
124
- });
125
- const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', (enabled) => {
126
- setReduceMotion(enabled);
127
- });
128
- return () => {
129
- cancelled = true;
130
- sub.remove();
131
- };
132
- }, []);
148
+ // Reduce-motion preference, read as an external store (no effect). Passed to
149
+ // `PhotoPickerView` to skip cell stagger animations.
150
+ const reduceMotion = useReduceMotion();
133
151
 
134
152
  // Image-only picker mode: when the consumer restricts to image MIME types
135
153
  // (e.g. avatar picker), photos grid is the more useful default view.
@@ -203,25 +221,54 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
203
221
 
204
222
  return sorted;
205
223
  }, [files, searchQuery, viewMode, sortBy, sortOrder]);
206
- const [photoDimensions, setPhotoDimensions] = useState<{ [key: string]: { width: number, height: number } }>({});
207
- const photoDimensionsRef = useRef(photoDimensions);
208
- photoDimensionsRef.current = photoDimensions;
209
- const [loadingDimensions, setLoadingDimensions] = useState(false);
210
- // Selection state
211
- const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(initialSelectedIds));
212
- const [lastSelectedFileId, setLastSelectedFileId] = useState<string | null>(null);
224
+ // Selection state. `initialSelectedIds` is the INITIAL selection only (lazy
225
+ // init) there is no prop→state sync effect, since no caller mutates it
226
+ // after mount.
227
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set(initialSelectedIds));
213
228
  const scrollViewRef = useRef<ScrollView>(null);
214
229
  const photoScrollViewRef = useRef<ScrollView>(null);
215
- const containerRef = useRef<View>(null);
216
- useEffect(() => {
217
- if (initialSelectedIds?.length) {
218
- setSelectedIds(new Set(initialSelectedIds));
230
+
231
+ // Scroll the browse list/grid to a just-selected file. Driven directly from
232
+ // the selection event handler (double-rAF so the selection re-render has
233
+ // committed first) — not an effect. No-ops in the image-only picker, whose
234
+ // own FlatList is not either ScrollView ref.
235
+ const scrollToSelectedFile = useCallback((fileId: string) => {
236
+ if (viewMode === 'all' && scrollViewRef.current) {
237
+ const itemIndex = filteredFiles.findIndex(file => file.id === fileId);
238
+ if (itemIndex < 0) return;
239
+ // GroupedItem dense rows are ~65px, +30px when a description wraps.
240
+ const baseItemHeight = 65;
241
+ const descriptionHeight = 30;
242
+ let scrollPosition = 0;
243
+ for (let i = 0; i <= itemIndex && i < filteredFiles.length; i++) {
244
+ scrollPosition += baseItemHeight;
245
+ if (filteredFiles[i].metadata?.description) scrollPosition += descriptionHeight;
246
+ }
247
+ // Offset for the header/controls/search/stats chrome (~250px) so the
248
+ // item lands near the top, not under the chrome.
249
+ const finalScrollPosition = 250 + scrollPosition - 150;
250
+ requestAnimationFrame(() => {
251
+ requestAnimationFrame(() => {
252
+ scrollViewRef.current?.scrollTo({ y: Math.max(0, finalScrollPosition), animated: true });
253
+ });
254
+ });
255
+ } else if (viewMode === 'photos' && photoScrollViewRef.current) {
256
+ const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
257
+ const photoIndex = photos.findIndex(p => p.id === fileId);
258
+ if (photoIndex < 0) return;
259
+ // Rough estimate for the justified grid (3 photos/row, variable heights).
260
+ const row = Math.floor(photoIndex / 3);
261
+ const finalScrollPosition = row * (150 + 4) - 100;
262
+ requestAnimationFrame(() => {
263
+ requestAnimationFrame(() => {
264
+ photoScrollViewRef.current?.scrollTo({ y: Math.max(0, finalScrollPosition), animated: true });
265
+ });
266
+ });
219
267
  }
220
- }, [initialSelectedIds]);
268
+ }, [viewMode, filteredFiles]);
221
269
 
222
270
  const toggleSelect = useCallback(async (file: FileMetadata) => {
223
- // Allow selection in regular mode for bulk operations
224
- // if (!selectMode) return;
271
+ // Selection is allowed in regular mode too (for bulk operations).
225
272
  if (disabledMimeTypes.length) {
226
273
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
227
274
  if (blocked) {
@@ -240,9 +287,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
240
287
  }
241
288
  }
242
289
 
243
- // Track the selected file for scrolling
244
- setLastSelectedFileId(file.id);
245
-
246
290
  // Link file to entity if linkContext is provided
247
291
  if (linkContext) {
248
292
  try {
@@ -260,11 +304,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
260
304
  }
261
305
 
262
306
  if (!multiSelect) {
263
- onSelect?.(file);
264
- if (afterSelect === 'back') {
265
- goBack?.();
266
- } else if (afterSelect === 'close') {
267
- onClose?.();
307
+ if (onSelect) {
308
+ // Legacy callback caller (inbox, WelcomeNewUser): keep the
309
+ // callback + afterSelect behaviour unchanged.
310
+ onSelect(file);
311
+ if (afterSelect === 'back') {
312
+ goBack?.();
313
+ } else if (afterSelect === 'close') {
314
+ onClose?.();
315
+ }
316
+ } else {
317
+ // Promise-based picker (avatar picker): resolve the surface's
318
+ // present() promise with the picked file.
319
+ dismiss?.(file);
268
320
  }
269
321
  return;
270
322
  }
@@ -282,12 +334,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
282
334
  }
283
335
  return next;
284
336
  });
285
- }, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext]);
337
+ scrollToSelectedFile(file.id);
338
+ }, [multiSelect, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext, t, scrollToSelectedFile]);
286
339
 
287
340
  const confirmMultiSelection = useCallback(async () => {
288
341
  if (!selectMode || !multiSelect) return;
289
342
  const map: Record<string, FileMetadata> = {};
290
- files.forEach(f => { map[f.id] = f; });
343
+ for (const f of files) { map[f.id] = f; }
291
344
  const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
292
345
 
293
346
  // Update visibility and link files if needed
@@ -322,9 +375,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
322
375
  // Wait for all updates (but don't block on failures)
323
376
  await Promise.allSettled(updatePromises);
324
377
 
325
- onConfirmSelection?.(chosen);
326
- onClose?.();
327
- }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose, defaultVisibility, oxyServices, linkContext]);
378
+ if (onConfirmSelection) {
379
+ // Legacy callback caller (inbox): keep the callback + close.
380
+ onConfirmSelection(chosen);
381
+ onClose?.();
382
+ } else {
383
+ // Promise-based picker: resolve the surface with the chosen files.
384
+ dismiss?.(chosen);
385
+ }
386
+ }, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose, dismiss, defaultVisibility, oxyServices, linkContext]);
328
387
 
329
388
  // Private-safe thumbnail URLs for the currently-shown files, resolved in
330
389
  // ONE batch per page (not N per-tile) and cached by React Query. Uploads
@@ -346,70 +405,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
346
405
  const backgroundColor = colors.backgroundSecondary;
347
406
  const borderColor = colors.border;
348
407
 
349
- const targetUserId = userId || user?.id;
350
-
351
- const storeSetDeleting = useFileStore(s => s.setDeleting);
352
-
353
- const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' | 'more' = 'initial') => {
354
- if (!targetUserId) return;
355
-
356
- try {
357
- if (mode === 'refresh') {
358
- setRefreshing(true);
359
- } else if (mode === 'initial') {
360
- setLoading(true);
361
- setPaging(p => ({ ...p, offset: 0, hasMore: true }));
362
- } else if (mode === 'more') {
363
- // Prevent duplicate fetches
364
- setPaging(p => ({ ...p, loadingMore: true }));
365
- }
366
- const currentPaging = mode === 'more' ? (prevPagingRef.current ?? paging) : paging;
367
- const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
368
- const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
369
- const assets: FileMetadata[] = (response.files || []).map((f: { id: string; originalName?: string; sha256?: string; mime?: string; size?: number; createdAt?: string; metadata?: Record<string, unknown>; variants?: unknown[] }) => ({
370
- id: f.id,
371
- filename: f.originalName ?? f.sha256 ?? '',
372
- contentType: f.mime ?? '',
373
- length: f.size ?? 0,
374
- chunkSize: 0,
375
- uploadDate: f.createdAt ?? '',
376
- metadata: f.metadata ?? {},
377
- variants: (f.variants ?? []) as FileMetadata['variants'],
378
- }));
379
- if (mode === 'more') {
380
- // append
381
- useFileStore.getState().setFiles(assets, { merge: true });
382
- setPaging(p => ({
383
- ...p,
384
- offset: effectiveOffset,
385
- total: response.total || (effectiveOffset + assets.length),
386
- hasMore: response.hasMore,
387
- loadingMore: false,
388
- }));
389
- } else {
390
- useFileStore.getState().setFiles(assets, { merge: false });
391
- setPaging(p => ({
392
- ...p,
393
- offset: 0,
394
- total: response.total || assets.length,
395
- hasMore: response.hasMore,
396
- loadingMore: false,
397
- }));
398
- }
399
- } catch (error: unknown) {
400
- toast.error(getErrorMessage(error) || t('fileManagement.toasts.loadFailed'));
401
- } finally {
402
- setLoading(false);
403
- setRefreshing(false);
404
- setPaging(p => ({ ...p, loadingMore: false }));
405
- }
406
- }, [targetUserId, oxyServices, paging]);
407
-
408
408
  // Self-contained document-picking + upload-preview state and handlers.
409
+ // Optimistic uploads edit the file query cache directly (see
410
+ // useFileQueries), so no loader is threaded in.
409
411
  const {
410
412
  isPickingDocument,
411
413
  pendingFiles,
412
414
  showUploadPreview,
415
+ uploading,
416
+ uploadProgress,
413
417
  handleFileUpload,
414
418
  handleConfirmUpload,
415
419
  handleCancelUpload,
@@ -426,156 +430,61 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
426
430
  onClose,
427
431
  selectedIds,
428
432
  setSelectedIds,
429
- loadFiles,
430
433
  t,
431
434
  });
432
435
 
433
- // Keep a ref to avoid stale closure when calculating next offset
434
- const prevPagingRef = useRef(paging);
435
- useEffect(() => { prevPagingRef.current = paging; }, [paging]);
436
-
437
- // (removed effect; filteredFiles is memoized)
438
-
439
- // Load photo dimensions for justified grid - unified approach using Image.getSize
440
- const loadPhotoDimensions = useCallback(async (photos: FileMetadata[]) => {
441
- if (photos.length === 0) return;
442
-
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
-
449
- if (photosToLoad.length === 0) {
450
- setLoadingDimensions(false);
451
- return;
452
- }
453
-
454
- try {
455
- const measured: { [key: string]: { width: number; height: number } } = {};
456
-
457
- await Promise.all(
458
- photosToLoad.map(async (photo) => {
459
- try {
460
- const downloadUrl = thumbSourceFor(photo);
461
- // URL not resolved yet — skip (do NOT record fallback
462
- // dims) so this photo is re-measured once its private-safe
463
- // URL resolves. Measuring the broken public URL is exactly
464
- // the bug we are fixing.
465
- if (!downloadUrl) {
466
- return;
467
- }
468
-
469
- // Unified approach using Image.getSize (works on all platforms)
470
- await new Promise<void>((resolve) => {
471
- Image.getSize(
472
- downloadUrl,
473
- (width: number, height: number) => {
474
- measured[photo.id] = { width, height };
475
- resolve();
476
- },
477
- () => {
478
- // Fallback dimensions
479
- measured[photo.id] = { width: 1, height: 1 };
480
- resolve();
481
- }
482
- );
483
- });
484
- } catch (error) {
485
- // Fallback dimensions for any errors
486
- measured[photo.id] = { width: 1, height: 1 };
487
- }
488
- })
489
- );
490
-
491
- if (Object.keys(measured).length > 0) {
492
- setPhotoDimensions((prev) => ({ ...prev, ...measured }));
493
- }
494
- } catch (error) {
495
- // Photo dimensions loading failed, continue without dimensions
496
- } finally {
497
- setLoadingDimensions(false);
498
- }
499
- }, [thumbSourceFor]);
500
-
501
- // Re-measure photo dimensions when their private-safe URLs resolve. The
502
- // justified grid's own trigger fires on the photo SET, not on URL
503
- // availability, so newly-resolved private tiles would otherwise keep their
504
- // skipped (unmeasured) state and render with fallback geometry.
505
- useEffect(() => {
506
- if (viewMode !== 'photos') return;
507
- const photos = filteredFiles.filter((file) => file.contentType.startsWith('image/'));
508
- if (photos.length > 0) {
509
- loadPhotoDimensions(photos);
510
- }
511
- }, [resolvedThumbUrls, viewMode, filteredFiles, loadPhotoDimensions]);
512
-
513
- // Create justified rows from photos with responsive algorithm
514
- const createJustifiedRows = useCallback((photos: FileMetadata[], containerWidth: number) => {
515
- if (photos.length === 0) return [];
516
-
517
- const rows: FileMetadata[][] = [];
518
- const photosPerRow = 3; // Fixed 3 photos per row for consistency
519
-
520
- for (let i = 0; i < photos.length; i += photosPerRow) {
521
- const rowPhotos = photos.slice(i, i + photosPerRow);
522
- rows.push(rowPhotos);
523
- }
524
-
525
- return rows;
526
- }, []);
527
-
528
- const confirmFileDelete = useCallback((fileId: string, filename: string) => {
529
- setPendingDeleteFile({ id: fileId, name: filename });
530
- fileDeleteDialog.open();
531
- }, [fileDeleteDialog]);
532
-
533
- const handleFileDelete = useCallback(async () => {
534
- if (!pendingDeleteFile) return;
535
- const { id: fileId } = pendingDeleteFile;
436
+ const confirmFileDelete = useCallback(async (fileId: string, filename: string) => {
437
+ const confirmed = await surfaces.confirm({
438
+ title: t('fileManagement.deleteFile') || 'Delete File',
439
+ message: t('fileManagement.confirms.deleteFile', { filename }),
440
+ confirmLabel: t('fileManagement.confirm') || 'Delete',
441
+ cancelLabel: t('common.cancel') || 'Cancel',
442
+ destructive: true,
443
+ });
444
+ if (!confirmed) return;
536
445
 
537
446
  try {
538
- storeSetDeleting(fileId);
447
+ setDeletingId(fileId);
539
448
  await oxyServices.deleteFile(fileId);
540
449
 
541
450
  toast.success(t('fileManagement.toasts.deleteSuccess'));
542
451
 
543
- // Reload files after successful deletion
544
- // Optimistic remove
545
- useFileStore.getState().removeFile(fileId);
546
- // Silent background reconcile
547
- setTimeout(() => loadFiles('silent'), 800);
452
+ // Remove from the query cache, then reconcile against the server.
453
+ removeFileFromCache(queryClient, targetUserId, fileId);
454
+ invalidateFiles();
548
455
  } catch (error: unknown) {
549
-
550
456
  // Provide specific error messages
551
457
  if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
552
458
  toast.error(t('fileManagement.toasts.fileNotFound'));
553
- // Still reload files to refresh the list
554
- setTimeout(() => loadFiles('silent'), 800);
459
+ // It's already gone server-side reconcile the list.
460
+ invalidateFiles();
555
461
  } else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
556
462
  toast.error(t('fileManagement.toasts.noPermission'));
557
463
  } else {
558
464
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
559
465
  }
560
466
  } finally {
561
- storeSetDeleting(null);
562
- setPendingDeleteFile(null);
467
+ setDeletingId(null);
563
468
  }
564
- }, [pendingDeleteFile, storeSetDeleting, oxyServices, loadFiles, t]);
469
+ }, [oxyServices, queryClient, targetUserId, invalidateFiles, t]);
565
470
 
566
- const confirmBulkDelete = useCallback(() => {
471
+ const confirmBulkDelete = useCallback(async () => {
567
472
  if (selectedIds.size === 0) return;
568
- bulkDeleteDialog.open();
569
- }, [selectedIds.size, bulkDeleteDialog]);
570
473
 
571
- const handleBulkDelete = useCallback(async () => {
572
- if (selectedIds.size === 0) return;
474
+ const confirmed = await surfaces.confirm({
475
+ title: t('fileManagement.deleteFiles') || 'Delete Files',
476
+ message: t('fileManagement.confirms.deleteFiles', { count: selectedIds.size }),
477
+ confirmLabel: t('fileManagement.confirm') || 'Delete',
478
+ cancelLabel: t('common.cancel') || 'Cancel',
479
+ destructive: true,
480
+ });
481
+ if (!confirmed) return;
573
482
 
574
483
  try {
575
484
  const deletePromises = Array.from(selectedIds).map(async (fileId) => {
576
485
  try {
577
486
  await oxyServices.deleteFile(fileId);
578
- useFileStore.getState().removeFile(fileId);
487
+ removeFileFromCache(queryClient, targetUserId, fileId);
579
488
  return { success: true, fileId };
580
489
  } catch (error: unknown) {
581
490
  return { success: false, fileId, error };
@@ -594,11 +503,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
594
503
  }
595
504
 
596
505
  setSelectedIds(new Set());
597
- setTimeout(() => loadFiles('silent'), 800);
506
+ invalidateFiles();
598
507
  } catch (error: unknown) {
599
508
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
600
509
  }
601
- }, [selectedIds, files, oxyServices, loadFiles]);
510
+ }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
602
511
 
603
512
  const handleBulkVisibilityChange = useCallback(async (visibility: 'private' | 'public' | 'unlisted') => {
604
513
  if (selectedIds.size === 0) return;
@@ -619,25 +528,38 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
619
528
 
620
529
  if (successful > 0) {
621
530
  toast.success(t('fileManagement.toasts.visibilitySuccess', { count: successful, visibility }));
622
- // Update file metadata in store
623
- Array.from(selectedIds).forEach(fileId => {
624
- useFileStore.getState().updateFile(fileId, {
625
- metadata: { ...files.find(f => f.id === fileId)?.metadata, visibility } as Partial<FileMetadata>['metadata']
626
- });
627
- });
531
+ // Reflect the new visibility in the cached files.
532
+ for (const fileId of selectedIds) {
533
+ patchFileMetadataInCache(queryClient, targetUserId, fileId, { visibility });
534
+ }
628
535
  }
629
536
  if (failed > 0) {
630
537
  toast.error(t('fileManagement.toasts.visibilityFailed', { count: failed }));
631
538
  }
632
539
 
633
- setTimeout(() => loadFiles('silent'), 800);
540
+ invalidateFiles();
634
541
  } catch (error: unknown) {
635
542
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
636
543
  }
637
- }, [selectedIds, oxyServices, files, loadFiles]);
544
+ }, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
545
+
546
+ const handleVisibilityChange = useCallback(async () => {
547
+ if (selectedIds.size === 0) return;
548
+ const visibility = await presentActionSheet<'private' | 'public' | 'unlisted'>({
549
+ title: t('fileManagement.changeVisibility') || 'Change Visibility',
550
+ message: t('fileManagement.changeVisibilityConfirm', { count: selectedIds.size }),
551
+ options: [
552
+ { label: t('fileManagement.private') || 'Private', value: 'private' },
553
+ { label: t('fileManagement.public') || 'Public', value: 'public' },
554
+ { label: t('fileManagement.unlisted') || 'Unlisted', value: 'unlisted' },
555
+ ],
556
+ cancelLabel: t('common.cancel') || 'Cancel',
557
+ });
558
+ if (visibility) await handleBulkVisibilityChange(visibility);
559
+ }, [selectedIds.size, handleBulkVisibilityChange, t]);
638
560
 
639
561
  // Unified download function - works on all platforms
640
- const handleFileDownload = async (fileId: string, filename: string) => {
562
+ const handleFileDownload = useCallback(async (fileId: string, filename: string) => {
641
563
  try {
642
564
  // Resolve an authenticated, private-safe URL. The synchronous
643
565
  // `getFileDownloadUrl` yields the public CDN origin, which 404s for
@@ -681,10 +603,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
681
603
  } catch (error: unknown) {
682
604
  toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
683
605
  }
684
- };
606
+ }, [oxyServices, t]);
685
607
 
686
-
687
- const handleFileOpen = async (file: FileMetadata) => {
608
+ const handleFileOpen = useCallback(async (file: FileMetadata) => {
688
609
  if (selectMode) {
689
610
  toggleSelect(file);
690
611
  return;
@@ -736,19 +657,23 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
736
657
  } finally {
737
658
  setLoadingFileContent(false);
738
659
  }
739
- };
660
+ }, [selectMode, toggleSelect, oxyServices, t]);
740
661
 
741
- const handleCloseFile = () => {
662
+ const handleCloseFile = useCallback(() => {
742
663
  setOpenedFile(null);
743
664
  setFileContent(null);
744
665
  setShowFileDetailsInViewer(false);
745
666
  // Don't reset view mode when closing a file
746
- };
667
+ }, []);
747
668
 
748
- const showFileDetailsModal = (file: FileMetadata) => {
749
- setSelectedFile(file);
750
- fileDetailsControl.open();
751
- };
669
+ const showFileDetailsModal = useCallback((file: FileMetadata) => {
670
+ presentFileDetails({
671
+ file,
672
+ onDownload: handleFileDownload,
673
+ onDelete: confirmFileDelete,
674
+ isOwner: user?.id === targetUserId,
675
+ });
676
+ }, [handleFileDownload, confirmFileDelete, user?.id, targetUserId]);
752
677
 
753
678
  const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
754
679
  const downloadUrl = thumbSourceFor(photo);
@@ -786,18 +711,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
786
711
  </View>
787
712
  </TouchableOpacity>
788
713
  );
789
- }, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text]);
790
-
791
- // Run initial load once per targetUserId change to avoid accidental loops
792
- const lastLoadedFor = useRef<string | undefined>(undefined);
793
- useEffect(() => {
794
- const key = targetUserId || 'anonymous';
795
- if (lastLoadedFor.current !== key) {
796
- lastLoadedFor.current = key;
797
- loadFiles('initial');
798
- }
799
- // eslint-disable-next-line react-hooks/exhaustive-deps
800
- }, [targetUserId]);
714
+ }, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text, handleFileOpen]);
801
715
 
802
716
  // SettingsListItem-based file items (for 'all' view)
803
717
  const groupedFileItems: FileListItem[] = useMemo(() => {
@@ -895,9 +809,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
895
809
  className="w-[34px] h-[34px] rounded-[8px] items-center justify-center"
896
810
  style={{ backgroundColor: colors.negativeSubtle }}
897
811
  onPress={() => confirmFileDelete(file.id, file.filename)}
898
- disabled={deleting === file.id}
812
+ disabled={deletingId === file.id}
899
813
  >
900
- {deleting === file.id ? (
814
+ {deletingId === file.id ? (
901
815
  <ActivityIndicator size="small" color={colors.error} />
902
816
  ) : (
903
817
  <Ionicons name="trash" size={18} color={colors.error} />
@@ -907,89 +821,30 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
907
821
  ) : undefined,
908
822
  };
909
823
  });
910
- }, [filteredFiles, theme, deleting, handleFileDownload, confirmFileDelete, handleFileOpen, thumbSourceFor, selectMode, selectedIds]);
911
-
912
- // Scroll to selected file after selection
913
- useEffect(() => {
914
- if (lastSelectedFileId && selectMode) {
915
- if (viewMode === 'all' && scrollViewRef.current) {
916
- // Find the index of the selected file (filteredFiles is already sorted)
917
- const itemIndex = filteredFiles.findIndex(file => file.id === lastSelectedFileId);
918
-
919
- if (itemIndex >= 0) {
920
- // Estimate item height (GroupedItem with dense mode is approximately 60-70px)
921
- // Account for description rows which add extra height
922
- const baseItemHeight = 65;
923
- const descriptionHeight = 30; // Approximate height for description
924
- // Use filteredFiles which is already sorted according to user's selection
925
- const sortedFiles = filteredFiles;
926
-
927
- // Calculate total height up to this item
928
- let scrollPosition = 0;
929
- for (let i = 0; i <= itemIndex && i < sortedFiles.length; i++) {
930
- const file = sortedFiles[i];
931
- scrollPosition += baseItemHeight;
932
- if (file.metadata?.description) {
933
- scrollPosition += descriptionHeight;
934
- }
935
- }
936
-
937
- // Add header, controls, search, and stats height (approximately 250px)
938
- const headerHeight = 250;
939
- const finalScrollPosition = headerHeight + scrollPosition - 150; // Offset to show item near top
940
-
941
- // Use requestAnimationFrame to ensure DOM is updated before scrolling
942
- requestAnimationFrame(() => {
943
- requestAnimationFrame(() => {
944
- scrollViewRef.current?.scrollTo({
945
- y: Math.max(0, finalScrollPosition),
946
- animated: true,
947
- });
948
- });
949
- });
950
- }
951
- } else if (viewMode === 'photos' && photoScrollViewRef.current) {
952
- // For photo grid, find the photo index
953
- const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
954
- const photoIndex = photos.findIndex(p => p.id === lastSelectedFileId);
955
-
956
- if (photoIndex >= 0) {
957
- // Rough scroll estimate for the justified grid (3 photos/row; variable row heights).
958
- const itemsPerRow = 3;
959
- const estimatedRowHeight = 150;
960
- const rowGap = 4;
961
- const row = Math.floor(photoIndex / itemsPerRow);
962
- const finalScrollPosition = row * (estimatedRowHeight + rowGap) - 100;
963
-
964
- // Use requestAnimationFrame to ensure DOM is updated before scrolling
965
- requestAnimationFrame(() => {
966
- requestAnimationFrame(() => {
967
- photoScrollViewRef.current?.scrollTo({
968
- y: Math.max(0, finalScrollPosition),
969
- animated: true,
970
- });
971
- });
972
- });
973
- }
974
- }
975
- }
976
- }, [lastSelectedFileId, selectMode, viewMode, filteredFiles, safeContainerWidth]);
977
-
978
- // Clear selected file ID after scroll animation completes
979
- useEffect(() => {
980
- if (lastSelectedFileId && scrollViewRef.current) {
981
- const timeoutId = setTimeout(() => {
982
- setLastSelectedFileId(null);
983
- }, 600); // Allow time for scroll animation to complete
984
-
985
- return () => clearTimeout(timeoutId);
986
- }
987
- }, [lastSelectedFileId]);
824
+ }, [filteredFiles, colors, deletingId, toggleSelect, handleFileDownload, confirmFileDelete, handleFileOpen, thumbSourceFor, selectMode, selectedIds]);
825
+
826
+ // Browse-themed terminal load-failure state DISTINCT from the empty state.
827
+ const renderLoadError = () => (
828
+ <FileLibraryError
829
+ title={t('fileManagement.loadError.title')}
830
+ description={t('fileManagement.loadError.description')}
831
+ retryLabel={t('fileManagement.retry')}
832
+ onRetry={() => filesQuery.refetch()}
833
+ iconColor={colors.error}
834
+ titleColor={colors.text}
835
+ descriptionColor={colors.textSecondary}
836
+ buttonColor={colors.primary}
837
+ />
838
+ );
988
839
 
989
- const renderPhotoGrid = useCallback(() => {
840
+ // Browse "photos" view. The justified grid owns its own dimension
841
+ // measurement + reflow (see JustifiedPhotoGrid); this only supplies the
842
+ // photo set, the private-safe URL resolver, and the tile renderer.
843
+ const renderPhotoGrid = () => {
990
844
  const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
991
845
 
992
846
  if (photos.length === 0) {
847
+ if (hasLoadError) return renderLoadError();
993
848
  return (
994
849
  <View className="items-center py-[40px] px-[24px]">
995
850
  <Ionicons name="images-outline" size={64} color={colors.textTertiary} />
@@ -1006,9 +861,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1006
861
  onPress={handleFileUpload}
1007
862
  disabled={uploading || isPickingDocument}
1008
863
  >
1009
- {uploading ? (
1010
- <ActivityIndicator size="small" color="#FFFFFF" />
1011
- ) : isPickingDocument ? (
864
+ {(uploading || isPickingDocument) ? (
1012
865
  <ActivityIndicator size="small" color="#FFFFFF" />
1013
866
  ) : (
1014
867
  <>
@@ -1029,8 +882,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1029
882
  contentContainerClassName="p-[10px]"
1030
883
  refreshControl={
1031
884
  <RefreshControl
1032
- refreshing={refreshing}
1033
- onRefresh={() => loadFiles('refresh')}
885
+ refreshing={isRefreshingFiles}
886
+ onRefresh={() => filesQuery.refetch()}
1034
887
  tintColor={colors.primary}
1035
888
  />
1036
889
  }
@@ -1038,24 +891,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1038
891
  onScroll={({ nativeEvent }) => {
1039
892
  const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
1040
893
  const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
1041
- if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
1042
- loadFiles('more');
894
+ if (distanceFromBottom < 200 && !isFetchingMore && hasMoreFiles) {
895
+ filesQuery.fetchNextPage();
1043
896
  }
1044
897
  }}
1045
898
  scrollEventThrottle={250}
1046
899
  >
1047
- {loadingDimensions && (
1048
- <View className="flex-row items-center justify-center py-[16px] gap-[8px]">
1049
- <ActivityIndicator size="small" color={colors.primary} />
1050
- <Text className="text-[14px] italic" style={{ color: colors.textSecondary }}>{t('fileManagement.loadingPhotoLayout')}</Text>
1051
- </View>
1052
- )}
1053
-
1054
900
  <JustifiedPhotoGrid
1055
901
  photos={photos}
1056
- photoDimensions={photoDimensions}
1057
- loadPhotoDimensions={loadPhotoDimensions}
1058
- createJustifiedRows={createJustifiedRows}
902
+ getThumbUrl={thumbSourceFor}
1059
903
  renderJustifiedPhotoItem={renderJustifiedPhotoItem}
1060
904
  textColor={colors.text}
1061
905
  containerWidth={
@@ -1066,28 +910,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1066
910
  />
1067
911
  </ScrollView>
1068
912
  );
1069
- }, [
1070
- filteredFiles,
1071
- colors,
1072
- user?.id,
1073
- targetUserId,
1074
- uploading,
1075
- handleFileUpload,
1076
- refreshing,
1077
- loadFiles,
1078
- loadingDimensions,
1079
- photoDimensions,
1080
- loadPhotoDimensions,
1081
- createJustifiedRows,
1082
- renderJustifiedPhotoItem,
1083
- containerWidth,
1084
- ]);
1085
-
1086
- // Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
1087
-
1088
-
913
+ };
1089
914
 
1090
- const renderEmptyState = () => (
915
+ const renderEmptyState = () => hasLoadError ? renderLoadError() : (
1091
916
  <View className="items-center py-[40px] px-[24px]">
1092
917
  <Ionicons name="folder-open-outline" size={64} color={colors.textTertiary} />
1093
918
  <Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>{t('fileManagement.emptyFiles.title')}</Text>
@@ -1104,9 +929,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1104
929
  onPress={handleFileUpload}
1105
930
  disabled={uploading || isPickingDocument}
1106
931
  >
1107
- {uploading ? (
1108
- <ActivityIndicator size="small" color="#FFFFFF" />
1109
- ) : isPickingDocument ? (
932
+ {(uploading || isPickingDocument) ? (
1110
933
  <ActivityIndicator size="small" color="#FFFFFF" />
1111
934
  ) : (
1112
935
  <>
@@ -1129,7 +952,65 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1129
952
  // picker's placement-aware container width / columns / tile size and the
1130
953
  // Dialog-vs-bottom-sheet sizing. Skipping the browse chrome here is what keeps
1131
954
  // the picker from flashing the wrong (file-manager) skeleton inside the Dialog.
1132
- if (loading && !isImageOnlyPicker) {
955
+ // The Dialog owns the nav header; this screen contributes its state-dependent
956
+ // title/subtitle + action slots. The image-only picker mode is headerless
957
+ // (PhotoPickerView owns its own bar), so this config is simply ignored there.
958
+ const fmHeaderRight = useMemo<React.ReactNode>(() => {
959
+ const actions = selectMode && multiSelect
960
+ ? [
961
+ { key: 'clear', label: t('fileManagement.clear'), onPress: () => setSelectedIds(new Set()), disabled: selectedIds.size === 0 },
962
+ { key: 'confirm', label: t('fileManagement.confirm'), onPress: confirmMultiSelection, disabled: selectedIds.size === 0 },
963
+ ]
964
+ : !selectMode && selectedIds.size > 0
965
+ ? [
966
+ { key: 'clear', label: t('fileManagement.clear'), onPress: () => setSelectedIds(new Set()), disabled: false },
967
+ { key: 'delete', label: t('fileManagement.delete', { count: selectedIds.size }), onPress: confirmBulkDelete, disabled: false },
968
+ { key: 'visibility', label: t('fileManagement.visibility'), onPress: handleVisibilityChange, disabled: false },
969
+ ]
970
+ : [];
971
+ if (actions.length === 0) return undefined;
972
+ return (
973
+ <View style={fmHeaderStyles.actionsRow}>
974
+ {actions.map((a) => (
975
+ <SurfaceHeaderAction key={a.key} label={a.label} onPress={a.onPress} disabled={a.disabled} />
976
+ ))}
977
+ </View>
978
+ );
979
+ }, [selectMode, multiSelect, selectedIds, t, confirmMultiSelection, confirmBulkDelete, handleVisibilityChange]);
980
+
981
+ const fmHeaderBack = useMemo<React.ReactNode>(() => (
982
+ <Pressable
983
+ onPress={handleCancelUpload}
984
+ accessibilityRole="button"
985
+ accessibilityLabel={t('common.back') || 'Back'}
986
+ hitSlop={8}
987
+ style={fmHeaderStyles.backButton}
988
+ >
989
+ <Ionicons name="chevron-back" size={20} color={colors.text} />
990
+ </Pressable>
991
+ ), [handleCancelUpload, colors.text, t]);
992
+
993
+ useSurfaceHeader(
994
+ showUploadPreview
995
+ ? {
996
+ title: t('fileManagement.reviewFiles'),
997
+ subtitle: t('fileManagement.readyToUpload', { count: pendingFiles.length }),
998
+ left: fmHeaderBack,
999
+ largeTitle: false,
1000
+ }
1001
+ : {
1002
+ title: selectMode
1003
+ ? (multiSelect ? (maxSelection ? t('fileManagement.selectedWithMax', { count: selectedIds.size, max: maxSelection }) : t('fileManagement.selected', { count: selectedIds.size })) : t('fileManagement.selectFile'))
1004
+ : (viewMode === 'photos' ? t('fileManagement.photos') : t('fileManagement.title')),
1005
+ subtitle: selectMode
1006
+ ? (multiSelect ? t('fileManagement.available', { count: filteredFiles.length }) : t('fileManagement.tapToSelect'))
1007
+ : (filteredFiles.length === 1 ? t('fileManagement.itemCount', { count: filteredFiles.length }) : t('fileManagement.itemCount_plural', { count: filteredFiles.length })),
1008
+ right: fmHeaderRight,
1009
+ largeTitle: false,
1010
+ },
1011
+ );
1012
+
1013
+ if (isLoadingFiles && !isImageOnlyPicker) {
1133
1014
  const GRID_PADDING = 10;
1134
1015
  const TILE_GAP = 4;
1135
1016
  const GRID_COLUMNS = 3;
@@ -1190,71 +1071,61 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1190
1071
  const allowUpload = isOwner && allowUploadInSelectMode;
1191
1072
 
1192
1073
  return (
1193
- <>
1194
- <PhotoPickerView
1195
- photos={photosOnly}
1196
- selectedIds={selectedIds}
1197
- multiSelect={multiSelect}
1198
- maxSelection={maxSelection}
1199
- allowUpload={allowUpload}
1200
- refreshing={refreshing}
1201
- uploading={uploading}
1202
- isPickingDocument={isPickingDocument}
1203
- uploadProgress={uploadProgress}
1204
- hasMore={paging.hasMore}
1205
- loadingMore={paging.loadingMore}
1206
- loading={loading}
1207
- reduceMotion={reduceMotion}
1208
- getThumbUrl={thumbSourceFor}
1209
- primaryColor={colors.primary}
1210
- isOwner={isOwner}
1211
- onTogglePhoto={toggleSelect}
1212
- onPreviewPhoto={(file) => showFileDetailsModal(file)}
1213
- onUpload={handleFileUpload}
1214
- onRefresh={() => loadFiles('refresh')}
1215
- onLoadMore={() => loadFiles('more')}
1216
- onCancel={() => {
1074
+ <PhotoPickerView
1075
+ photos={photosOnly}
1076
+ selectedIds={selectedIds}
1077
+ multiSelect={multiSelect}
1078
+ maxSelection={maxSelection}
1079
+ allowUpload={allowUpload}
1080
+ refreshing={isRefreshingFiles}
1081
+ uploading={uploading}
1082
+ isPickingDocument={isPickingDocument}
1083
+ uploadProgress={uploadProgress}
1084
+ hasMore={hasMoreFiles}
1085
+ loadingMore={isFetchingMore}
1086
+ loading={isLoadingFiles}
1087
+ loadError={hasLoadError}
1088
+ onRetry={() => filesQuery.refetch()}
1089
+ reduceMotion={reduceMotion}
1090
+ getThumbUrl={thumbSourceFor}
1091
+ primaryColor={colors.primary}
1092
+ isOwner={isOwner}
1093
+ onTogglePhoto={toggleSelect}
1094
+ // Long-press preview surfaces the file-detail panel on the stack.
1095
+ onPreviewPhoto={(file) => showFileDetailsModal(file)}
1096
+ onUpload={handleFileUpload}
1097
+ onRefresh={() => filesQuery.refetch()}
1098
+ onLoadMore={() => filesQuery.fetchNextPage()}
1099
+ onCancel={() => {
1100
+ if (onSelect || onConfirmSelection) {
1101
+ // Legacy callback caller: preserve close/back behaviour.
1217
1102
  if (onClose) onClose();
1218
1103
  else goBack?.();
1219
- }}
1220
- onConfirm={confirmMultiSelection}
1221
- t={t}
1222
- />
1223
- {/* Long-press preview surfaces the existing details modal. */}
1224
- <FileDetailsModal
1225
- control={fileDetailsControl}
1226
- file={selectedFile}
1227
- onDownload={handleFileDownload}
1228
- onDelete={confirmFileDelete}
1229
- isOwner={isOwner}
1230
- />
1231
- </>
1104
+ } else {
1105
+ // Promise picker (avatar): dismiss just this surface.
1106
+ dismiss?.();
1107
+ }
1108
+ }}
1109
+ onConfirm={confirmMultiSelection}
1110
+ t={t}
1111
+ />
1232
1112
  );
1233
1113
  }
1234
1114
 
1235
1115
  // If a file is opened, show the file viewer
1236
1116
  if (!selectMode && openedFile) {
1237
1117
  return (
1238
- <>
1239
- <FileViewer
1240
- file={openedFile}
1241
- fileContent={fileContent}
1242
- loadingFileContent={loadingFileContent}
1243
- showFileDetailsInViewer={showFileDetailsInViewer}
1244
- onToggleDetails={() => setShowFileDetailsInViewer(!showFileDetailsInViewer)}
1245
- onClose={handleCloseFile}
1246
- onDownload={handleFileDownload}
1247
- onDelete={confirmFileDelete}
1248
- isOwner={user?.id === targetUserId}
1249
- />
1250
- <FileDetailsModal
1251
- control={fileDetailsControl}
1252
- file={selectedFile}
1253
- onDownload={handleFileDownload}
1254
- onDelete={confirmFileDelete}
1255
- isOwner={user?.id === targetUserId}
1256
- />
1257
- </>
1118
+ <FileViewer
1119
+ file={openedFile}
1120
+ fileContent={fileContent}
1121
+ loadingFileContent={loadingFileContent}
1122
+ showFileDetailsInViewer={showFileDetailsInViewer}
1123
+ onToggleDetails={() => setShowFileDetailsInViewer(!showFileDetailsInViewer)}
1124
+ onClose={handleCloseFile}
1125
+ onDownload={handleFileDownload}
1126
+ onDelete={confirmFileDelete}
1127
+ isOwner={user?.id === targetUserId}
1128
+ />
1258
1129
  );
1259
1130
  }
1260
1131
 
@@ -1262,15 +1133,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1262
1133
  if (showUploadPreview) {
1263
1134
  return (
1264
1135
  <View className="flex-1">
1265
- <Header
1266
- title={t('fileManagement.reviewFiles')}
1267
- subtitle={t('fileManagement.readyToUpload', { count: pendingFiles.length })}
1268
- onBack={handleCancelUpload}
1269
- showBackButton
1270
- variant="minimal"
1271
- elevation="none"
1272
- titleAlignment="left"
1273
- />
1274
1136
  <UploadPreview
1275
1137
  pendingFiles={pendingFiles}
1276
1138
  onConfirm={handleConfirmUpload}
@@ -1284,51 +1146,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1284
1146
 
1285
1147
  return (
1286
1148
  <View className="flex-1">
1287
- <Header
1288
- title={selectMode ? (multiSelect ? (maxSelection ? t('fileManagement.selectedWithMax', { count: selectedIds.size, max: maxSelection }) : t('fileManagement.selected', { count: selectedIds.size })) : t('fileManagement.selectFile')) : (viewMode === 'photos' ? t('fileManagement.photos') : t('fileManagement.title'))}
1289
- subtitle={selectMode ? (multiSelect ? t('fileManagement.available', { count: filteredFiles.length }) : t('fileManagement.tapToSelect')) : (filteredFiles.length === 1 ? t('fileManagement.itemCount', { count: filteredFiles.length }) : t('fileManagement.itemCount_plural', { count: filteredFiles.length }))}
1290
- actions={selectMode && multiSelect ? [
1291
- {
1292
- key: 'clear',
1293
- text: t('fileManagement.clear'),
1294
- onPress: () => setSelectedIds(new Set()),
1295
- disabled: selectedIds.size === 0,
1296
- },
1297
- {
1298
- key: 'confirm',
1299
- text: t('fileManagement.confirm'),
1300
- onPress: confirmMultiSelection,
1301
- disabled: selectedIds.size === 0,
1302
- }
1303
- ] : !selectMode && selectedIds.size > 0 ? [
1304
- {
1305
- key: 'clear',
1306
- text: t('fileManagement.clear'),
1307
- onPress: () => setSelectedIds(new Set()),
1308
- },
1309
- {
1310
- key: 'delete',
1311
- text: t('fileManagement.delete', { count: selectedIds.size }),
1312
- onPress: confirmBulkDelete,
1313
- icon: 'delete',
1314
- },
1315
- {
1316
- key: 'visibility',
1317
- text: t('fileManagement.visibility'),
1318
- onPress: () => {
1319
- visibilityChangeDialog.open();
1320
- },
1321
- icon: 'eye',
1322
- }
1323
- ] : undefined}
1324
- onBack={onClose || goBack}
1325
-
1326
- showBackButton
1327
- variant="minimal"
1328
- elevation="none"
1329
- titleAlignment="left"
1330
- />
1331
-
1332
1149
  <View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
1333
1150
  <ScrollView
1334
1151
  horizontal
@@ -1358,37 +1175,33 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1358
1175
  style={screenStyles.viewModeButton}
1359
1176
  accessibilityLabel={t('fileManagement.a11y.viewPhotos') || 'Show photos only'}
1360
1177
  />
1361
- {!isImageOnlyPicker && (
1362
- <>
1363
- <AnimatedButton
1364
- isSelected={viewMode === 'videos'}
1365
- onPress={() => setViewMode('videos')}
1366
- icon={viewMode === 'videos' ? 'video' : 'video-outline'}
1367
- primaryColor={colors.primary}
1368
- textColor={colors.text}
1369
- style={screenStyles.viewModeButton}
1370
- accessibilityLabel={t('fileManagement.a11y.viewVideos') || 'Show videos only'}
1371
- />
1372
- <AnimatedButton
1373
- isSelected={viewMode === 'documents'}
1374
- onPress={() => setViewMode('documents')}
1375
- icon={viewMode === 'documents' ? 'file-document' : 'file-document-outline'}
1376
- primaryColor={colors.primary}
1377
- textColor={colors.text}
1378
- style={screenStyles.viewModeButton}
1379
- accessibilityLabel={t('fileManagement.a11y.viewDocuments') || 'Show documents only'}
1380
- />
1381
- <AnimatedButton
1382
- isSelected={viewMode === 'audio'}
1383
- onPress={() => setViewMode('audio')}
1384
- icon={viewMode === 'audio' ? 'music-note' : 'music-note-outline'}
1385
- primaryColor={colors.primary}
1386
- textColor={colors.text}
1387
- style={screenStyles.viewModeButton}
1388
- accessibilityLabel={t('fileManagement.a11y.viewAudio') || 'Show audio only'}
1389
- />
1390
- </>
1391
- )}
1178
+ <AnimatedButton
1179
+ isSelected={viewMode === 'videos'}
1180
+ onPress={() => setViewMode('videos')}
1181
+ icon={viewMode === 'videos' ? 'video' : 'video-outline'}
1182
+ primaryColor={colors.primary}
1183
+ textColor={colors.text}
1184
+ style={screenStyles.viewModeButton}
1185
+ accessibilityLabel={t('fileManagement.a11y.viewVideos') || 'Show videos only'}
1186
+ />
1187
+ <AnimatedButton
1188
+ isSelected={viewMode === 'documents'}
1189
+ onPress={() => setViewMode('documents')}
1190
+ icon={viewMode === 'documents' ? 'file-document' : 'file-document-outline'}
1191
+ primaryColor={colors.primary}
1192
+ textColor={colors.text}
1193
+ style={screenStyles.viewModeButton}
1194
+ accessibilityLabel={t('fileManagement.a11y.viewDocuments') || 'Show documents only'}
1195
+ />
1196
+ <AnimatedButton
1197
+ isSelected={viewMode === 'audio'}
1198
+ onPress={() => setViewMode('audio')}
1199
+ icon={viewMode === 'audio' ? 'music-note' : 'music-note-outline'}
1200
+ primaryColor={colors.primary}
1201
+ textColor={colors.text}
1202
+ style={screenStyles.viewModeButton}
1203
+ accessibilityLabel={t('fileManagement.a11y.viewAudio') || 'Show audio only'}
1204
+ />
1392
1205
  </View>
1393
1206
  </ScrollView>
1394
1207
  <TouchableOpacity
@@ -1426,18 +1239,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1426
1239
  color={colors.textSecondary}
1427
1240
  />
1428
1241
  </TouchableOpacity>
1429
- {user?.id === targetUserId && (!selectMode || (selectMode && allowUploadInSelectMode)) && (
1242
+ {user?.id === targetUserId && (!selectMode || allowUploadInSelectMode) && (
1430
1243
  <TouchableOpacity
1431
- className={`h-[44px] rounded-[22px] items-center justify-center ${isImageOnlyPicker ? 'w-auto min-w-[44px] px-[14px]' : 'w-[44px]'}`}
1244
+ className="h-[44px] w-[44px] rounded-[22px] items-center justify-center"
1432
1245
  style={{ backgroundColor: colors.primary }}
1433
1246
  onPress={handleFileUpload}
1434
1247
  disabled={uploading || isPickingDocument}
1435
1248
  accessibilityRole="button"
1436
- accessibilityLabel={
1437
- isImageOnlyPicker
1438
- ? (t('fileManagement.a11y.uploadFromDevice') || 'Upload photo from device')
1439
- : (t('fileManagement.a11y.uploadFile') || 'Upload file')
1440
- }
1249
+ accessibilityLabel={t('fileManagement.a11y.uploadFile') || 'Upload file'}
1441
1250
  accessibilityState={{ busy: uploading || isPickingDocument }}
1442
1251
  >
1443
1252
  {uploading ? (
@@ -1451,13 +1260,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1451
1260
  </View>
1452
1261
  ) : isPickingDocument ? (
1453
1262
  <ActivityIndicator size="small" color="#FFFFFF" />
1454
- ) : isImageOnlyPicker ? (
1455
- <View className="flex-row items-center gap-[6px]">
1456
- <Ionicons name="cloud-upload" size={18} color="#FFFFFF" />
1457
- <Text className="text-white text-[14px] font-semibold" numberOfLines={1}>
1458
- {t('fileManagement.upload') || 'Upload'}
1459
- </Text>
1460
- </View>
1461
1263
  ) : (
1462
1264
  <Ionicons name="add" size={22} color="#FFFFFF" />
1463
1265
  )}
@@ -1531,27 +1333,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1531
1333
  filteredFiles={filteredFiles}
1532
1334
  searchQuery={searchQuery}
1533
1335
  items={groupedFileItems}
1534
- paging={paging}
1535
- refreshing={refreshing}
1336
+ paging={{ loadingMore: isFetchingMore, hasMore: hasMoreFiles }}
1337
+ refreshing={isRefreshingFiles}
1536
1338
  colors={colors}
1537
1339
  t={t}
1538
- onRefresh={() => loadFiles('refresh')}
1539
- onLoadMore={() => loadFiles('more')}
1340
+ onRefresh={() => filesQuery.refetch()}
1341
+ onLoadMore={() => filesQuery.fetchNextPage()}
1540
1342
  onClearSearch={() => setSearchQuery('')}
1541
1343
  renderEmptyState={renderEmptyState}
1542
1344
  />
1543
1345
  )}
1544
1346
 
1545
- {!selectMode && (
1546
- <FileDetailsModal
1547
- control={fileDetailsControl}
1548
- file={selectedFile}
1549
- onDownload={handleFileDownload}
1550
- onDelete={confirmFileDelete}
1551
- isOwner={user?.id === targetUserId}
1552
- />
1553
- )}
1554
-
1555
1347
  {/* Uploading banner overlay with progress */}
1556
1348
  {!selectMode && uploading && (
1557
1349
  <UploadBar
@@ -1561,38 +1353,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1561
1353
  t={t}
1562
1354
  />
1563
1355
  )}
1564
-
1565
- {/* Selection bar removed; actions are now in header */}
1566
- {/* Global loadingMore bar removed; now inline in scroll areas */}
1567
- <Dialog
1568
- control={fileDeleteDialog}
1569
- title={t('fileManagement.deleteFile') || 'Delete File'}
1570
- description={pendingDeleteFile ? t('fileManagement.confirms.deleteFile', { filename: pendingDeleteFile.name }) : ''}
1571
- actions={[
1572
- { label: t('fileManagement.confirm') || 'Delete', color: 'destructive', onPress: handleFileDelete },
1573
- { label: t('common.cancel') || 'Cancel', color: 'cancel' },
1574
- ]}
1575
- />
1576
- <Dialog
1577
- control={bulkDeleteDialog}
1578
- title={t('fileManagement.deleteFiles') || 'Delete Files'}
1579
- description={t('fileManagement.confirms.deleteFiles', { count: selectedIds.size })}
1580
- actions={[
1581
- { label: t('fileManagement.confirm') || 'Delete', color: 'destructive', onPress: handleBulkDelete },
1582
- { label: t('common.cancel') || 'Cancel', color: 'cancel' },
1583
- ]}
1584
- />
1585
- <Dialog
1586
- control={visibilityChangeDialog}
1587
- title={t('fileManagement.changeVisibility') || 'Change Visibility'}
1588
- description={t('fileManagement.changeVisibilityConfirm', { count: selectedIds.size })}
1589
- actions={[
1590
- { label: t('fileManagement.private') || 'Private', onPress: () => handleBulkVisibilityChange('private') },
1591
- { label: t('fileManagement.public') || 'Public', onPress: () => handleBulkVisibilityChange('public') },
1592
- { label: t('fileManagement.unlisted') || 'Unlisted', onPress: () => handleBulkVisibilityChange('unlisted') },
1593
- { label: t('common.cancel') || 'Cancel', color: 'cancel' },
1594
- ]}
1595
- />
1596
1356
  </View>
1597
1357
  );
1598
1358
  };