@oxyhq/services 5.10.15 → 5.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/README.md +604 -127
  2. package/lib/commonjs/assets/assets/icons/OxyServices.tsx +2 -2
  3. package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +1 -1
  4. package/lib/commonjs/assets/icons/OxyServices.js +0 -2
  5. package/lib/commonjs/assets/icons/OxyServices.js.map +1 -1
  6. package/lib/commonjs/assets/illustrations/HighFive.js +0 -2
  7. package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -1
  8. package/lib/commonjs/core/OxyServices.js +244 -26
  9. package/lib/commonjs/core/OxyServices.js.map +1 -1
  10. package/lib/commonjs/core/index.js +7 -0
  11. package/lib/commonjs/core/index.js.map +1 -1
  12. package/lib/commonjs/index.js +93 -0
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/commonjs/lib/sonner-safe.js +2 -0
  15. package/lib/commonjs/lib/sonner-safe.js.map +1 -1
  16. package/lib/commonjs/lib/sonner.js +2 -0
  17. package/lib/commonjs/lib/sonner.js.map +1 -1
  18. package/lib/commonjs/node/index.js +7 -0
  19. package/lib/commonjs/node/index.js.map +1 -1
  20. package/lib/commonjs/ui/components/Avatar.js +0 -2
  21. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  22. package/lib/commonjs/ui/components/FollowButton.js +4 -4
  23. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  24. package/lib/commonjs/ui/components/FontLoader.js +3 -2
  25. package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
  26. package/lib/commonjs/ui/components/GroupedItem.js +7 -4
  27. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  28. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  29. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  30. package/lib/commonjs/ui/components/Header.js +0 -1
  31. package/lib/commonjs/ui/components/Header.js.map +1 -1
  32. package/lib/commonjs/ui/components/OxyLogo.js +0 -2
  33. package/lib/commonjs/ui/components/OxyLogo.js.map +1 -1
  34. package/lib/commonjs/ui/components/OxyPayButton.js +4 -5
  35. package/lib/commonjs/ui/components/OxyPayButton.js.map +1 -1
  36. package/lib/commonjs/ui/components/OxyProvider.js +4 -3
  37. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  38. package/lib/commonjs/ui/components/OxySignInButton.js +0 -1
  39. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  40. package/lib/commonjs/ui/components/ProfileCard.js +0 -1
  41. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  42. package/lib/commonjs/ui/components/QuickActions.js +0 -2
  43. package/lib/commonjs/ui/components/QuickActions.js.map +1 -1
  44. package/lib/commonjs/ui/components/Section.js +0 -1
  45. package/lib/commonjs/ui/components/Section.js.map +1 -1
  46. package/lib/commonjs/ui/components/SectionTitle.js +0 -2
  47. package/lib/commonjs/ui/components/SectionTitle.js.map +1 -1
  48. package/lib/commonjs/ui/components/icon/FAIRWalletIcon.js +0 -2
  49. package/lib/commonjs/ui/components/icon/FAIRWalletIcon.js.map +1 -1
  50. package/lib/commonjs/ui/components/icon/OxyIcon.js +0 -2
  51. package/lib/commonjs/ui/components/icon/OxyIcon.js.map +1 -1
  52. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +0 -2
  53. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
  54. package/lib/commonjs/ui/components/internal/PinInput.js +4 -3
  55. package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
  56. package/lib/commonjs/ui/components/internal/TextField.js +3 -3
  57. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  58. package/lib/commonjs/ui/hooks/useAssets.js +263 -0
  59. package/lib/commonjs/ui/hooks/useAssets.js.map +1 -0
  60. package/lib/commonjs/ui/navigation/OxyRouter.js +1 -2
  61. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  62. package/lib/commonjs/ui/screens/AccountCenterScreen.js +0 -2
  63. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  64. package/lib/commonjs/ui/screens/AccountManagementDemo.js +0 -1
  65. package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
  66. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +2 -2
  67. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  68. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +5 -6
  69. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  70. package/lib/commonjs/ui/screens/AppInfoScreen.js +1 -2
  71. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  72. package/lib/commonjs/ui/screens/FileManagementScreen.js +631 -430
  73. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  74. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js +2 -2
  75. package/lib/commonjs/ui/screens/PaymentGatewayScreen.js.map +1 -1
  76. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +1 -2
  77. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  78. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -2
  79. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  80. package/lib/commonjs/ui/screens/RecoverAccountScreen.js +1 -2
  81. package/lib/commonjs/ui/screens/RecoverAccountScreen.js.map +1 -1
  82. package/lib/commonjs/ui/screens/SessionManagementScreen.js +1 -2
  83. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  84. package/lib/commonjs/ui/screens/SignInScreen.js +1 -2
  85. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  86. package/lib/commonjs/ui/screens/UserLinksScreen.js +0 -2
  87. package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -1
  88. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +1 -2
  89. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  90. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +1 -2
  91. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  92. package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js +1 -2
  93. package/lib/commonjs/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
  94. package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js +1 -2
  95. package/lib/commonjs/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
  96. package/lib/commonjs/ui/screens/internal/SignUpSummaryStep.js +0 -1
  97. package/lib/commonjs/ui/screens/internal/SignUpSummaryStep.js.map +1 -1
  98. package/lib/commonjs/ui/screens/internal/SignUpWelcomeStep.js +0 -1
  99. package/lib/commonjs/ui/screens/internal/SignUpWelcomeStep.js.map +1 -1
  100. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +0 -2
  101. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
  102. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -2
  103. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  104. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +1 -2
  105. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  106. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +0 -2
  107. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
  108. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -2
  109. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  110. package/lib/commonjs/ui/stores/assetStore.js +225 -0
  111. package/lib/commonjs/ui/stores/assetStore.js.map +1 -0
  112. package/lib/commonjs/ui/stores/authStore.js +1 -1
  113. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  114. package/lib/commonjs/ui/stores/fileStore.js +153 -0
  115. package/lib/commonjs/ui/stores/fileStore.js.map +1 -0
  116. package/lib/commonjs/ui/stores/followStore.js +2 -2
  117. package/lib/commonjs/ui/stores/followStore.js.map +1 -1
  118. package/lib/commonjs/utils/asyncUtils.js +1 -1
  119. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  120. package/lib/commonjs/utils/errorUtils.js +19 -11
  121. package/lib/commonjs/utils/errorUtils.js.map +1 -1
  122. package/lib/commonjs/utils/hookUtils.js +1 -1
  123. package/lib/commonjs/utils/hookUtils.js.map +1 -1
  124. package/lib/commonjs/utils/loggerUtils.js.map +1 -1
  125. package/lib/commonjs/utils/validationUtils.js +2 -2
  126. package/lib/commonjs/utils/validationUtils.js.map +1 -1
  127. package/lib/module/assets/assets/icons/OxyServices.tsx +2 -2
  128. package/lib/module/assets/assets/illustrations/HighFive.tsx +1 -1
  129. package/lib/module/assets/icons/OxyServices.js +0 -1
  130. package/lib/module/assets/icons/OxyServices.js.map +1 -1
  131. package/lib/module/assets/illustrations/HighFive.js +0 -1
  132. package/lib/module/assets/illustrations/HighFive.js.map +1 -1
  133. package/lib/module/core/OxyServices.js +243 -25
  134. package/lib/module/core/OxyServices.js.map +1 -1
  135. package/lib/module/core/index.js +1 -1
  136. package/lib/module/core/index.js.map +1 -1
  137. package/lib/module/index.js +3 -1
  138. package/lib/module/index.js.map +1 -1
  139. package/lib/module/lib/sonner-safe.js +2 -0
  140. package/lib/module/lib/sonner-safe.js.map +1 -1
  141. package/lib/module/lib/sonner.js +3 -0
  142. package/lib/module/lib/sonner.js.map +1 -1
  143. package/lib/module/node/index.js +2 -2
  144. package/lib/module/node/index.js.map +1 -1
  145. package/lib/module/ui/components/Avatar.js +0 -1
  146. package/lib/module/ui/components/Avatar.js.map +1 -1
  147. package/lib/module/ui/components/FollowButton.js +4 -3
  148. package/lib/module/ui/components/FollowButton.js.map +1 -1
  149. package/lib/module/ui/components/FontLoader.js +3 -2
  150. package/lib/module/ui/components/FontLoader.js.map +1 -1
  151. package/lib/module/ui/components/GroupedItem.js +7 -3
  152. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  153. package/lib/module/ui/components/GroupedSection.js +1 -1
  154. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  155. package/lib/module/ui/components/Header.js +0 -1
  156. package/lib/module/ui/components/Header.js.map +1 -1
  157. package/lib/module/ui/components/OxyLogo.js +0 -1
  158. package/lib/module/ui/components/OxyLogo.js.map +1 -1
  159. package/lib/module/ui/components/OxyPayButton.js +4 -4
  160. package/lib/module/ui/components/OxyPayButton.js.map +1 -1
  161. package/lib/module/ui/components/OxyProvider.js +4 -2
  162. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  163. package/lib/module/ui/components/OxySignInButton.js +0 -1
  164. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  165. package/lib/module/ui/components/ProfileCard.js +0 -1
  166. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  167. package/lib/module/ui/components/QuickActions.js +0 -1
  168. package/lib/module/ui/components/QuickActions.js.map +1 -1
  169. package/lib/module/ui/components/Section.js +0 -1
  170. package/lib/module/ui/components/Section.js.map +1 -1
  171. package/lib/module/ui/components/SectionTitle.js +0 -1
  172. package/lib/module/ui/components/SectionTitle.js.map +1 -1
  173. package/lib/module/ui/components/icon/FAIRWalletIcon.js +0 -1
  174. package/lib/module/ui/components/icon/FAIRWalletIcon.js.map +1 -1
  175. package/lib/module/ui/components/icon/OxyIcon.js +0 -1
  176. package/lib/module/ui/components/icon/OxyIcon.js.map +1 -1
  177. package/lib/module/ui/components/internal/GroupedPillButtons.js +0 -1
  178. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
  179. package/lib/module/ui/components/internal/PinInput.js +4 -2
  180. package/lib/module/ui/components/internal/PinInput.js.map +1 -1
  181. package/lib/module/ui/components/internal/TextField.js +3 -3
  182. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  183. package/lib/module/ui/context/OxyContext.js.map +1 -1
  184. package/lib/module/ui/hooks/useAssets.js +257 -0
  185. package/lib/module/ui/hooks/useAssets.js.map +1 -0
  186. package/lib/module/ui/navigation/OxyRouter.js +1 -1
  187. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  188. package/lib/module/ui/screens/AccountCenterScreen.js +0 -1
  189. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  190. package/lib/module/ui/screens/AccountManagementDemo.js +0 -1
  191. package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
  192. package/lib/module/ui/screens/AccountSettingsScreen.js +2 -2
  193. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  194. package/lib/module/ui/screens/AccountSwitcherScreen.js +5 -5
  195. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  196. package/lib/module/ui/screens/AppInfoScreen.js +1 -1
  197. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  198. package/lib/module/ui/screens/FileManagementScreen.js +633 -432
  199. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  200. package/lib/module/ui/screens/PaymentGatewayScreen.js +1 -1
  201. package/lib/module/ui/screens/PaymentGatewayScreen.js.map +1 -1
  202. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +1 -1
  203. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  204. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  205. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  206. package/lib/module/ui/screens/RecoverAccountScreen.js +1 -1
  207. package/lib/module/ui/screens/RecoverAccountScreen.js.map +1 -1
  208. package/lib/module/ui/screens/SessionManagementScreen.js +1 -1
  209. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  210. package/lib/module/ui/screens/SignInScreen.js +1 -1
  211. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  212. package/lib/module/ui/screens/UserLinksScreen.js +0 -1
  213. package/lib/module/ui/screens/UserLinksScreen.js.map +1 -1
  214. package/lib/module/ui/screens/internal/SignInPasswordStep.js +1 -1
  215. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  216. package/lib/module/ui/screens/internal/SignInUsernameStep.js +1 -1
  217. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  218. package/lib/module/ui/screens/internal/SignUpIdentityStep.js +1 -1
  219. package/lib/module/ui/screens/internal/SignUpIdentityStep.js.map +1 -1
  220. package/lib/module/ui/screens/internal/SignUpSecurityStep.js +1 -1
  221. package/lib/module/ui/screens/internal/SignUpSecurityStep.js.map +1 -1
  222. package/lib/module/ui/screens/internal/SignUpSummaryStep.js +0 -1
  223. package/lib/module/ui/screens/internal/SignUpSummaryStep.js.map +1 -1
  224. package/lib/module/ui/screens/internal/SignUpWelcomeStep.js +0 -1
  225. package/lib/module/ui/screens/internal/SignUpWelcomeStep.js.map +1 -1
  226. package/lib/module/ui/screens/karma/KarmaAboutScreen.js +0 -1
  227. package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
  228. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  229. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  230. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
  231. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  232. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +0 -1
  233. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
  234. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
  235. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  236. package/lib/module/ui/stores/assetStore.js +212 -0
  237. package/lib/module/ui/stores/assetStore.js.map +1 -0
  238. package/lib/module/ui/stores/authStore.js +1 -1
  239. package/lib/module/ui/stores/authStore.js.map +1 -1
  240. package/lib/module/ui/stores/fileStore.js +145 -0
  241. package/lib/module/ui/stores/fileStore.js.map +1 -0
  242. package/lib/module/ui/stores/followStore.js +2 -2
  243. package/lib/module/ui/stores/followStore.js.map +1 -1
  244. package/lib/module/ui/styles/fonts.js.map +1 -1
  245. package/lib/module/ui/styles/theme.js.map +1 -1
  246. package/lib/module/utils/asyncUtils.js +1 -1
  247. package/lib/module/utils/asyncUtils.js.map +1 -1
  248. package/lib/module/utils/errorUtils.js +19 -11
  249. package/lib/module/utils/errorUtils.js.map +1 -1
  250. package/lib/module/utils/hookUtils.js +1 -1
  251. package/lib/module/utils/hookUtils.js.map +1 -1
  252. package/lib/module/utils/loggerUtils.js.map +1 -1
  253. package/lib/module/utils/validationUtils.js +2 -2
  254. package/lib/module/utils/validationUtils.js.map +1 -1
  255. package/lib/typescript/assets/icons/OxyServices.d.ts +2 -2
  256. package/lib/typescript/assets/icons/OxyServices.d.ts.map +1 -1
  257. package/lib/typescript/assets/illustrations/HighFive.d.ts +1 -1
  258. package/lib/typescript/assets/illustrations/HighFive.d.ts.map +1 -1
  259. package/lib/typescript/core/OxyServices.d.ts +69 -12
  260. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  261. package/lib/typescript/core/index.d.ts +1 -1
  262. package/lib/typescript/core/index.d.ts.map +1 -1
  263. package/lib/typescript/index.d.ts +4 -2
  264. package/lib/typescript/index.d.ts.map +1 -1
  265. package/lib/typescript/lib/sonner-safe.d.ts +2 -1
  266. package/lib/typescript/lib/sonner-safe.d.ts.map +1 -1
  267. package/lib/typescript/lib/sonner.d.ts +11 -3
  268. package/lib/typescript/lib/sonner.d.ts.map +1 -1
  269. package/lib/typescript/models/interfaces.d.ts +105 -6
  270. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  271. package/lib/typescript/node/index.d.ts +2 -2
  272. package/lib/typescript/node/index.d.ts.map +1 -1
  273. package/lib/typescript/types/expo-vector-icons.d.ts +8 -1
  274. package/lib/typescript/types/express.d.ts +22 -3
  275. package/lib/typescript/ui/components/Avatar.d.ts +2 -2
  276. package/lib/typescript/ui/components/Avatar.d.ts.map +1 -1
  277. package/lib/typescript/ui/components/FollowButton.d.ts +2 -2
  278. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  279. package/lib/typescript/ui/components/FontLoader.d.ts +1 -1
  280. package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
  281. package/lib/typescript/ui/components/GroupedItem.d.ts +2 -1
  282. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  283. package/lib/typescript/ui/components/GroupedSection.d.ts +2 -1
  284. package/lib/typescript/ui/components/GroupedSection.d.ts.map +1 -1
  285. package/lib/typescript/ui/components/Header.d.ts +1 -1
  286. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  287. package/lib/typescript/ui/components/OxyLogo.d.ts +2 -2
  288. package/lib/typescript/ui/components/OxyLogo.d.ts.map +1 -1
  289. package/lib/typescript/ui/components/OxyPayButton.d.ts +2 -2
  290. package/lib/typescript/ui/components/OxyPayButton.d.ts.map +1 -1
  291. package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
  292. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  293. package/lib/typescript/ui/components/OxySignInButton.d.ts +2 -2
  294. package/lib/typescript/ui/components/OxySignInButton.d.ts.map +1 -1
  295. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -1
  296. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  297. package/lib/typescript/ui/components/QuickActions.d.ts +1 -1
  298. package/lib/typescript/ui/components/QuickActions.d.ts.map +1 -1
  299. package/lib/typescript/ui/components/Section.d.ts +1 -1
  300. package/lib/typescript/ui/components/Section.d.ts.map +1 -1
  301. package/lib/typescript/ui/components/SectionTitle.d.ts +1 -1
  302. package/lib/typescript/ui/components/SectionTitle.d.ts.map +1 -1
  303. package/lib/typescript/ui/components/icon/FAIRWalletIcon.d.ts +1 -1
  304. package/lib/typescript/ui/components/icon/FAIRWalletIcon.d.ts.map +1 -1
  305. package/lib/typescript/ui/components/icon/OxyIcon.d.ts +1 -1
  306. package/lib/typescript/ui/components/icon/OxyIcon.d.ts.map +1 -1
  307. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts +1 -1
  308. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
  309. package/lib/typescript/ui/components/internal/PinInput.d.ts +1 -1
  310. package/lib/typescript/ui/components/internal/PinInput.d.ts.map +1 -1
  311. package/lib/typescript/ui/components/internal/TextField.d.ts +1 -1
  312. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  313. package/lib/typescript/ui/context/OxyContext.d.ts +3 -3
  314. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  315. package/lib/typescript/ui/hooks/useAssets.d.ts +35 -0
  316. package/lib/typescript/ui/hooks/useAssets.d.ts.map +1 -0
  317. package/lib/typescript/ui/navigation/OxyRouter.d.ts +2 -2
  318. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  319. package/lib/typescript/ui/navigation/types.d.ts +7 -7
  320. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  321. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts +2 -2
  322. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  323. package/lib/typescript/ui/screens/AccountManagementDemo.d.ts +1 -1
  324. package/lib/typescript/ui/screens/AccountManagementDemo.d.ts.map +1 -1
  325. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts +1 -1
  326. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  327. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts +1 -1
  328. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  329. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts +2 -2
  330. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  331. package/lib/typescript/ui/screens/AppInfoScreen.d.ts +2 -2
  332. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  333. package/lib/typescript/ui/screens/FeedbackScreen.d.ts +1 -1
  334. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  335. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +1 -1
  336. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  337. package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts +2 -2
  338. package/lib/typescript/ui/screens/PaymentGatewayScreen.d.ts.map +1 -1
  339. package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts +2 -2
  340. package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
  341. package/lib/typescript/ui/screens/ProfileScreen.d.ts +2 -2
  342. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  343. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts +1 -1
  344. package/lib/typescript/ui/screens/RecoverAccountScreen.d.ts.map +1 -1
  345. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts +2 -2
  346. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  347. package/lib/typescript/ui/screens/SignInScreen.d.ts +2 -2
  348. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  349. package/lib/typescript/ui/screens/SignUpScreen.d.ts +1 -1
  350. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  351. package/lib/typescript/ui/screens/UserLinksScreen.d.ts +2 -2
  352. package/lib/typescript/ui/screens/UserLinksScreen.d.ts.map +1 -1
  353. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +1 -1
  354. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  355. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +1 -1
  356. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  357. package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts +1 -1
  358. package/lib/typescript/ui/screens/internal/SignUpIdentityStep.d.ts.map +1 -1
  359. package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts +1 -1
  360. package/lib/typescript/ui/screens/internal/SignUpSecurityStep.d.ts.map +1 -1
  361. package/lib/typescript/ui/screens/internal/SignUpSummaryStep.d.ts +1 -1
  362. package/lib/typescript/ui/screens/internal/SignUpSummaryStep.d.ts.map +1 -1
  363. package/lib/typescript/ui/screens/internal/SignUpWelcomeStep.d.ts +1 -1
  364. package/lib/typescript/ui/screens/internal/SignUpWelcomeStep.d.ts.map +1 -1
  365. package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts +2 -2
  366. package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -1
  367. package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts +2 -2
  368. package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -1
  369. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts +1 -1
  370. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
  371. package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts +2 -2
  372. package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -1
  373. package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts +2 -2
  374. package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -1
  375. package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts +2 -2
  376. package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -1
  377. package/lib/typescript/ui/stores/assetStore.d.ts +54 -0
  378. package/lib/typescript/ui/stores/assetStore.d.ts.map +1 -0
  379. package/lib/typescript/ui/stores/fileStore.d.ts +31 -0
  380. package/lib/typescript/ui/stores/fileStore.d.ts.map +1 -0
  381. package/lib/typescript/ui/stores/followStore.d.ts +1 -1
  382. package/lib/typescript/ui/stores/followStore.d.ts.map +1 -1
  383. package/lib/typescript/ui/styles/fonts.d.ts +1 -1
  384. package/lib/typescript/ui/styles/fonts.d.ts.map +1 -1
  385. package/lib/typescript/ui/styles/theme.d.ts +1 -1
  386. package/lib/typescript/ui/styles/theme.d.ts.map +1 -1
  387. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  388. package/lib/typescript/utils/errorUtils.d.ts +5 -5
  389. package/lib/typescript/utils/errorUtils.d.ts.map +1 -1
  390. package/lib/typescript/utils/hookUtils.d.ts +4 -4
  391. package/lib/typescript/utils/hookUtils.d.ts.map +1 -1
  392. package/lib/typescript/utils/loggerUtils.d.ts +18 -18
  393. package/lib/typescript/utils/loggerUtils.d.ts.map +1 -1
  394. package/lib/typescript/utils/validationUtils.d.ts +6 -6
  395. package/lib/typescript/utils/validationUtils.d.ts.map +1 -1
  396. package/package.json +149 -143
  397. package/src/assets/icons/OxyServices.tsx +2 -2
  398. package/src/assets/illustrations/HighFive.tsx +1 -1
  399. package/src/core/OxyServices.ts +268 -41
  400. package/src/core/__tests__/OxyServices.test.ts +180 -0
  401. package/src/core/index.ts +1 -1
  402. package/src/index.ts +16 -2
  403. package/src/lib/sonner-safe.ts +4 -1
  404. package/src/lib/sonner.ts +19 -2
  405. package/src/models/interfaces.ts +117 -6
  406. package/src/node/index.ts +2 -2
  407. package/src/types/expo-vector-icons.d.ts +8 -1
  408. package/src/types/express.d.ts +22 -3
  409. package/src/ui/components/Avatar.tsx +2 -2
  410. package/src/ui/components/FollowButton.tsx +10 -8
  411. package/src/ui/components/FontLoader.tsx +5 -3
  412. package/src/ui/components/GroupedItem.tsx +12 -2
  413. package/src/ui/components/GroupedSection.tsx +3 -1
  414. package/src/ui/components/Header.tsx +1 -1
  415. package/src/ui/components/OxyLogo.tsx +2 -2
  416. package/src/ui/components/OxyPayButton.tsx +6 -5
  417. package/src/ui/components/OxyProvider.tsx +7 -4
  418. package/src/ui/components/OxySignInButton.tsx +2 -2
  419. package/src/ui/components/ProfileCard.tsx +1 -1
  420. package/src/ui/components/QuickActions.tsx +1 -1
  421. package/src/ui/components/Section.tsx +1 -1
  422. package/src/ui/components/SectionTitle.tsx +1 -1
  423. package/src/ui/components/icon/FAIRWalletIcon.tsx +1 -1
  424. package/src/ui/components/icon/OxyIcon.tsx +1 -1
  425. package/src/ui/components/internal/GroupedPillButtons.tsx +1 -1
  426. package/src/ui/components/internal/PinInput.tsx +3 -2
  427. package/src/ui/components/internal/TextField.tsx +9 -11
  428. package/src/ui/context/OxyContext.tsx +3 -3
  429. package/src/ui/hooks/useAssets.ts +306 -0
  430. package/src/ui/navigation/OxyRouter.tsx +3 -2
  431. package/src/ui/navigation/types.ts +8 -8
  432. package/src/ui/screens/AccountCenterScreen.tsx +2 -2
  433. package/src/ui/screens/AccountManagementDemo.tsx +1 -1
  434. package/src/ui/screens/AccountOverviewScreen.tsx +1 -1
  435. package/src/ui/screens/AccountSettingsScreen.tsx +3 -3
  436. package/src/ui/screens/AccountSwitcherScreen.tsx +9 -8
  437. package/src/ui/screens/AppInfoScreen.tsx +3 -2
  438. package/src/ui/screens/FeedbackScreen.tsx +1 -1
  439. package/src/ui/screens/FileManagementScreen.tsx +619 -494
  440. package/src/ui/screens/PaymentGatewayScreen.tsx +3 -2
  441. package/src/ui/screens/PremiumSubscriptionScreen.tsx +3 -2
  442. package/src/ui/screens/ProfileScreen.tsx +3 -2
  443. package/src/ui/screens/RecoverAccountScreen.tsx +3 -2
  444. package/src/ui/screens/SessionManagementScreen.tsx +4 -3
  445. package/src/ui/screens/SignInScreen.tsx +3 -2
  446. package/src/ui/screens/SignUpScreen.tsx +1 -1
  447. package/src/ui/screens/UserLinksScreen.tsx +2 -2
  448. package/src/ui/screens/internal/SignInPasswordStep.tsx +3 -2
  449. package/src/ui/screens/internal/SignInUsernameStep.tsx +3 -2
  450. package/src/ui/screens/internal/SignUpIdentityStep.tsx +3 -2
  451. package/src/ui/screens/internal/SignUpSecurityStep.tsx +3 -2
  452. package/src/ui/screens/internal/SignUpSummaryStep.tsx +1 -1
  453. package/src/ui/screens/internal/SignUpWelcomeStep.tsx +1 -1
  454. package/src/ui/screens/karma/KarmaAboutScreen.tsx +2 -2
  455. package/src/ui/screens/karma/KarmaCenterScreen.tsx +3 -2
  456. package/src/ui/screens/karma/KarmaFAQScreen.tsx +1 -1
  457. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +3 -2
  458. package/src/ui/screens/karma/KarmaRewardsScreen.tsx +2 -2
  459. package/src/ui/screens/karma/KarmaRulesScreen.tsx +3 -2
  460. package/src/ui/stores/assetStore.ts +281 -0
  461. package/src/ui/stores/authStore.ts +1 -1
  462. package/src/ui/stores/fileStore.ts +118 -0
  463. package/src/ui/stores/followStore.ts +4 -4
  464. package/src/ui/styles/fonts.ts +1 -1
  465. package/src/ui/styles/theme.ts +1 -1
  466. package/src/utils/__tests__/validationUtils.test.ts +236 -0
  467. package/src/utils/asyncUtils.ts +4 -4
  468. package/src/utils/errorUtils.ts +35 -23
  469. package/src/utils/hookUtils.ts +7 -7
  470. package/src/utils/loggerUtils.ts +18 -18
  471. package/src/utils/validationUtils.ts +8 -8
@@ -6,30 +6,20 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
+ var _expoImage = require("expo-image");
9
10
  var _OxyContext = require("../context/OxyContext");
10
11
  var _fonts = require("../styles/fonts");
11
12
  var _sonner = require("../../lib/sonner");
12
13
  var _vectorIcons = require("@expo/vector-icons");
14
+ var _fileStore = require("../stores/fileStore");
15
+ var _Header = _interopRequireDefault(require("../components/Header"));
16
+ var _components = require("../components");
13
17
  var _jsxRuntime = require("react/jsx-runtime");
18
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
19
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
20
  // Add this helper function near the top (after imports):
16
- async function uploadFileRaw(file, userId) {
17
- const fileName = file.name || 'upload.bin';
18
- const mimeType = file.type || 'application/octet-stream';
19
- const res = await fetch('/api/files/upload-raw', {
20
- method: 'POST',
21
- headers: {
22
- 'Content-Type': mimeType,
23
- 'X-File-Name': encodeURIComponent(fileName),
24
- 'X-User-Id': userId
25
- },
26
- body: file,
27
- credentials: 'include' // if you use cookies/session
28
- });
29
- if (!res.ok) {
30
- throw new Error(await res.text());
31
- }
32
- return await res.json();
21
+ async function uploadFileRaw(file, userId, oxyServices) {
22
+ return await oxyServices.uploadRawFile(file);
33
23
  }
34
24
  const FileManagementScreen = ({
35
25
  onClose,
@@ -55,12 +45,12 @@ const FileManagementScreen = ({
55
45
  console.log('[FileManagementScreen] Available content width:', availableContentWidth);
56
46
  console.log('[FileManagementScreen] Spacing fix applied: 4px uniform gap both horizontal and vertical');
57
47
  }, [containerWidth]);
58
- const [files, setFiles] = (0, _react.useState)([]);
48
+ const files = (0, _fileStore.useFiles)();
49
+ const uploading = (0, _fileStore.useUploading)();
50
+ const uploadProgress = (0, _fileStore.useUploadAggregateProgress)();
51
+ const deleting = (0, _fileStore.useDeleting)();
59
52
  const [loading, setLoading] = (0, _react.useState)(true);
60
53
  const [refreshing, setRefreshing] = (0, _react.useState)(false);
61
- const [uploading, setUploading] = (0, _react.useState)(false);
62
- const [uploadProgress, setUploadProgress] = (0, _react.useState)(null);
63
- const [deleting, setDeleting] = (0, _react.useState)(null);
64
54
  const [selectedFile, setSelectedFile] = (0, _react.useState)(null);
65
55
  const [showFileDetails, setShowFileDetails] = (0, _react.useState)(false);
66
56
  const [openedFile, setOpenedFile] = (0, _react.useState)(null);
@@ -69,11 +59,66 @@ const FileManagementScreen = ({
69
59
  const [showFileDetailsInViewer, setShowFileDetailsInViewer] = (0, _react.useState)(false);
70
60
  const [viewMode, setViewMode] = (0, _react.useState)('all');
71
61
  const [searchQuery, setSearchQuery] = (0, _react.useState)('');
72
- const [filteredFiles, setFilteredFiles] = (0, _react.useState)([]);
62
+ // Derived filtered files (avoid setState loops)
63
+ const filteredFiles = (0, _react.useMemo)(() => {
64
+ let filteredByMode = files;
65
+ if (viewMode === 'photos') {
66
+ filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
67
+ }
68
+ if (!searchQuery.trim()) {
69
+ return filteredByMode;
70
+ }
71
+ const query = searchQuery.toLowerCase();
72
+ return filteredByMode.filter(file => file.filename.toLowerCase().includes(query) || file.contentType.toLowerCase().includes(query) || file.metadata?.description && file.metadata.description.toLowerCase().includes(query));
73
+ }, [files, searchQuery, viewMode]);
73
74
  const [isDragging, setIsDragging] = (0, _react.useState)(false);
74
75
  const [photoDimensions, setPhotoDimensions] = (0, _react.useState)({});
75
76
  const [loadingDimensions, setLoadingDimensions] = (0, _react.useState)(false);
76
77
  const [hoveredPreview, setHoveredPreview] = (0, _react.useState)(null);
78
+ const uploadStartRef = (0, _react.useRef)(null);
79
+ const MIN_BANNER_MS = 600;
80
+ const endUpload = (0, _react.useCallback)(() => {
81
+ const started = uploadStartRef.current;
82
+ const elapsed = started ? Date.now() - started : MIN_BANNER_MS;
83
+ const remaining = elapsed < MIN_BANNER_MS ? MIN_BANNER_MS - elapsed : 0;
84
+ setTimeout(() => {
85
+ _fileStore.useFileStore.getState().setUploading(false);
86
+ uploadStartRef.current = null;
87
+ }, remaining);
88
+ }, []);
89
+
90
+ // Helper to safely request a thumbnail variant only for image mime types.
91
+ // Prevents backend warnings: "Variant thumb not supported for mime application/pdf".
92
+ const getSafeDownloadUrl = (0, _react.useCallback)((file, variant = 'thumb') => {
93
+ const isImage = file.contentType.startsWith('image/');
94
+ const isVideo = file.contentType.startsWith('video/');
95
+
96
+ // Prefer explicit variant key if variants metadata present
97
+ if (file.variants && file.variants.length > 0) {
98
+ // For videos, try 'poster' regardless of requested variant
99
+ if (isVideo) {
100
+ const poster = file.variants.find(v => v.type === 'poster');
101
+ if (poster) return oxyServices.getFileDownloadUrl(file.id, 'poster');
102
+ }
103
+ if (isImage) {
104
+ const desired = file.variants.find(v => v.type === variant);
105
+ if (desired) return oxyServices.getFileDownloadUrl(file.id, variant);
106
+ }
107
+ }
108
+ if (isImage) {
109
+ return oxyServices.getFileDownloadUrl(file.id, variant);
110
+ }
111
+ if (isVideo) {
112
+ // Fallback to poster if backend supports implicit generation
113
+ try {
114
+ return oxyServices.getFileDownloadUrl(file.id, 'poster');
115
+ } catch {
116
+ return oxyServices.getFileDownloadUrl(file.id);
117
+ }
118
+ }
119
+ // Other mime types: no variant
120
+ return oxyServices.getFileDownloadUrl(file.id);
121
+ }, [oxyServices]);
77
122
 
78
123
  // Memoize theme-related calculations to prevent unnecessary recalculations
79
124
  const themeStyles = (0, _react.useMemo)(() => {
@@ -94,16 +139,32 @@ const FileManagementScreen = ({
94
139
  const backgroundColor = themeStyles.backgroundColor;
95
140
  const borderColor = themeStyles.borderColor;
96
141
  const targetUserId = userId || user?.id;
97
- const loadFiles = (0, _react.useCallback)(async (isRefresh = false) => {
142
+ const storeSetUploading = (0, _fileStore.useFileStore)(s => s.setUploading);
143
+ const storeSetUploadProgress = (0, _fileStore.useFileStore)(s => s.setUploadProgress);
144
+ const storeSetDeleting = (0, _fileStore.useFileStore)(s => s.setDeleting);
145
+ const loadFiles = (0, _react.useCallback)(async (mode = 'initial') => {
98
146
  if (!targetUserId) return;
99
147
  try {
100
- if (isRefresh) {
148
+ if (mode === 'refresh') {
101
149
  setRefreshing(true);
102
- } else {
150
+ } else if (mode === 'initial') {
103
151
  setLoading(true);
104
152
  }
105
- const response = await oxyServices.listUserFiles(targetUserId);
106
- setFiles(response.files || []);
153
+ const response = await oxyServices.listUserFiles();
154
+ const assets = (response.files || []).map(f => ({
155
+ id: f.id,
156
+ filename: f.originalName || f.sha256,
157
+ contentType: f.mime,
158
+ length: f.size,
159
+ chunkSize: 0,
160
+ uploadDate: f.createdAt,
161
+ metadata: f.metadata || {},
162
+ variants: f.variants || []
163
+ }));
164
+ // Merge to preserve existing order & allow incremental updates
165
+ _fileStore.useFileStore.getState().setFiles(assets, {
166
+ merge: true
167
+ });
107
168
  } catch (error) {
108
169
  console.error('Failed to load files:', error);
109
170
  _sonner.toast.error(error.message || 'Failed to load files');
@@ -113,24 +174,7 @@ const FileManagementScreen = ({
113
174
  }
114
175
  }, [targetUserId, oxyServices]);
115
176
 
116
- // Filter files based on search query and view mode
117
- (0, _react.useEffect)(() => {
118
- let filteredByMode = files;
119
-
120
- // Filter by view mode first
121
- if (viewMode === 'photos') {
122
- filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
123
- }
124
-
125
- // Then filter by search query
126
- if (!searchQuery.trim()) {
127
- setFilteredFiles(filteredByMode);
128
- } else {
129
- const query = searchQuery.toLowerCase();
130
- const filtered = filteredByMode.filter(file => file.filename.toLowerCase().includes(query) || file.contentType.toLowerCase().includes(query) || file.metadata?.description && file.metadata.description.toLowerCase().includes(query));
131
- setFilteredFiles(filtered);
132
- }
133
- }, [files, searchQuery, viewMode]);
177
+ // (removed effect; filteredFiles is memoized)
134
178
 
135
179
  // Load photo dimensions for justified grid
136
180
  const loadPhotoDimensions = (0, _react.useCallback)(async photos => {
@@ -150,7 +194,7 @@ const FileManagementScreen = ({
150
194
  try {
151
195
  await Promise.all(photosToLoad.map(async photo => {
152
196
  try {
153
- const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
197
+ const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
154
198
  if (_reactNative.Platform.OS === 'web') {
155
199
  const img = new window.Image();
156
200
  await new Promise((resolve, reject) => {
@@ -229,7 +273,7 @@ const FileManagementScreen = ({
229
273
  if (selectedFiles.length === 0) return;
230
274
  if (!targetUserId) return; // Guard clause to ensure userId is defined
231
275
  try {
232
- setUploadProgress({
276
+ storeSetUploadProgress({
233
277
  current: 0,
234
278
  total: selectedFiles.length
235
279
  });
@@ -244,12 +288,55 @@ const FileManagementScreen = ({
244
288
  let failureCount = 0;
245
289
  const errors = [];
246
290
  for (let i = 0; i < selectedFiles.length; i++) {
247
- setUploadProgress({
291
+ storeSetUploadProgress({
248
292
  current: i + 1,
249
293
  total: selectedFiles.length
250
294
  });
251
295
  try {
252
- await uploadFileRaw(selectedFiles[i], targetUserId);
296
+ const raw = selectedFiles[i];
297
+ const optimisticId = `temp-${Date.now()}-${i}`;
298
+ const optimisticFile = {
299
+ id: optimisticId,
300
+ filename: raw.name,
301
+ contentType: raw.type || 'application/octet-stream',
302
+ length: raw.size,
303
+ chunkSize: 0,
304
+ uploadDate: new Date().toISOString(),
305
+ metadata: {
306
+ uploading: true
307
+ },
308
+ variants: []
309
+ };
310
+ _fileStore.useFileStore.getState().addFile(optimisticFile, {
311
+ prepend: true
312
+ });
313
+ const result = await uploadFileRaw(raw, targetUserId, oxyServices);
314
+ // Attempt to refresh file list incrementally – fetch single file metadata if API allows
315
+ if (result?.file || result?.files?.[0]) {
316
+ const f = result.file || result.files[0];
317
+ const merged = {
318
+ id: f.id,
319
+ filename: f.originalName || f.sha256 || raw.name,
320
+ contentType: f.mime || raw.type || 'application/octet-stream',
321
+ length: f.size || raw.size,
322
+ chunkSize: 0,
323
+ uploadDate: f.createdAt || new Date().toISOString(),
324
+ metadata: f.metadata || {},
325
+ variants: f.variants || []
326
+ };
327
+ // Remove optimistic then add real
328
+ _fileStore.useFileStore.getState().removeFile(optimisticId);
329
+ _fileStore.useFileStore.getState().addFile(merged, {
330
+ prepend: true
331
+ });
332
+ } else {
333
+ // Fallback: will reconcile on later list refresh
334
+ _fileStore.useFileStore.getState().updateFile(optimisticId, {
335
+ metadata: {
336
+ uploading: false
337
+ }
338
+ });
339
+ }
253
340
  successCount++;
254
341
  } catch (error) {
255
342
  failureCount++;
@@ -263,29 +350,51 @@ const FileManagementScreen = ({
263
350
  const errorMessage = `${failureCount} file(s) failed to upload${errors.length > 0 ? ':\n' + errors.slice(0, 3).join('\n') + (errors.length > 3 ? '\n...' : '') : ''}`;
264
351
  _sonner.toast.error(errorMessage);
265
352
  }
266
- setTimeout(async () => {
267
- await loadFiles();
268
- }, 500);
353
+ // Silent background refresh to ensure metadata/variants updated
354
+ setTimeout(() => {
355
+ loadFiles('silent');
356
+ }, 1200);
269
357
  } catch (error) {
270
358
  console.error('Upload error:', error);
271
359
  _sonner.toast.error(error.message || 'Failed to upload files');
272
360
  } finally {
273
- setUploadProgress(null);
361
+ storeSetUploadProgress(null);
274
362
  }
275
363
  };
276
364
  const handleFileUpload = async () => {
277
365
  try {
278
- setUploading(true);
279
- setUploadProgress(null);
366
+ uploadStartRef.current = Date.now();
367
+ storeSetUploading(true);
368
+ storeSetUploadProgress(null);
280
369
  if (_reactNative.Platform.OS === 'web') {
281
370
  // Web file picker implementation
282
371
  const input = document.createElement('input');
283
372
  input.type = 'file';
284
373
  input.multiple = true;
285
374
  input.accept = '*/*';
375
+ // Fallback: if the user cancels the dialog (no onchange fires or 0 files), hide banner
376
+ const cancellationTimer = setTimeout(() => {
377
+ const state = _fileStore.useFileStore.getState();
378
+ if (state.uploading && uploadStartRef.current && !state.uploadProgress) {
379
+ // No selection happened; treat as cancel
380
+ endUpload();
381
+ }
382
+ }, 1500); // allow enough time for user to pick
383
+
286
384
  input.onchange = async e => {
287
- const selectedFiles = Array.from(e.target.files);
385
+ clearTimeout(cancellationTimer);
386
+ const selectedFiles = Array.from(e.target.files || []);
387
+ if (selectedFiles.length === 0) {
388
+ // User explicitly canceled (some browsers still fire onchange with empty list)
389
+ endUpload();
390
+ return;
391
+ }
392
+ storeSetUploadProgress({
393
+ current: 0,
394
+ total: selectedFiles.length
395
+ });
288
396
  await processFileUploads(selectedFiles);
397
+ endUpload();
289
398
  };
290
399
  input.click();
291
400
  } else {
@@ -301,8 +410,11 @@ const FileManagementScreen = ({
301
410
  } catch (error) {
302
411
  _sonner.toast.error(error.message || 'Failed to upload file');
303
412
  } finally {
304
- setUploading(false);
305
- setUploadProgress(null);
413
+ // IMPORTANT: Do NOT call endUpload here.
414
+ // We only want to hide the banner after the actual upload(s) complete.
415
+ // The input.onchange handler invokes processFileUploads then calls endUpload().
416
+ // Calling endUpload here caused the banner to disappear while files were still uploading.
417
+ storeSetUploadProgress(null); // keep clearing any stale progress
306
418
  }
307
419
  };
308
420
  const handleFileDelete = async (fileId, filename) => {
@@ -319,15 +431,16 @@ const FileManagementScreen = ({
319
431
  });
320
432
  console.log('Target user ID:', targetUserId);
321
433
  console.log('Current user ID:', user?.id);
322
- setDeleting(fileId);
434
+ storeSetDeleting(fileId);
323
435
  const result = await oxyServices.deleteFile(fileId);
324
436
  console.log('Delete result:', result);
325
437
  _sonner.toast.success('File deleted successfully');
326
438
 
327
439
  // Reload files after successful deletion
328
- setTimeout(async () => {
329
- await loadFiles();
330
- }, 500);
440
+ // Optimistic remove
441
+ _fileStore.useFileStore.getState().removeFile(fileId);
442
+ // Silent background reconcile
443
+ setTimeout(() => loadFiles('silent'), 800);
331
444
  } catch (error) {
332
445
  console.error('Delete error:', error);
333
446
  console.error('Error details:', error.response?.data || error.message);
@@ -336,16 +449,14 @@ const FileManagementScreen = ({
336
449
  if (error.message?.includes('File not found') || error.message?.includes('404')) {
337
450
  _sonner.toast.error('File not found. It may have already been deleted.');
338
451
  // Still reload files to refresh the list
339
- setTimeout(async () => {
340
- await loadFiles();
341
- }, 500);
452
+ setTimeout(() => loadFiles('silent'), 800);
342
453
  } else if (error.message?.includes('permission') || error.message?.includes('403')) {
343
454
  _sonner.toast.error('You do not have permission to delete this file.');
344
455
  } else {
345
456
  _sonner.toast.error(error.message || 'Failed to delete file');
346
457
  }
347
458
  } finally {
348
- setDeleting(null);
459
+ storeSetDeleting(null);
349
460
  }
350
461
  };
351
462
 
@@ -356,24 +467,68 @@ const FileManagementScreen = ({
356
467
  setIsDragging(true);
357
468
  }
358
469
  };
470
+ const handleDragEnter = e => {
471
+ if (_reactNative.Platform.OS === 'web' && user?.id === targetUserId) {
472
+ e.preventDefault();
473
+ setIsDragging(true);
474
+ }
475
+ };
359
476
  const handleDragLeave = e => {
360
477
  if (_reactNative.Platform.OS === 'web') {
361
478
  e.preventDefault();
362
479
  setIsDragging(false);
363
480
  }
364
481
  };
482
+
483
+ // Global drag listeners (web) to catch drags outside component bounds
484
+ (0, _react.useEffect)(() => {
485
+ if (_reactNative.Platform.OS !== 'web' || user?.id !== targetUserId) return;
486
+ const onDocDragEnter = e => {
487
+ if (e?.dataTransfer?.types?.includes('Files')) setIsDragging(true);
488
+ };
489
+ const onDocDragOver = e => {
490
+ if (e?.dataTransfer?.types?.includes('Files')) {
491
+ e.preventDefault();
492
+ setIsDragging(true);
493
+ }
494
+ };
495
+ const onDocDrop = e => {
496
+ if (e?.dataTransfer?.files?.length) {
497
+ e.preventDefault();
498
+ setIsDragging(false);
499
+ }
500
+ };
501
+ const onDocDragLeave = e => {
502
+ if (!e.relatedTarget && e.screenX === 0 && e.screenY === 0) setIsDragging(false);
503
+ };
504
+ document.addEventListener('dragenter', onDocDragEnter);
505
+ document.addEventListener('dragover', onDocDragOver);
506
+ document.addEventListener('drop', onDocDrop);
507
+ document.addEventListener('dragleave', onDocDragLeave);
508
+ return () => {
509
+ document.removeEventListener('dragenter', onDocDragEnter);
510
+ document.removeEventListener('dragover', onDocDragOver);
511
+ document.removeEventListener('drop', onDocDrop);
512
+ document.removeEventListener('dragleave', onDocDragLeave);
513
+ };
514
+ }, [user?.id, targetUserId]);
365
515
  const handleDrop = async e => {
366
516
  if (_reactNative.Platform.OS === 'web' && user?.id === targetUserId) {
367
517
  e.preventDefault();
368
518
  setIsDragging(false);
369
- setUploading(true);
519
+ uploadStartRef.current = Date.now();
520
+ storeSetUploading(true);
370
521
  try {
371
522
  const files = Array.from(e.dataTransfer.files);
523
+ if (files.length > 0) storeSetUploadProgress({
524
+ current: 0,
525
+ total: files.length
526
+ });
372
527
  await processFileUploads(files);
373
528
  } catch (error) {
374
529
  _sonner.toast.error(error.message || 'Failed to upload files');
375
530
  } finally {
376
- setUploading(false);
531
+ endUpload();
377
532
  }
378
533
  }
379
534
  };
@@ -428,7 +583,7 @@ const FileManagementScreen = ({
428
583
  const k = 1024;
429
584
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
430
585
  const i = Math.floor(Math.log(bytes) / Math.log(k));
431
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
586
+ return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
432
587
  };
433
588
  const getFileIcon = contentType => {
434
589
  if (contentType.startsWith('image/')) return 'image';
@@ -488,7 +643,7 @@ const FileManagementScreen = ({
488
643
  setShowFileDetails(true);
489
644
  };
490
645
  const renderSimplePhotoItem = (0, _react.useCallback)((photo, index) => {
491
- const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
646
+ const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
492
647
 
493
648
  // Calculate photo item width based on actual container size from bottom sheet
494
649
  let itemsPerRow = 3; // Default for mobile
@@ -510,41 +665,24 @@ const FileManagementScreen = ({
510
665
  activeOpacity: 0.8,
511
666
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
512
667
  style: styles.simplePhotoContainer,
513
- children: _reactNative.Platform.OS === 'web' ? /*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
514
- src: downloadUrl,
515
- alt: photo.filename,
516
- style: {
517
- width: '100%',
518
- height: '100%',
519
- objectFit: 'cover',
520
- borderRadius: 8,
521
- transition: 'transform 0.2s ease'
522
- },
523
- loading: "lazy",
524
- onError: e => {
525
- console.error('Photo failed to load:', e);
526
- },
527
- onMouseEnter: e => {
528
- e.currentTarget.style.transform = 'scale(1.05)';
529
- },
530
- onMouseLeave: e => {
531
- e.currentTarget.style.transform = 'scale(1)';
532
- }
533
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
668
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_expoImage.Image, {
534
669
  source: {
535
670
  uri: downloadUrl
536
671
  },
537
672
  style: styles.simplePhotoImage,
538
- resizeMode: "cover",
673
+ contentFit: "cover",
674
+ transition: 120,
675
+ cachePolicy: "memory-disk",
539
676
  onError: e => {
540
- console.error('Photo failed to load:', e);
541
- }
677
+ console.error('Photo failed to load:', e?.nativeEvent ?? e);
678
+ },
679
+ accessibilityLabel: photo.filename
542
680
  })
543
681
  })
544
682
  }, photo.id);
545
683
  }, [oxyServices, containerWidth]);
546
684
  const renderJustifiedPhotoItem = (0, _react.useCallback)((photo, width, height, isLast) => {
547
- const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
685
+ const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
548
686
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
549
687
  style: [styles.justifiedPhotoItem, {
550
688
  width,
@@ -554,46 +692,33 @@ const FileManagementScreen = ({
554
692
  activeOpacity: 0.8,
555
693
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
556
694
  style: styles.justifiedPhotoContainer,
557
- children: _reactNative.Platform.OS === 'web' ? /*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
558
- src: downloadUrl,
559
- alt: photo.filename,
560
- style: {
561
- width: '100%',
562
- height: '100%',
563
- objectFit: 'cover',
564
- borderRadius: 6,
565
- transition: 'transform 0.2s ease, box-shadow 0.2s ease'
566
- },
567
- loading: "lazy",
568
- onError: e => {
569
- console.error('Photo failed to load:', e);
570
- },
571
- onMouseEnter: e => {
572
- e.currentTarget.style.transform = 'scale(1.02)';
573
- e.currentTarget.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)';
574
- e.currentTarget.style.zIndex = '10';
575
- },
576
- onMouseLeave: e => {
577
- e.currentTarget.style.transform = 'scale(1)';
578
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
579
- e.currentTarget.style.zIndex = '1';
580
- }
581
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
695
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_expoImage.Image, {
582
696
  source: {
583
697
  uri: downloadUrl
584
698
  },
585
699
  style: styles.justifiedPhotoImage,
586
- resizeMode: "cover",
700
+ contentFit: "cover",
701
+ transition: 120,
702
+ cachePolicy: "memory-disk",
587
703
  onError: e => {
588
- console.error('Photo failed to load:', e);
589
- }
704
+ console.error('Photo failed to load:', e?.nativeEvent ?? e);
705
+ },
706
+ accessibilityLabel: photo.filename
590
707
  })
591
708
  })
592
709
  }, photo.id);
593
710
  }, [oxyServices]);
711
+
712
+ // Run initial load once per targetUserId change to avoid accidental loops
713
+ const lastLoadedFor = (0, _react.useRef)(undefined);
594
714
  (0, _react.useEffect)(() => {
595
- loadFiles();
596
- }, [loadFiles]);
715
+ const key = targetUserId || 'anonymous';
716
+ if (lastLoadedFor.current !== key) {
717
+ lastLoadedFor.current = key;
718
+ loadFiles('initial');
719
+ }
720
+ // eslint-disable-next-line react-hooks/exhaustive-deps
721
+ }, [targetUserId]);
597
722
  const renderFileItem = file => {
598
723
  const isImage = file.contentType.startsWith('image/');
599
724
  const isPDF = file.contentType.includes('pdf');
@@ -617,35 +742,19 @@ const FileManagementScreen = ({
617
742
  onMouseEnter: () => setHoveredPreview(file.id),
618
743
  onMouseLeave: () => setHoveredPreview(null)
619
744
  }),
620
- children: [isImage && (_reactNative.Platform.OS === 'web' ? /*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
621
- src: oxyServices.getFileDownloadUrl(file.id),
622
- style: {
623
- width: '100%',
624
- height: '100%',
625
- objectFit: 'cover',
626
- borderRadius: 8,
627
- transition: 'transform 0.2s ease',
628
- transform: hoveredPreview === file.id ? 'scale(1.05)' : 'scale(1)'
629
- },
630
- onError: e => {
631
- // Show fallback icon if image fails to load
632
- e.currentTarget.style.display = 'none';
633
- const fallbackElement = e.currentTarget.parentElement?.querySelector('[data-fallback="true"]');
634
- if (fallbackElement) {
635
- fallbackElement.style.display = 'flex';
636
- }
637
- }
638
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
745
+ children: [isImage && /*#__PURE__*/(0, _jsxRuntime.jsx)(_expoImage.Image, {
639
746
  source: {
640
- uri: oxyServices.getFileDownloadUrl(file.id)
747
+ uri: getSafeDownloadUrl(file, 'thumb')
641
748
  },
642
749
  style: styles.previewImage,
643
- resizeMode: "cover",
644
- onError: () => {
645
- // For React Native, you might want to set an error state
646
- console.warn('Failed to load image preview for file:', file.id);
647
- }
648
- })), isPDF && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
750
+ contentFit: "cover",
751
+ transition: 120,
752
+ cachePolicy: "memory-disk",
753
+ onError: _ => {
754
+ console.warn('Failed to load image preview.');
755
+ },
756
+ accessibilityLabel: file.filename
757
+ }), isPDF && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
649
758
  style: styles.pdfPreview,
650
759
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
651
760
  name: "document",
@@ -658,16 +767,26 @@ const FileManagementScreen = ({
658
767
  children: "PDF"
659
768
  })]
660
769
  }), isVideo && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
661
- style: styles.videoPreview,
662
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
663
- name: "play-circle",
664
- size: 32,
665
- color: themeStyles.primaryColor
666
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
667
- style: [styles.videoLabel, {
668
- color: themeStyles.primaryColor
669
- }],
670
- children: "VIDEO"
770
+ style: styles.videoPreviewWrapper,
771
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_expoImage.Image, {
772
+ source: {
773
+ uri: getSafeDownloadUrl(file, 'thumb')
774
+ },
775
+ style: styles.videoPosterImage,
776
+ contentFit: "cover",
777
+ transition: 120,
778
+ cachePolicy: "memory-disk",
779
+ onError: _ => {
780
+ // If thumbnail not available, we still show icon overlay
781
+ },
782
+ accessibilityLabel: file.filename + ' video thumbnail'
783
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
784
+ style: styles.videoOverlay,
785
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
786
+ name: "play",
787
+ size: 24,
788
+ color: "#FFFFFF"
789
+ })
671
790
  })]
672
791
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
673
792
  style: [styles.fallbackIcon, {
@@ -760,8 +879,78 @@ const FileManagementScreen = ({
760
879
  })]
761
880
  }, file.id);
762
881
  };
882
+
883
+ // GroupedSection-based file items (for 'all' view) replacing legacy flat list look
884
+ const groupedFileItems = (0, _react.useMemo)(() => {
885
+ return filteredFiles.filter(f => true) // placeholder for future filtering
886
+ .sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime()).map(file => {
887
+ const isImage = file.contentType.startsWith('image/');
888
+ const isVideo = file.contentType.startsWith('video/');
889
+ const hasPreview = isImage || isVideo;
890
+ const previewUrl = hasPreview ? isVideo ? getSafeDownloadUrl(file, 'poster') : getSafeDownloadUrl(file, 'thumb') : undefined;
891
+ return {
892
+ id: file.id,
893
+ image: previewUrl,
894
+ imageSize: 44,
895
+ icon: !previewUrl ? getFileIcon(file.contentType) : undefined,
896
+ iconColor: themeStyles.primaryColor,
897
+ title: file.filename,
898
+ subtitle: `${formatFileSize(file.length)} • ${new Date(file.uploadDate).toLocaleDateString()}`,
899
+ theme: theme,
900
+ onPress: () => handleFileOpen(file),
901
+ showChevron: false,
902
+ dense: true,
903
+ multiRow: !!file.metadata?.description,
904
+ customContent: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
905
+ style: styles.groupedActions,
906
+ children: [(isImage || isVideo || file.contentType.includes('pdf')) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
907
+ style: [styles.groupedActionBtn, {
908
+ backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0'
909
+ }],
910
+ onPress: () => handleFileOpen(file),
911
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
912
+ name: "eye",
913
+ size: 18,
914
+ color: themeStyles.primaryColor
915
+ })
916
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
917
+ style: [styles.groupedActionBtn, {
918
+ backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0'
919
+ }],
920
+ onPress: () => handleFileDownload(file.id, file.filename),
921
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
922
+ name: "download",
923
+ size: 18,
924
+ color: themeStyles.primaryColor
925
+ })
926
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
927
+ style: [styles.groupedActionBtn, {
928
+ backgroundColor: themeStyles.isDarkTheme ? '#400000' : '#FFEBEE'
929
+ }],
930
+ onPress: () => handleFileDelete(file.id, file.filename),
931
+ disabled: deleting === file.id,
932
+ children: deleting === file.id ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
933
+ size: "small",
934
+ color: themeStyles.dangerColor
935
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
936
+ name: "trash",
937
+ size: 18,
938
+ color: themeStyles.dangerColor
939
+ })
940
+ })]
941
+ }),
942
+ customContentBelow: file.metadata?.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
943
+ style: [styles.groupedDescription, {
944
+ color: themeStyles.isDarkTheme ? '#AAAAAA' : '#666666'
945
+ }],
946
+ numberOfLines: 2,
947
+ children: file.metadata.description
948
+ }) : undefined
949
+ }; // GroupedSectionItem shape
950
+ });
951
+ }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl]);
763
952
  const renderPhotoItem = (photo, index) => {
764
- const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
953
+ const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
765
954
 
766
955
  // Calculate photo item width based on actual container size from bottom sheet
767
956
  let itemsPerRow = 3; // Default for mobile
@@ -782,36 +971,18 @@ const FileManagementScreen = ({
782
971
  activeOpacity: 0.8,
783
972
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
784
973
  style: styles.photoContainer,
785
- children: _reactNative.Platform.OS === 'web' ? /*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
786
- src: downloadUrl,
787
- alt: photo.filename,
788
- style: {
789
- width: '100%',
790
- height: '100%',
791
- objectFit: 'cover',
792
- borderRadius: 8,
793
- transition: 'transform 0.2s ease'
794
- },
795
- loading: "lazy",
796
- onError: e => {
797
- console.error('Photo failed to load:', e);
798
- // Could replace with placeholder image
799
- },
800
- onMouseEnter: e => {
801
- e.currentTarget.style.transform = 'scale(1.02)';
802
- },
803
- onMouseLeave: e => {
804
- e.currentTarget.style.transform = 'scale(1)';
805
- }
806
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
974
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_expoImage.Image, {
807
975
  source: {
808
976
  uri: downloadUrl
809
977
  },
810
978
  style: styles.photoImage,
811
- resizeMode: "cover",
812
- onError: e => {
813
- console.error('Photo failed to load:', e);
814
- }
979
+ contentFit: "cover",
980
+ transition: 120,
981
+ cachePolicy: "memory-disk",
982
+ onError: _ => {
983
+ console.warn('Failed to load image preview for photo:', photo.id);
984
+ },
985
+ accessibilityLabel: photo.filename
815
986
  })
816
987
  })
817
988
  }, photo.id);
@@ -830,11 +1001,11 @@ const FileManagementScreen = ({
830
1001
  color: themeStyles.textColor
831
1002
  }],
832
1003
  children: "No Photos Yet"
833
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1004
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
834
1005
  style: [styles.emptyStateDescription, {
835
1006
  color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'
836
1007
  }],
837
- children: user?.id === targetUserId ? `Upload photos to get started. You can select multiple photos at once${_reactNative.Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}` : "This user hasn't uploaded any photos yet"
1008
+ children: [" ", user?.id === targetUserId ? `Upload photos to get started. You can select multiple photos at once${_reactNative.Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}` : "This user hasn't uploaded any photos yet", " "]
838
1009
  }), user?.id === targetUserId && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
839
1010
  style: [styles.emptyStateButton, {
840
1011
  backgroundColor: themeStyles.primaryColor
@@ -862,7 +1033,7 @@ const FileManagementScreen = ({
862
1033
  contentContainerStyle: styles.photoScrollContainer,
863
1034
  refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, {
864
1035
  refreshing: refreshing,
865
- onRefresh: () => loadFiles(true),
1036
+ onRefresh: () => loadFiles('refresh'),
866
1037
  tintColor: themeStyles.primaryColor
867
1038
  }),
868
1039
  showsVerticalScrollIndicator: false,
@@ -904,7 +1075,9 @@ const FileManagementScreen = ({
904
1075
  // Load dimensions for new photos
905
1076
  _react.default.useEffect(() => {
906
1077
  loadPhotoDimensions(photos);
907
- }, [photos.map(p => p.id).join(','), loadPhotoDimensions]);
1078
+ // Depend only on photo IDs to avoid re-running from dimension state changes
1079
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1080
+ }, [photos.map(p => p.id).join(',')]);
908
1081
 
909
1082
  // Group photos by date
910
1083
  const photosByDate = _react.default.useMemo(() => {
@@ -1343,31 +1516,22 @@ const FileManagementScreen = ({
1343
1516
  })]
1344
1517
  }) : isImage && fileContent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1345
1518
  style: styles.imageContainer,
1346
- children: _reactNative.Platform.OS === 'web' ? /*#__PURE__*/(0, _jsxRuntime.jsx)("img", {
1347
- src: fileContent,
1348
- alt: openedFile.filename,
1349
- style: {
1350
- maxWidth: '100%',
1351
- maxHeight: '80vh',
1352
- objectFit: 'contain',
1353
- borderRadius: 8
1354
- },
1355
- onError: e => {
1356
- console.error('Image failed to load:', e);
1357
- }
1358
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
1519
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_expoImage.Image, {
1359
1520
  source: {
1360
1521
  uri: fileContent
1361
1522
  },
1362
1523
  style: {
1363
1524
  width: '100%',
1364
1525
  height: 400,
1365
- resizeMode: 'contain',
1366
1526
  borderRadius: 8
1367
1527
  },
1528
+ contentFit: "contain",
1529
+ transition: 120,
1530
+ cachePolicy: "memory-disk",
1368
1531
  onError: e => {
1369
- console.error('Image failed to load:', e);
1370
- }
1532
+ console.error('Image failed to load:', e?.nativeEvent ?? e);
1533
+ },
1534
+ accessibilityLabel: openedFile.filename
1371
1535
  })
1372
1536
  }) : isText && fileContent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1373
1537
  style: [styles.textContainer, {
@@ -1530,157 +1694,76 @@ const FileManagementScreen = ({
1530
1694
  });
1531
1695
  }
1532
1696
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1533
- style: [styles.container, {
1534
- backgroundColor
1535
- }, isDragging && _reactNative.Platform.OS === 'web' && styles.dragOverlay],
1697
+ style: [styles.container, isDragging && _reactNative.Platform.OS === 'web' && styles.dragOverlay],
1536
1698
  ...(_reactNative.Platform.OS === 'web' && user?.id === targetUserId ? {
1537
1699
  onDragOver: handleDragOver,
1700
+ onDragEnter: handleDragEnter,
1538
1701
  onDragLeave: handleDragLeave,
1539
1702
  onDrop: handleDrop
1540
1703
  } : {}),
1541
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1542
- style: [styles.header, {
1543
- borderBottomColor: borderColor,
1544
- backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1545
- shadowColor: '#000000',
1546
- shadowOffset: {
1547
- width: 0,
1548
- height: 2
1549
- },
1550
- shadowOpacity: themeStyles.isDarkTheme ? 0.3 : 0.1,
1551
- shadowRadius: 8,
1552
- elevation: 4,
1553
- ..._reactNative.Platform.select({
1554
- web: {
1555
- boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
1556
- },
1557
- default: {}
1558
- })
1559
- }],
1560
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1561
- style: [styles.backButton, {
1562
- backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
1563
- borderRadius: 12
1704
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Header.default, {
1705
+ title: viewMode === 'photos' ? 'Photos' : 'File Management',
1706
+ subtitle: `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`,
1707
+ onBack: onClose || goBack,
1708
+ theme: theme,
1709
+ showBackButton: true,
1710
+ variant: "minimal",
1711
+ elevation: "none",
1712
+ titleAlignment: "left"
1713
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1714
+ style: styles.controlsBar,
1715
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1716
+ style: [styles.viewModeToggle, {
1717
+ backgroundColor: themeStyles.isDarkTheme ? '#181818' : '#FFFFFF',
1718
+ borderWidth: 1,
1719
+ borderColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#E8E9EA'
1564
1720
  }],
1565
- onPress: onClose || goBack,
1566
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1567
- name: "arrow-back",
1568
- size: 22,
1569
- color: themeStyles.textColor
1570
- })
1571
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1572
- style: styles.headerTitleContainer,
1573
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1574
- style: [styles.headerTitle, {
1575
- color: themeStyles.textColor
1576
- }],
1577
- children: viewMode === 'photos' ? 'Photos' : 'File Management'
1578
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1579
- style: [styles.headerSubtitle, {
1580
- color: themeStyles.isDarkTheme ? '#AAAAAA' : '#666666'
1581
- }],
1582
- children: [filteredFiles.length, " ", filteredFiles.length === 1 ? 'item' : 'items']
1583
- })]
1584
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1585
- style: styles.headerActions,
1586
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1587
- style: [styles.viewModeToggle, {
1588
- backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
1589
- borderWidth: 1,
1590
- borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1591
- shadowColor: '#000000',
1592
- shadowOffset: {
1593
- width: 0,
1594
- height: 1
1595
- },
1596
- shadowOpacity: themeStyles.isDarkTheme ? 0.3 : 0.05,
1597
- shadowRadius: 4,
1598
- elevation: 2
1721
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1722
+ style: [styles.viewModeButton, viewMode === 'all' && {
1723
+ backgroundColor: themeStyles.primaryColor
1599
1724
  }],
1600
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1601
- style: [styles.viewModeButton, viewMode === 'all' && {
1602
- backgroundColor: themeStyles.primaryColor,
1603
- shadowColor: themeStyles.primaryColor,
1604
- shadowOffset: {
1605
- width: 0,
1606
- height: 2
1607
- },
1608
- shadowOpacity: 0.3,
1609
- shadowRadius: 4,
1610
- elevation: 3
1611
- }],
1612
- onPress: () => setViewMode('all'),
1613
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1614
- name: "folder",
1615
- size: 18,
1616
- color: viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor
1617
- })
1618
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1619
- style: [styles.viewModeButton, viewMode === 'photos' && {
1620
- backgroundColor: themeStyles.primaryColor,
1621
- shadowColor: themeStyles.primaryColor,
1622
- shadowOffset: {
1623
- width: 0,
1624
- height: 2
1625
- },
1626
- shadowOpacity: 0.3,
1627
- shadowRadius: 4,
1628
- elevation: 3
1629
- }],
1630
- onPress: () => setViewMode('photos'),
1631
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1632
- name: "images",
1633
- size: 18,
1634
- color: viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor
1635
- })
1636
- })]
1637
- }), user?.id === targetUserId && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1638
- style: [styles.uploadButton, {
1639
- backgroundColor: themeStyles.primaryColor,
1640
- shadowColor: themeStyles.primaryColor,
1641
- shadowOffset: {
1642
- width: 0,
1643
- height: 3
1644
- },
1645
- shadowOpacity: 0.4,
1646
- shadowRadius: 6,
1647
- elevation: 5,
1648
- transform: uploading ? [{
1649
- scale: 0.95
1650
- }] : [{
1651
- scale: 1
1652
- }]
1725
+ onPress: () => setViewMode('all'),
1726
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1727
+ name: "folder",
1728
+ size: 18,
1729
+ color: viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor
1730
+ })
1731
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1732
+ style: [styles.viewModeButton, viewMode === 'photos' && {
1733
+ backgroundColor: themeStyles.primaryColor
1653
1734
  }],
1654
- onPress: handleFileUpload,
1655
- disabled: uploading,
1656
- children: uploading ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1657
- style: styles.uploadProgress,
1658
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
1659
- size: "small",
1660
- color: "#FFFFFF"
1661
- }), uploadProgress && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1662
- style: styles.uploadProgressText,
1663
- children: [uploadProgress.current, "/", uploadProgress.total]
1664
- })]
1665
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1666
- name: "add",
1667
- size: 26,
1668
- color: "#FFFFFF"
1735
+ onPress: () => setViewMode('photos'),
1736
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1737
+ name: "images",
1738
+ size: 18,
1739
+ color: viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor
1669
1740
  })
1670
1741
  })]
1742
+ }), user?.id === targetUserId && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1743
+ style: [styles.uploadButton, {
1744
+ backgroundColor: themeStyles.primaryColor
1745
+ }],
1746
+ onPress: handleFileUpload,
1747
+ disabled: uploading,
1748
+ children: uploading ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1749
+ style: styles.uploadProgress,
1750
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
1751
+ size: "small",
1752
+ color: "#FFFFFF"
1753
+ }), uploadProgress && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1754
+ style: styles.uploadProgressText,
1755
+ children: [uploadProgress.current, "/", uploadProgress.total]
1756
+ })]
1757
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1758
+ name: "add",
1759
+ size: 22,
1760
+ color: "#FFFFFF"
1761
+ })
1671
1762
  })]
1672
1763
  }), files.length > 0 && (viewMode === 'all' || files.some(f => f.contentType.startsWith('image/'))) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1673
1764
  style: [styles.searchContainer, {
1674
1765
  backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1675
- borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1676
- shadowColor: '#000000',
1677
- shadowOffset: {
1678
- width: 0,
1679
- height: 1
1680
- },
1681
- shadowOpacity: themeStyles.isDarkTheme ? 0.2 : 0.05,
1682
- shadowRadius: 4,
1683
- elevation: 2
1766
+ borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA'
1684
1767
  }],
1685
1768
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1686
1769
  name: "search",
@@ -1706,15 +1789,7 @@ const FileManagementScreen = ({
1706
1789
  }), files.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1707
1790
  style: [styles.statsContainer, {
1708
1791
  backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1709
- borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1710
- shadowColor: '#000000',
1711
- shadowOffset: {
1712
- width: 0,
1713
- height: 1
1714
- },
1715
- shadowOpacity: themeStyles.isDarkTheme ? 0.2 : 0.05,
1716
- shadowRadius: 4,
1717
- elevation: 2
1792
+ borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA'
1718
1793
  }],
1719
1794
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1720
1795
  style: styles.statItem,
@@ -1761,7 +1836,7 @@ const FileManagementScreen = ({
1761
1836
  contentContainerStyle: styles.scrollContainer,
1762
1837
  refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, {
1763
1838
  refreshing: refreshing,
1764
- onRefresh: () => loadFiles(true),
1839
+ onRefresh: () => loadFiles('refresh'),
1765
1840
  tintColor: themeStyles.primaryColor
1766
1841
  }),
1767
1842
  children: filteredFiles.length === 0 && searchQuery.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -1794,10 +1869,37 @@ const FileManagementScreen = ({
1794
1869
  children: "Clear Search"
1795
1870
  })]
1796
1871
  })]
1797
- }) : filteredFiles.length === 0 ? renderEmptyState() : /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
1798
- children: filteredFiles.map(renderFileItem)
1872
+ }) : filteredFiles.length === 0 ? renderEmptyState() : /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.GroupedSection, {
1873
+ items: groupedFileItems,
1874
+ theme: theme
1875
+ })
1876
+ }), renderFileDetailsModal(), uploading && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1877
+ pointerEvents: "none",
1878
+ style: styles.uploadBannerContainer,
1879
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1880
+ style: [styles.uploadBanner, {
1881
+ backgroundColor: themeStyles.isDarkTheme ? '#222831EE' : '#FFFFFFEE',
1882
+ borderColor: themeStyles.borderColor
1883
+ }],
1884
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1885
+ name: "cloud-upload",
1886
+ size: 18,
1887
+ color: themeStyles.primaryColor
1888
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1889
+ style: [styles.uploadBannerText, {
1890
+ color: themeStyles.textColor
1891
+ }],
1892
+ children: ["Uploading", uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...']
1893
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1894
+ style: styles.uploadBannerDots,
1895
+ children: [0, 1, 2].map(i => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1896
+ style: [styles.dot, {
1897
+ opacity: (Date.now() / 400 + i) % 3 < 1 ? 1 : 0.25
1898
+ }]
1899
+ }, i))
1900
+ })]
1799
1901
  })
1800
- }), renderFileDetailsModal(), isDragging && _reactNative.Platform.OS === 'web' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1902
+ }), isDragging && _reactNative.Platform.OS === 'web' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1801
1903
  style: styles.dragDropOverlay,
1802
1904
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1803
1905
  style: styles.dragDropContent,
@@ -1825,9 +1927,9 @@ const styles = _reactNative.StyleSheet.create({
1825
1927
  flex: 1
1826
1928
  },
1827
1929
  dragOverlay: {
1828
- backgroundColor: 'rgba(0, 122, 255, 0.1)',
1829
- borderWidth: 2,
1830
- borderColor: '#007AFF',
1930
+ backgroundColor: 'rgba(0, 122, 255, 0.06)',
1931
+ borderWidth: 1,
1932
+ borderColor: '#66AFFF',
1831
1933
  borderStyle: 'dashed'
1832
1934
  },
1833
1935
  centerContent: {
@@ -1838,8 +1940,8 @@ const styles = _reactNative.StyleSheet.create({
1838
1940
  flexDirection: 'row',
1839
1941
  alignItems: 'center',
1840
1942
  justifyContent: 'space-between',
1841
- paddingHorizontal: 24,
1842
- paddingVertical: 20,
1943
+ paddingHorizontal: 16,
1944
+ paddingVertical: 12,
1843
1945
  borderBottomWidth: 1,
1844
1946
  position: 'relative'
1845
1947
  },
@@ -1888,16 +1990,57 @@ const styles = _reactNative.StyleSheet.create({
1888
1990
  fontWeight: '600',
1889
1991
  marginTop: 2
1890
1992
  },
1993
+ uploadBannerContainer: {
1994
+ position: 'absolute',
1995
+ top: 72,
1996
+ // below header
1997
+ left: 0,
1998
+ right: 0,
1999
+ alignItems: 'center',
2000
+ zIndex: 50
2001
+ },
2002
+ uploadBanner: {
2003
+ flexDirection: 'row',
2004
+ alignItems: 'center',
2005
+ paddingHorizontal: 14,
2006
+ paddingVertical: 8,
2007
+ borderRadius: 24,
2008
+ gap: 8,
2009
+ borderWidth: 1,
2010
+ shadowColor: '#000',
2011
+ shadowOpacity: 0.1,
2012
+ shadowRadius: 6,
2013
+ shadowOffset: {
2014
+ width: 0,
2015
+ height: 2
2016
+ },
2017
+ elevation: 2
2018
+ },
2019
+ uploadBannerText: {
2020
+ fontSize: 13,
2021
+ fontWeight: '500'
2022
+ },
2023
+ uploadBannerDots: {
2024
+ flexDirection: 'row',
2025
+ gap: 4,
2026
+ marginLeft: 2
2027
+ },
2028
+ dot: {
2029
+ width: 6,
2030
+ height: 6,
2031
+ borderRadius: 3,
2032
+ backgroundColor: '#007AFF'
2033
+ },
1891
2034
  searchContainer: {
1892
2035
  flexDirection: 'row',
1893
2036
  alignItems: 'center',
1894
- paddingHorizontal: 20,
1895
- paddingVertical: 16,
1896
- marginHorizontal: 24,
1897
- marginTop: 20,
1898
- borderRadius: 16,
2037
+ paddingHorizontal: 14,
2038
+ paddingVertical: 10,
2039
+ marginHorizontal: 16,
2040
+ marginTop: 12,
2041
+ borderRadius: 10,
1899
2042
  borderWidth: 1,
1900
- gap: 16
2043
+ gap: 10
1901
2044
  },
1902
2045
  searchInput: {
1903
2046
  flex: 1,
@@ -1916,11 +2059,11 @@ const styles = _reactNative.StyleSheet.create({
1916
2059
  },
1917
2060
  statsContainer: {
1918
2061
  flexDirection: 'row',
1919
- paddingHorizontal: 24,
1920
- paddingVertical: 20,
1921
- marginHorizontal: 24,
1922
- marginTop: 20,
1923
- borderRadius: 16,
2062
+ paddingHorizontal: 14,
2063
+ paddingVertical: 10,
2064
+ marginHorizontal: 16,
2065
+ marginTop: 12,
2066
+ borderRadius: 10,
1924
2067
  borderWidth: 1
1925
2068
  },
1926
2069
  statItem: {
@@ -1929,31 +2072,31 @@ const styles = _reactNative.StyleSheet.create({
1929
2072
  paddingVertical: 4
1930
2073
  },
1931
2074
  statValue: {
1932
- fontSize: 28,
2075
+ fontSize: 20,
1933
2076
  fontWeight: '800',
1934
2077
  fontFamily: _fonts.fontFamilies.phuduBold,
1935
2078
  letterSpacing: -0.5,
1936
- lineHeight: 32
2079
+ lineHeight: 24
1937
2080
  },
1938
2081
  statLabel: {
1939
- fontSize: 15,
2082
+ fontSize: 12,
1940
2083
  fontWeight: '500',
1941
2084
  fontFamily: _fonts.fontFamilies.phuduMedium,
1942
- marginTop: 4,
1943
- letterSpacing: 0.3
2085
+ marginTop: 2,
2086
+ letterSpacing: 0.2
1944
2087
  },
1945
2088
  scrollView: {
1946
2089
  flex: 1
1947
2090
  },
1948
2091
  scrollContainer: {
1949
- padding: 20
2092
+ padding: 12
1950
2093
  },
1951
2094
  fileItem: {
1952
2095
  flexDirection: 'row',
1953
2096
  alignItems: 'center',
1954
- padding: 16,
1955
- marginBottom: 12,
1956
- borderRadius: 12,
2097
+ padding: 10,
2098
+ marginBottom: 8,
2099
+ borderRadius: 10,
1957
2100
  borderWidth: 1
1958
2101
  },
1959
2102
  fileContent: {
@@ -1969,15 +2112,15 @@ const styles = _reactNative.StyleSheet.create({
1969
2112
  marginRight: 12
1970
2113
  },
1971
2114
  filePreviewContainer: {
1972
- width: 60,
1973
- height: 60,
1974
- marginRight: 12
2115
+ width: 52,
2116
+ height: 52,
2117
+ marginRight: 10
1975
2118
  },
1976
2119
  filePreview: {
1977
2120
  width: '100%',
1978
2121
  height: '100%',
1979
2122
  borderRadius: 8,
1980
- backgroundColor: '#F5F5F5',
2123
+ backgroundColor: 'transparent',
1981
2124
  alignItems: 'center',
1982
2125
  justifyContent: 'center',
1983
2126
  overflow: 'hidden',
@@ -2020,7 +2163,7 @@ const styles = _reactNative.StyleSheet.create({
2020
2163
  bottom: 0,
2021
2164
  alignItems: 'center',
2022
2165
  justifyContent: 'center',
2023
- backgroundColor: '#F5F5F5',
2166
+ backgroundColor: 'transparent',
2024
2167
  borderRadius: 8
2025
2168
  },
2026
2169
  previewOverlay: {
@@ -2029,11 +2172,57 @@ const styles = _reactNative.StyleSheet.create({
2029
2172
  left: 0,
2030
2173
  right: 0,
2031
2174
  bottom: 0,
2032
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
2175
+ backgroundColor: 'rgba(0, 0, 0, 0.25)',
2033
2176
  alignItems: 'center',
2034
2177
  justifyContent: 'center',
2035
2178
  borderRadius: 8
2036
2179
  },
2180
+ groupedActions: {
2181
+ flexDirection: 'row',
2182
+ alignItems: 'center',
2183
+ gap: 6,
2184
+ marginLeft: 12
2185
+ },
2186
+ groupedActionBtn: {
2187
+ width: 34,
2188
+ height: 34,
2189
+ borderRadius: 8,
2190
+ alignItems: 'center',
2191
+ justifyContent: 'center'
2192
+ },
2193
+ groupedDescription: {
2194
+ fontSize: 12,
2195
+ lineHeight: 16,
2196
+ marginTop: 6
2197
+ },
2198
+ videoPreviewWrapper: {
2199
+ width: '100%',
2200
+ height: '100%',
2201
+ borderRadius: 8,
2202
+ overflow: 'hidden',
2203
+ backgroundColor: '#000000',
2204
+ alignItems: 'center',
2205
+ justifyContent: 'center'
2206
+ },
2207
+ videoPosterImage: {
2208
+ position: 'absolute',
2209
+ top: 0,
2210
+ left: 0,
2211
+ right: 0,
2212
+ bottom: 0,
2213
+ width: '100%',
2214
+ height: '100%'
2215
+ },
2216
+ videoOverlay: {
2217
+ position: 'absolute',
2218
+ top: 0,
2219
+ left: 0,
2220
+ right: 0,
2221
+ bottom: 0,
2222
+ alignItems: 'center',
2223
+ justifyContent: 'center',
2224
+ backgroundColor: 'rgba(0,0,0,0.25)'
2225
+ },
2037
2226
  fileInfo: {
2038
2227
  flex: 1
2039
2228
  },
@@ -2055,16 +2244,19 @@ const styles = _reactNative.StyleSheet.create({
2055
2244
  gap: 8
2056
2245
  },
2057
2246
  actionButton: {
2058
- width: 40,
2059
- height: 40,
2060
- borderRadius: 20,
2247
+ width: 36,
2248
+ height: 36,
2249
+ borderRadius: 18,
2061
2250
  alignItems: 'center',
2062
- justifyContent: 'center'
2251
+ justifyContent: 'center',
2252
+ borderWidth: 1,
2253
+ borderColor: '#E0E0E0',
2254
+ backgroundColor: 'transparent'
2063
2255
  },
2064
2256
  emptyState: {
2065
2257
  alignItems: 'center',
2066
- paddingVertical: 60,
2067
- paddingHorizontal: 40
2258
+ paddingVertical: 40,
2259
+ paddingHorizontal: 24
2068
2260
  },
2069
2261
  emptyStateTitle: {
2070
2262
  fontSize: 24,
@@ -2104,8 +2296,8 @@ const styles = _reactNative.StyleSheet.create({
2104
2296
  flexDirection: 'row',
2105
2297
  alignItems: 'center',
2106
2298
  justifyContent: 'space-between',
2107
- paddingHorizontal: 20,
2108
- paddingVertical: 16,
2299
+ paddingHorizontal: 16,
2300
+ paddingVertical: 12,
2109
2301
  borderBottomWidth: 1
2110
2302
  },
2111
2303
  modalCloseButton: {
@@ -2121,11 +2313,11 @@ const styles = _reactNative.StyleSheet.create({
2121
2313
  },
2122
2314
  modalContent: {
2123
2315
  flex: 1,
2124
- padding: 20
2316
+ padding: 16
2125
2317
  },
2126
2318
  fileDetailCard: {
2127
- padding: 24,
2128
- borderRadius: 16,
2319
+ padding: 18,
2320
+ borderRadius: 14,
2129
2321
  borderWidth: 1,
2130
2322
  alignItems: 'center'
2131
2323
  },
@@ -2187,25 +2379,25 @@ const styles = _reactNative.StyleSheet.create({
2187
2379
  left: 0,
2188
2380
  right: 0,
2189
2381
  bottom: 0,
2190
- backgroundColor: 'rgba(0, 0, 0, 0.8)',
2382
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
2191
2383
  justifyContent: 'center',
2192
2384
  alignItems: 'center',
2193
2385
  zIndex: 1000
2194
2386
  },
2195
2387
  dragDropContent: {
2196
2388
  alignItems: 'center',
2197
- backgroundColor: 'rgba(255, 255, 255, 0.9)',
2198
- padding: 40,
2199
- borderRadius: 20,
2200
- borderWidth: 3,
2201
- borderColor: '#007AFF',
2389
+ backgroundColor: 'rgba(255, 255, 255, 0.95)',
2390
+ padding: 20,
2391
+ borderRadius: 14,
2392
+ borderWidth: 1,
2393
+ borderColor: '#66AFFF',
2202
2394
  borderStyle: 'dashed'
2203
2395
  },
2204
2396
  dragDropTitle: {
2205
- fontSize: 24,
2397
+ fontSize: 20,
2206
2398
  fontWeight: 'bold',
2207
- marginTop: 16,
2208
- marginBottom: 8
2399
+ marginTop: 12,
2400
+ marginBottom: 6
2209
2401
  },
2210
2402
  dragDropSubtitle: {
2211
2403
  fontSize: 16,
@@ -2218,8 +2410,8 @@ const styles = _reactNative.StyleSheet.create({
2218
2410
  fileViewerHeader: {
2219
2411
  flexDirection: 'row',
2220
2412
  alignItems: 'center',
2221
- paddingHorizontal: 20,
2222
- paddingVertical: 16,
2413
+ paddingHorizontal: 16,
2414
+ paddingVertical: 12,
2223
2415
  borderBottomWidth: 1
2224
2416
  },
2225
2417
  fileViewerTitleContainer: {
@@ -2247,7 +2439,7 @@ const styles = _reactNative.StyleSheet.create({
2247
2439
  },
2248
2440
  fileViewerContentContainer: {
2249
2441
  flexGrow: 1,
2250
- padding: 20
2442
+ padding: 14
2251
2443
  },
2252
2444
  fileViewerLoading: {
2253
2445
  flex: 1,
@@ -2265,10 +2457,10 @@ const styles = _reactNative.StyleSheet.create({
2265
2457
  },
2266
2458
  textContainer: {
2267
2459
  flex: 1,
2268
- borderRadius: 12,
2460
+ borderRadius: 10,
2269
2461
  borderWidth: 1,
2270
- padding: 16,
2271
- minHeight: 200,
2462
+ padding: 12,
2463
+ minHeight: 180,
2272
2464
  maxHeight: '80%'
2273
2465
  },
2274
2466
  textContent: {
@@ -2280,8 +2472,8 @@ const styles = _reactNative.StyleSheet.create({
2280
2472
  flex: 1,
2281
2473
  justifyContent: 'center',
2282
2474
  alignItems: 'center',
2283
- paddingVertical: 60,
2284
- paddingHorizontal: 40
2475
+ paddingVertical: 40,
2476
+ paddingHorizontal: 24
2285
2477
  },
2286
2478
  unsupportedFileTitle: {
2287
2479
  fontSize: 24,
@@ -2300,9 +2492,9 @@ const styles = _reactNative.StyleSheet.create({
2300
2492
  downloadButtonLarge: {
2301
2493
  flexDirection: 'row',
2302
2494
  alignItems: 'center',
2303
- paddingHorizontal: 24,
2304
- paddingVertical: 16,
2305
- borderRadius: 24,
2495
+ paddingHorizontal: 18,
2496
+ paddingVertical: 12,
2497
+ borderRadius: 20,
2306
2498
  gap: 8
2307
2499
  },
2308
2500
  downloadButtonText: {
@@ -2328,10 +2520,10 @@ const styles = _reactNative.StyleSheet.create({
2328
2520
  },
2329
2521
  // File Details in Viewer styles
2330
2522
  fileDetailsSection: {
2331
- margin: 16,
2523
+ margin: 12,
2332
2524
  marginTop: 0,
2333
- padding: 20,
2334
- borderRadius: 12,
2525
+ padding: 14,
2526
+ borderRadius: 10,
2335
2527
  borderWidth: 1
2336
2528
  },
2337
2529
  fileDetailsSectionTitle: {
@@ -2374,6 +2566,15 @@ const styles = _reactNative.StyleSheet.create({
2374
2566
  alignItems: 'center',
2375
2567
  gap: 16
2376
2568
  },
2569
+ controlsBar: {
2570
+ flexDirection: 'row',
2571
+ alignItems: 'center',
2572
+ justifyContent: 'space-between',
2573
+ paddingHorizontal: 16,
2574
+ paddingTop: 8,
2575
+ paddingBottom: 4,
2576
+ gap: 12
2577
+ },
2377
2578
  viewModeToggle: {
2378
2579
  flexDirection: 'row',
2379
2580
  borderRadius: 24,
@@ -2391,17 +2592,17 @@ const styles = _reactNative.StyleSheet.create({
2391
2592
  },
2392
2593
  // Photo Grid styles
2393
2594
  photoScrollContainer: {
2394
- padding: 16
2595
+ padding: 10
2395
2596
  },
2396
2597
  photoDateSection: {
2397
- marginBottom: 24
2598
+ marginBottom: 16
2398
2599
  },
2399
2600
  photoDateHeader: {
2400
- fontSize: 18,
2601
+ fontSize: 16,
2401
2602
  fontWeight: '600',
2402
2603
  fontFamily: _fonts.fontFamilies.phuduSemiBold,
2403
- marginBottom: 12,
2404
- paddingHorizontal: 4
2604
+ marginBottom: 8,
2605
+ paddingHorizontal: 2
2405
2606
  },
2406
2607
  photoGrid: {
2407
2608
  flexDirection: 'row',
@@ -2453,7 +2654,7 @@ const styles = _reactNative.StyleSheet.create({
2453
2654
  position: 'relative',
2454
2655
  borderRadius: 6,
2455
2656
  overflow: 'hidden',
2456
- backgroundColor: '#F5F5F5'
2657
+ backgroundColor: 'transparent'
2457
2658
  },
2458
2659
  justifiedPhotoImage: {
2459
2660
  width: '100%',
@@ -2464,7 +2665,7 @@ const styles = _reactNative.StyleSheet.create({
2464
2665
  simplePhotoItem: {
2465
2666
  borderRadius: 8,
2466
2667
  overflow: 'hidden',
2467
- backgroundColor: '#F5F5F5'
2668
+ backgroundColor: 'transparent'
2468
2669
  },
2469
2670
  simplePhotoContainer: {
2470
2671
  width: '100%',