@oxyhq/services 5.13.4 → 5.13.10

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 (170) hide show
  1. package/lib/commonjs/core/HttpClient.js +1 -1
  2. package/lib/commonjs/core/HttpClient.js.map +1 -1
  3. package/lib/commonjs/core/OxyServices.js +83 -30
  4. package/lib/commonjs/core/OxyServices.js.map +1 -1
  5. package/lib/commonjs/core/index.js +0 -7
  6. package/lib/commonjs/core/index.js.map +1 -1
  7. package/lib/commonjs/i18n/locales/en-US.json +222 -6
  8. package/lib/commonjs/index.js +0 -7
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/lib/sonner.js.map +1 -1
  11. package/lib/commonjs/ui/components/GroupedItem.js +24 -22
  12. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  13. package/lib/commonjs/ui/components/OxyProvider.js +35 -14
  14. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  15. package/lib/commonjs/ui/navigation/routes.js +36 -1
  16. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  17. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +150 -5
  18. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  19. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +475 -319
  20. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  21. package/lib/commonjs/ui/screens/AccountVerificationScreen.js +217 -0
  22. package/lib/commonjs/ui/screens/AccountVerificationScreen.js.map +1 -0
  23. package/lib/commonjs/ui/screens/FileManagementScreen.js +911 -213
  24. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  25. package/lib/commonjs/ui/screens/HelpSupportScreen.js +131 -0
  26. package/lib/commonjs/ui/screens/HelpSupportScreen.js.map +1 -0
  27. package/lib/commonjs/ui/screens/HistoryViewScreen.js +258 -0
  28. package/lib/commonjs/ui/screens/HistoryViewScreen.js.map +1 -0
  29. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js +211 -0
  30. package/lib/commonjs/ui/screens/LegalDocumentsScreen.js.map +1 -0
  31. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +0 -1
  32. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +307 -0
  34. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -0
  35. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -7
  36. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/SavesCollectionsScreen.js +205 -0
  38. package/lib/commonjs/ui/screens/SavesCollectionsScreen.js.map +1 -0
  39. package/lib/commonjs/ui/screens/SearchSettingsScreen.js +239 -0
  40. package/lib/commonjs/ui/screens/SearchSettingsScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/SignInScreen.js +14 -29
  42. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  43. package/lib/commonjs/utils/asyncUtils.js +1 -0
  44. package/lib/commonjs/utils/asyncUtils.js.map +1 -1
  45. package/lib/commonjs/utils/cache.js +4 -4
  46. package/lib/commonjs/utils/cache.js.map +1 -1
  47. package/lib/commonjs/utils/index.js +0 -6
  48. package/lib/commonjs/utils/index.js.map +1 -1
  49. package/lib/module/core/HttpClient.js +1 -1
  50. package/lib/module/core/HttpClient.js.map +1 -1
  51. package/lib/module/core/OxyServices.js +82 -29
  52. package/lib/module/core/OxyServices.js.map +1 -1
  53. package/lib/module/core/index.js +1 -1
  54. package/lib/module/core/index.js.map +1 -1
  55. package/lib/module/i18n/locales/en-US.json +222 -6
  56. package/lib/module/index.js +1 -1
  57. package/lib/module/index.js.map +1 -1
  58. package/lib/module/lib/sonner.js.map +1 -1
  59. package/lib/module/ui/components/GroupedItem.js +24 -22
  60. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  61. package/lib/module/ui/components/OxyProvider.js +40 -17
  62. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  63. package/lib/module/ui/navigation/routes.js +36 -1
  64. package/lib/module/ui/navigation/routes.js.map +1 -1
  65. package/lib/module/ui/screens/AccountOverviewScreen.js +151 -6
  66. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  67. package/lib/module/ui/screens/AccountSettingsScreen.js +475 -319
  68. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  69. package/lib/module/ui/screens/AccountVerificationScreen.js +212 -0
  70. package/lib/module/ui/screens/AccountVerificationScreen.js.map +1 -0
  71. package/lib/module/ui/screens/FileManagementScreen.js +913 -212
  72. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  73. package/lib/module/ui/screens/HelpSupportScreen.js +126 -0
  74. package/lib/module/ui/screens/HelpSupportScreen.js.map +1 -0
  75. package/lib/module/ui/screens/HistoryViewScreen.js +253 -0
  76. package/lib/module/ui/screens/HistoryViewScreen.js.map +1 -0
  77. package/lib/module/ui/screens/LegalDocumentsScreen.js +206 -0
  78. package/lib/module/ui/screens/LegalDocumentsScreen.js.map +1 -0
  79. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +0 -1
  80. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  81. package/lib/module/ui/screens/PrivacySettingsScreen.js +302 -0
  82. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -0
  83. package/lib/module/ui/screens/ProfileScreen.js +1 -7
  84. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  85. package/lib/module/ui/screens/SavesCollectionsScreen.js +200 -0
  86. package/lib/module/ui/screens/SavesCollectionsScreen.js.map +1 -0
  87. package/lib/module/ui/screens/SearchSettingsScreen.js +234 -0
  88. package/lib/module/ui/screens/SearchSettingsScreen.js.map +1 -0
  89. package/lib/module/ui/screens/SignInScreen.js +14 -29
  90. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  91. package/lib/module/utils/asyncUtils.js +1 -0
  92. package/lib/module/utils/asyncUtils.js.map +1 -1
  93. package/lib/module/utils/cache.js +3 -3
  94. package/lib/module/utils/cache.js.map +1 -1
  95. package/lib/module/utils/index.js +1 -1
  96. package/lib/module/utils/index.js.map +1 -1
  97. package/lib/typescript/core/OxyServices.d.ts +30 -24
  98. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  99. package/lib/typescript/core/index.d.ts +1 -1
  100. package/lib/typescript/core/index.d.ts.map +1 -1
  101. package/lib/typescript/index.d.ts +1 -1
  102. package/lib/typescript/index.d.ts.map +1 -1
  103. package/lib/typescript/lib/sonner.d.ts +1 -0
  104. package/lib/typescript/lib/sonner.d.ts.map +1 -1
  105. package/lib/typescript/types/expo-document-picker.d.ts +36 -0
  106. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  107. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  108. package/lib/typescript/ui/navigation/routes.d.ts +1 -1
  109. package/lib/typescript/ui/navigation/routes.d.ts.map +1 -1
  110. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  111. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  112. package/lib/typescript/ui/screens/AccountVerificationScreen.d.ts +5 -0
  113. package/lib/typescript/ui/screens/AccountVerificationScreen.d.ts.map +1 -0
  114. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  115. package/lib/typescript/ui/screens/HelpSupportScreen.d.ts +5 -0
  116. package/lib/typescript/ui/screens/HelpSupportScreen.d.ts.map +1 -0
  117. package/lib/typescript/ui/screens/HistoryViewScreen.d.ts +5 -0
  118. package/lib/typescript/ui/screens/HistoryViewScreen.d.ts.map +1 -0
  119. package/lib/typescript/ui/screens/LegalDocumentsScreen.d.ts +5 -0
  120. package/lib/typescript/ui/screens/LegalDocumentsScreen.d.ts.map +1 -0
  121. package/lib/typescript/ui/screens/PremiumSubscriptionScreen.d.ts.map +1 -1
  122. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts +5 -0
  123. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -0
  124. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  125. package/lib/typescript/ui/screens/SavesCollectionsScreen.d.ts +5 -0
  126. package/lib/typescript/ui/screens/SavesCollectionsScreen.d.ts.map +1 -0
  127. package/lib/typescript/ui/screens/SearchSettingsScreen.d.ts +5 -0
  128. package/lib/typescript/ui/screens/SearchSettingsScreen.d.ts.map +1 -0
  129. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  130. package/lib/typescript/utils/asyncUtils.d.ts.map +1 -1
  131. package/lib/typescript/utils/cache.d.ts +3 -3
  132. package/lib/typescript/utils/cache.d.ts.map +1 -1
  133. package/lib/typescript/utils/index.d.ts +1 -1
  134. package/lib/typescript/utils/index.d.ts.map +1 -1
  135. package/package.json +1 -1
  136. package/src/core/HttpClient.ts +1 -1
  137. package/src/core/OxyServices.ts +80 -30
  138. package/src/core/index.ts +1 -1
  139. package/src/i18n/locales/en-US.json +222 -6
  140. package/src/index.ts +1 -1
  141. package/src/lib/sonner.ts +1 -0
  142. package/src/types/expo-document-picker.d.ts +36 -0
  143. package/src/ui/components/GroupedItem.tsx +23 -21
  144. package/src/ui/components/OxyProvider.tsx +33 -11
  145. package/src/ui/navigation/routes.ts +42 -0
  146. package/src/ui/screens/AccountOverviewScreen.tsx +175 -5
  147. package/src/ui/screens/AccountSettingsScreen.tsx +521 -360
  148. package/src/ui/screens/AccountVerificationScreen.tsx +235 -0
  149. package/src/ui/screens/FileManagementScreen.tsx +934 -208
  150. package/src/ui/screens/HelpSupportScreen.tsx +143 -0
  151. package/src/ui/screens/HistoryViewScreen.tsx +280 -0
  152. package/src/ui/screens/LegalDocumentsScreen.tsx +220 -0
  153. package/src/ui/screens/PremiumSubscriptionScreen.tsx +0 -1
  154. package/src/ui/screens/PrivacySettingsScreen.tsx +332 -0
  155. package/src/ui/screens/ProfileScreen.tsx +1 -8
  156. package/src/ui/screens/SavesCollectionsScreen.tsx +222 -0
  157. package/src/ui/screens/SearchSettingsScreen.tsx +219 -0
  158. package/src/ui/screens/SignInScreen.tsx +19 -35
  159. package/src/utils/asyncUtils.ts +1 -0
  160. package/src/utils/cache.ts +3 -3
  161. package/src/utils/index.ts +1 -1
  162. package/lib/commonjs/ui/components/StepBasedScreen.README.md +0 -337
  163. package/lib/commonjs/ui/components/internal/TextField.md +0 -436
  164. package/lib/commonjs/ui/styles/FONTS.md +0 -126
  165. package/lib/module/ui/components/StepBasedScreen.README.md +0 -337
  166. package/lib/module/ui/components/internal/TextField.md +0 -436
  167. package/lib/module/ui/styles/FONTS.md +0 -126
  168. package/src/ui/components/StepBasedScreen.README.md +0 -337
  169. package/src/ui/components/internal/TextField.md +0 -436
  170. package/src/ui/styles/FONTS.md +0 -126
@@ -17,9 +17,7 @@ var _JustifiedPhotoGrid = _interopRequireDefault(require("../components/photogri
17
17
  var _components = require("../components");
18
18
  var _jsxRuntime = require("react/jsx-runtime");
19
19
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
20
- 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); }
21
- // Exporting props & callback types so external callers (e.g. showBottomSheet config objects) can annotate
22
-
20
+ 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); } // Exporting props & callback types so external callers (e.g. showBottomSheet config objects) can annotate
23
21
  // Add this helper function near the top (after imports):
24
22
  async function uploadFileRaw(file, userId, oxyServices, visibility) {
25
23
  return await oxyServices.uploadRawFile(file, visibility);
@@ -48,18 +46,6 @@ const FileManagementScreen = ({
48
46
  user,
49
47
  oxyServices
50
48
  } = (0, _OxyContext.useOxy)();
51
-
52
- // Debug: log the actual container width
53
- (0, _react.useEffect)(() => {
54
- console.log('[FileManagementScreen] Container width (full):', containerWidth);
55
- // Padding structure:
56
- // - containerWidth = full bottom sheet container width (measured from OxyProvider)
57
- // - photoScrollContainer adds padding: 16 (32px total horizontal padding)
58
- // - Available content width = containerWidth - 32
59
- const availableContentWidth = containerWidth - 32;
60
- console.log('[FileManagementScreen] Available content width:', availableContentWidth);
61
- console.log('[FileManagementScreen] Spacing fix applied: 4px uniform gap both horizontal and vertical');
62
- }, [containerWidth]);
63
49
  const files = (0, _fileStore.useFiles)();
64
50
  const uploading = (0, _fileStore.useUploading)();
65
51
  const uploadProgress = (0, _fileStore.useUploadAggregateProgress)();
@@ -82,18 +68,46 @@ const FileManagementScreen = ({
82
68
  const [showFileDetailsInViewer, setShowFileDetailsInViewer] = (0, _react.useState)(false);
83
69
  const [viewMode, setViewMode] = (0, _react.useState)('all');
84
70
  const [searchQuery, setSearchQuery] = (0, _react.useState)('');
85
- // Derived filtered files (avoid setState loops)
71
+ const [sortBy, setSortBy] = (0, _react.useState)('date');
72
+ const [sortOrder, setSortOrder] = (0, _react.useState)('desc');
73
+ const [pendingFiles, setPendingFiles] = (0, _react.useState)([]);
74
+ const [showUploadPreview, setShowUploadPreview] = (0, _react.useState)(false);
75
+ // Derived filtered and sorted files (avoid setState loops)
86
76
  const filteredFiles = (0, _react.useMemo)(() => {
87
77
  let filteredByMode = files;
88
78
  if (viewMode === 'photos') {
89
79
  filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
80
+ } else if (viewMode === 'videos') {
81
+ filteredByMode = files.filter(file => file.contentType.startsWith('video/'));
82
+ } else if (viewMode === 'documents') {
83
+ filteredByMode = files.filter(file => file.contentType.includes('pdf') || file.contentType.includes('document') || file.contentType.includes('text') || file.contentType.includes('msword') || file.contentType.includes('excel') || file.contentType.includes('spreadsheet') || file.contentType.includes('presentation') || file.contentType.includes('powerpoint'));
84
+ } else if (viewMode === 'audio') {
85
+ filteredByMode = files.filter(file => file.contentType.startsWith('audio/'));
90
86
  }
91
- if (!searchQuery.trim()) {
92
- return filteredByMode;
87
+ let filtered = filteredByMode;
88
+ if (searchQuery.trim()) {
89
+ const query = searchQuery.toLowerCase();
90
+ filtered = filteredByMode.filter(file => file.filename.toLowerCase().includes(query) || file.contentType.toLowerCase().includes(query) || file.metadata?.description && file.metadata.description.toLowerCase().includes(query));
93
91
  }
94
- const query = searchQuery.toLowerCase();
95
- return filteredByMode.filter(file => file.filename.toLowerCase().includes(query) || file.contentType.toLowerCase().includes(query) || file.metadata?.description && file.metadata.description.toLowerCase().includes(query));
96
- }, [files, searchQuery, viewMode]);
92
+
93
+ // Sort files
94
+ const sorted = [...filtered].sort((a, b) => {
95
+ let comparison = 0;
96
+ if (sortBy === 'date') {
97
+ const dateA = new Date(a.uploadDate || 0).getTime();
98
+ const dateB = new Date(b.uploadDate || 0).getTime();
99
+ comparison = dateA - dateB;
100
+ } else if (sortBy === 'size') {
101
+ comparison = (a.length || 0) - (b.length || 0);
102
+ } else if (sortBy === 'name') {
103
+ comparison = (a.filename || '').localeCompare(b.filename || '');
104
+ } else if (sortBy === 'type') {
105
+ comparison = (a.contentType || '').localeCompare(b.contentType || '');
106
+ }
107
+ return sortOrder === 'asc' ? comparison : -comparison;
108
+ });
109
+ return sorted;
110
+ }, [files, searchQuery, viewMode, sortBy, sortOrder]);
97
111
  const [isDragging, setIsDragging] = (0, _react.useState)(false);
98
112
  const [photoDimensions, setPhotoDimensions] = (0, _react.useState)({});
99
113
  const [loadingDimensions, setLoadingDimensions] = (0, _react.useState)(false);
@@ -102,13 +116,19 @@ const FileManagementScreen = ({
102
116
  const MIN_BANNER_MS = 600;
103
117
  // Selection state
104
118
  const [selectedIds, setSelectedIds] = (0, _react.useState)(new Set(initialSelectedIds));
119
+ const [lastSelectedFileId, setLastSelectedFileId] = (0, _react.useState)(null);
120
+ const scrollViewRef = (0, _react.useRef)(null);
121
+ const photoScrollViewRef = (0, _react.useRef)(null);
122
+ const itemRefs = (0, _react.useRef)(new Map()); // Track item positions
123
+ const containerRef = (0, _react.useRef)(null); // Ref for drag and drop container
105
124
  (0, _react.useEffect)(() => {
106
125
  if (initialSelectedIds && initialSelectedIds.length) {
107
126
  setSelectedIds(new Set(initialSelectedIds));
108
127
  }
109
128
  }, [initialSelectedIds]);
110
129
  const toggleSelect = (0, _react.useCallback)(async file => {
111
- if (!selectMode) return;
130
+ // Allow selection in regular mode for bulk operations
131
+ // if (!selectMode) return;
112
132
  if (disabledMimeTypes.length) {
113
133
  const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
114
134
  if (blocked) {
@@ -122,20 +142,19 @@ const FileManagementScreen = ({
122
142
  if (fileVisibility !== defaultVisibility) {
123
143
  try {
124
144
  await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
125
- console.log(`Updated file ${file.id} visibility from ${fileVisibility} to ${defaultVisibility}`);
126
145
  } catch (error) {
127
- console.error('Failed to update file visibility:', error);
128
146
  // Continue anyway - selection shouldn't fail if visibility update fails
129
147
  }
130
148
  }
131
149
 
150
+ // Track the selected file for scrolling
151
+ setLastSelectedFileId(file.id);
152
+
132
153
  // Link file to entity if linkContext is provided
133
154
  if (linkContext) {
134
155
  try {
135
156
  await oxyServices.assetLink(file.id, linkContext.app, linkContext.entityType, linkContext.entityId, defaultVisibility, linkContext.webhookUrl);
136
- console.log(`Linked file ${file.id} to ${linkContext.app}/${linkContext.entityType}/${linkContext.entityId}`);
137
157
  } catch (error) {
138
- console.error('Failed to link file:', error);
139
158
  // Continue anyway - selection shouldn't fail if linking fails
140
159
  }
141
160
  }
@@ -178,9 +197,8 @@ const FileManagementScreen = ({
178
197
  if (fileVisibility !== defaultVisibility) {
179
198
  try {
180
199
  await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
181
- console.log(`Updated file ${file.id} visibility from ${fileVisibility} to ${defaultVisibility}`);
182
200
  } catch (error) {
183
- console.error(`Failed to update visibility for ${file.id}:`, error);
201
+ // Visibility update failed, continue with selection
184
202
  }
185
203
  }
186
204
 
@@ -188,9 +206,8 @@ const FileManagementScreen = ({
188
206
  if (linkContext) {
189
207
  try {
190
208
  await oxyServices.assetLink(file.id, linkContext.app, linkContext.entityType, linkContext.entityId, defaultVisibility, linkContext.webhookUrl);
191
- console.log(`Linked file ${file.id} to ${linkContext.app}/${linkContext.entityType}/${linkContext.entityId}`);
192
209
  } catch (error) {
193
- console.error(`Failed to link file ${file.id}:`, error);
210
+ // File linking failed, continue with selection
194
211
  }
195
212
  }
196
213
  });
@@ -322,7 +339,6 @@ const FileManagementScreen = ({
322
339
  }));
323
340
  }
324
341
  } catch (error) {
325
- console.error('Failed to load files:', error);
326
342
  _sonner.toast.error(error.message || 'Failed to load files');
327
343
  } finally {
328
344
  setLoading(false);
@@ -417,7 +433,7 @@ const FileManagementScreen = ({
417
433
  setPhotoDimensions(newDimensions);
418
434
  }
419
435
  } catch (error) {
420
- console.error('Error loading photo dimensions:', error);
436
+ // Photo dimensions loading failed, continue without dimensions
421
437
  } finally {
422
438
  setLoadingDimensions(false);
423
439
  }
@@ -521,85 +537,170 @@ const FileManagementScreen = ({
521
537
  loadFiles('silent');
522
538
  }, 1200);
523
539
  } catch (error) {
524
- console.error('Upload error:', error);
525
540
  _sonner.toast.error(error.message || 'Failed to upload files');
526
541
  } finally {
527
542
  storeSetUploadProgress(null);
528
543
  }
529
544
  };
545
+ const handleFileSelection = (0, _react.useCallback)(async selectedFiles => {
546
+ const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
547
+ const processedFiles = [];
548
+ for (const file of selectedFiles) {
549
+ // Validate file size
550
+ if (file.size > MAX_FILE_SIZE) {
551
+ _sonner.toast.error(`"${file.name}" is too large. Maximum file size is ${formatFileSize(MAX_FILE_SIZE)}`);
552
+ continue;
553
+ }
554
+
555
+ // Generate preview for images
556
+ let preview;
557
+ if (file.type.startsWith('image/')) {
558
+ preview = URL.createObjectURL(file);
559
+ }
560
+ processedFiles.push({
561
+ file,
562
+ preview,
563
+ size: file.size,
564
+ name: file.name,
565
+ type: file.type
566
+ });
567
+ }
568
+ if (processedFiles.length === 0) return;
569
+
570
+ // Show preview modal for user to review files before upload
571
+ setPendingFiles(processedFiles);
572
+ setShowUploadPreview(true);
573
+ }, []);
574
+ const handleConfirmUpload = async () => {
575
+ if (pendingFiles.length === 0) return;
576
+ setShowUploadPreview(false);
577
+ uploadStartRef.current = Date.now();
578
+ storeSetUploading(true);
579
+ storeSetUploadProgress(null);
580
+ try {
581
+ const filesToUpload = pendingFiles.map(pf => pf.file);
582
+ storeSetUploadProgress({
583
+ current: 0,
584
+ total: filesToUpload.length
585
+ });
586
+ await processFileUploads(filesToUpload);
587
+
588
+ // Cleanup preview URLs
589
+ pendingFiles.forEach(pf => {
590
+ if (pf.preview) {
591
+ URL.revokeObjectURL(pf.preview);
592
+ }
593
+ });
594
+ setPendingFiles([]);
595
+ endUpload();
596
+ } catch (error) {
597
+ _sonner.toast.error(error.message || 'Failed to upload files');
598
+ endUpload();
599
+ }
600
+ };
601
+ const handleCancelUpload = () => {
602
+ // Cleanup preview URLs
603
+ pendingFiles.forEach(pf => {
604
+ if (pf.preview) {
605
+ URL.revokeObjectURL(pf.preview);
606
+ }
607
+ });
608
+ setPendingFiles([]);
609
+ setShowUploadPreview(false);
610
+ };
611
+ const removePendingFile = index => {
612
+ const file = pendingFiles[index];
613
+ if (file.preview) {
614
+ URL.revokeObjectURL(file.preview);
615
+ }
616
+ const updated = pendingFiles.filter((_, i) => i !== index);
617
+ setPendingFiles(updated);
618
+ if (updated.length === 0) {
619
+ setShowUploadPreview(false);
620
+ }
621
+ };
530
622
  const handleFileUpload = async () => {
531
623
  try {
532
- uploadStartRef.current = Date.now();
533
- storeSetUploading(true);
534
- storeSetUploadProgress(null);
535
624
  if (_reactNative.Platform.OS === 'web') {
536
- // Web file picker implementation
625
+ // Enhanced web file picker
537
626
  const input = document.createElement('input');
538
627
  input.type = 'file';
539
628
  input.multiple = true;
540
629
  input.accept = '*/*';
541
- // Fallback: if the user cancels the dialog (no onchange fires or 0 files), hide banner
542
630
  const cancellationTimer = setTimeout(() => {
543
631
  const state = _fileStore.useFileStore.getState();
544
632
  if (state.uploading && uploadStartRef.current && !state.uploadProgress) {
545
- // No selection happened; treat as cancel
546
633
  endUpload();
547
634
  }
548
- }, 1500); // allow enough time for user to pick
549
-
635
+ }, 1500);
550
636
  input.onchange = async e => {
551
637
  clearTimeout(cancellationTimer);
552
638
  const selectedFiles = Array.from(e.target.files || []);
553
639
  if (selectedFiles.length === 0) {
554
- // User explicitly canceled (some browsers still fire onchange with empty list)
555
640
  endUpload();
556
641
  return;
557
642
  }
558
- storeSetUploadProgress({
559
- current: 0,
560
- total: selectedFiles.length
561
- });
562
- await processFileUploads(selectedFiles);
563
- endUpload();
643
+ await handleFileSelection(selectedFiles);
564
644
  };
565
645
  input.click();
566
646
  } else {
567
- // Mobile - show info that file picker can be added
568
- const installCommand = 'npm install expo-document-picker';
569
- const message = `Mobile File Upload\n\nTo enable file uploads on mobile, install expo-document-picker:\n\n${installCommand}\n\nThen import and use DocumentPicker.getDocumentAsync() in this method.`;
570
- if (window.confirm(`${message}\n\nWould you like to copy the install command?`)) {
571
- _sonner.toast.info(`Install: ${installCommand}`);
572
- } else {
573
- _sonner.toast.info('Mobile file upload requires expo-document-picker');
647
+ // Mobile file picker with expo-document-picker
648
+ try {
649
+ // Dynamically import to avoid breaking if not installed
650
+ const DocumentPicker = await Promise.resolve().then(() => _interopRequireWildcard(require('expo-document-picker'))).catch(() => null);
651
+ if (!DocumentPicker) {
652
+ _sonner.toast.error('File picker not available. Please install expo-document-picker');
653
+ return;
654
+ }
655
+ const result = await DocumentPicker.getDocumentAsync({
656
+ type: '*/*',
657
+ multiple: true,
658
+ copyToCacheDirectory: true
659
+ });
660
+ if (result.canceled) {
661
+ return;
662
+ }
663
+
664
+ // Convert expo document picker results to File-like objects
665
+ const files = [];
666
+ for (const doc of result.assets) {
667
+ if (doc.file) {
668
+ // expo-document-picker provides a File-like object
669
+ files.push(doc.file);
670
+ } else if (doc.uri) {
671
+ // Fallback: fetch and create Blob
672
+ const response = await fetch(doc.uri);
673
+ const blob = await response.blob();
674
+ const file = new File([blob], doc.name || 'file', {
675
+ type: doc.mimeType || 'application/octet-stream'
676
+ });
677
+ files.push(file);
678
+ }
679
+ }
680
+ if (files.length > 0) {
681
+ await handleFileSelection(files);
682
+ }
683
+ } catch (error) {
684
+ if (error.message?.includes('expo-document-picker')) {
685
+ _sonner.toast.error('File picker not available. Please install expo-document-picker');
686
+ } else {
687
+ _sonner.toast.error(error.message || 'Failed to select files');
688
+ }
574
689
  }
575
690
  }
576
691
  } catch (error) {
577
- _sonner.toast.error(error.message || 'Failed to upload file');
578
- } finally {
579
- // IMPORTANT: Do NOT call endUpload here.
580
- // We only want to hide the banner after the actual upload(s) complete.
581
- // The input.onchange handler invokes processFileUploads then calls endUpload().
582
- // Calling endUpload here caused the banner to disappear while files were still uploading.
583
- storeSetUploadProgress(null); // keep clearing any stale progress
692
+ _sonner.toast.error(error.message || 'Failed to open file picker');
584
693
  }
585
694
  };
586
695
  const handleFileDelete = async (fileId, filename) => {
587
696
  // Use web-compatible confirmation dialog
588
697
  const confirmed = window.confirm(`Are you sure you want to delete "${filename}"? This action cannot be undone.`);
589
698
  if (!confirmed) {
590
- console.log('Delete cancelled by user');
591
699
  return;
592
700
  }
593
701
  try {
594
- console.log('Deleting file:', {
595
- fileId,
596
- filename
597
- });
598
- console.log('Target user ID:', targetUserId);
599
- console.log('Current user ID:', user?.id);
600
702
  storeSetDeleting(fileId);
601
- const result = await oxyServices.deleteFile(fileId);
602
- console.log('Delete result:', result);
703
+ await oxyServices.deleteFile(fileId);
603
704
  _sonner.toast.success('File deleted successfully');
604
705
 
605
706
  // Reload files after successful deletion
@@ -608,9 +709,6 @@ const FileManagementScreen = ({
608
709
  // Silent background reconcile
609
710
  setTimeout(() => loadFiles('silent'), 800);
610
711
  } catch (error) {
611
- console.error('Delete error:', error);
612
- console.error('Error details:', error.response?.data || error.message);
613
-
614
712
  // Provide specific error messages
615
713
  if (error.message?.includes('File not found') || error.message?.includes('404')) {
616
714
  _sonner.toast.error('File not found. It may have already been deleted.');
@@ -625,90 +723,151 @@ const FileManagementScreen = ({
625
723
  storeSetDeleting(null);
626
724
  }
627
725
  };
628
-
629
- // Drag and drop handlers for web
630
- const handleDragOver = e => {
631
- if (_reactNative.Platform.OS === 'web' && user?.id === targetUserId) {
632
- e.preventDefault();
633
- setIsDragging(true);
634
- }
635
- };
636
- const handleDragEnter = e => {
637
- if (_reactNative.Platform.OS === 'web' && user?.id === targetUserId) {
638
- e.preventDefault();
639
- setIsDragging(true);
726
+ const handleBulkDelete = (0, _react.useCallback)(async () => {
727
+ if (selectedIds.size === 0) return;
728
+ const fileMap = {};
729
+ files.forEach(f => {
730
+ fileMap[f.id] = f;
731
+ });
732
+ const selectedFiles = Array.from(selectedIds).map(id => fileMap[id]).filter(Boolean);
733
+ const confirmed = window.confirm(`Are you sure you want to delete ${selectedFiles.length} file(s)? This action cannot be undone.`);
734
+ if (!confirmed) return;
735
+ try {
736
+ const deletePromises = Array.from(selectedIds).map(async fileId => {
737
+ try {
738
+ await oxyServices.deleteFile(fileId);
739
+ _fileStore.useFileStore.getState().removeFile(fileId);
740
+ return {
741
+ success: true,
742
+ fileId
743
+ };
744
+ } catch (error) {
745
+ return {
746
+ success: false,
747
+ fileId,
748
+ error
749
+ };
750
+ }
751
+ });
752
+ const results = await Promise.allSettled(deletePromises);
753
+ const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
754
+ const failed = results.length - successful;
755
+ if (successful > 0) {
756
+ _sonner.toast.success(`${successful} file(s) deleted successfully`);
757
+ }
758
+ if (failed > 0) {
759
+ _sonner.toast.error(`${failed} file(s) failed to delete`);
760
+ }
761
+ setSelectedIds(new Set());
762
+ setTimeout(() => loadFiles('silent'), 800);
763
+ } catch (error) {
764
+ _sonner.toast.error(error.message || 'Failed to delete files');
640
765
  }
641
- };
642
- const handleDragLeave = e => {
643
- if (_reactNative.Platform.OS === 'web') {
644
- e.preventDefault();
645
- setIsDragging(false);
766
+ }, [selectedIds, files, oxyServices, loadFiles]);
767
+ const handleBulkVisibilityChange = (0, _react.useCallback)(async visibility => {
768
+ if (selectedIds.size === 0) return;
769
+ try {
770
+ const updatePromises = Array.from(selectedIds).map(async fileId => {
771
+ try {
772
+ await oxyServices.assetUpdateVisibility(fileId, visibility);
773
+ return {
774
+ success: true,
775
+ fileId
776
+ };
777
+ } catch (error) {
778
+ return {
779
+ success: false,
780
+ fileId,
781
+ error
782
+ };
783
+ }
784
+ });
785
+ const results = await Promise.allSettled(updatePromises);
786
+ const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
787
+ const failed = results.length - successful;
788
+ if (successful > 0) {
789
+ _sonner.toast.success(`${successful} file(s) visibility updated to ${visibility}`);
790
+ // Update file metadata in store
791
+ Array.from(selectedIds).forEach(fileId => {
792
+ _fileStore.useFileStore.getState().updateFile(fileId, {
793
+ metadata: {
794
+ ...files.find(f => f.id === fileId)?.metadata,
795
+ visibility
796
+ }
797
+ });
798
+ });
799
+ }
800
+ if (failed > 0) {
801
+ _sonner.toast.error(`${failed} file(s) failed to update visibility`);
802
+ }
803
+ setTimeout(() => loadFiles('silent'), 800);
804
+ } catch (error) {
805
+ _sonner.toast.error(error.message || 'Failed to update visibility');
646
806
  }
647
- };
807
+ }, [selectedIds, oxyServices, files, loadFiles]);
648
808
 
649
- // Global drag listeners (web) to catch drags outside component bounds
809
+ // Global drag listeners (web) - attach to document for reliable drag and drop
650
810
  (0, _react.useEffect)(() => {
651
811
  if (_reactNative.Platform.OS !== 'web' || user?.id !== targetUserId) return;
652
- const onDocDragEnter = e => {
653
- if (e?.dataTransfer?.types?.includes('Files')) setIsDragging(true);
812
+ let dragCounter = 0; // Track drag enter/leave to handle nested elements
813
+
814
+ const onDragEnter = e => {
815
+ dragCounter++;
816
+ if (e?.dataTransfer?.types?.includes('Files')) {
817
+ e.preventDefault();
818
+ e.stopPropagation();
819
+ setIsDragging(true);
820
+ }
654
821
  };
655
- const onDocDragOver = e => {
822
+ const onDragOver = e => {
656
823
  if (e?.dataTransfer?.types?.includes('Files')) {
657
824
  e.preventDefault();
825
+ e.stopPropagation();
826
+ // Keep dragging state true while over document
658
827
  setIsDragging(true);
659
828
  }
660
829
  };
661
- const onDocDrop = e => {
830
+ const onDrop = async e => {
831
+ dragCounter = 0;
832
+ setIsDragging(false);
662
833
  if (e?.dataTransfer?.files?.length) {
663
834
  e.preventDefault();
664
- setIsDragging(false);
835
+ e.stopPropagation();
836
+ try {
837
+ const files = Array.from(e.dataTransfer.files);
838
+ if (files.length > 0) {
839
+ await handleFileSelection(files);
840
+ }
841
+ } catch (error) {
842
+ _sonner.toast.error(error.message || 'Failed to upload files');
843
+ }
665
844
  }
666
845
  };
667
- const onDocDragLeave = e => {
668
- if (!e.relatedTarget && e.screenX === 0 && e.screenY === 0) setIsDragging(false);
846
+ const onDragLeave = e => {
847
+ dragCounter--;
848
+ // Only hide drag overlay if we're actually leaving the document (drag counter reaches 0)
849
+ if (dragCounter === 0) {
850
+ setIsDragging(false);
851
+ }
669
852
  };
670
- document.addEventListener('dragenter', onDocDragEnter);
671
- document.addEventListener('dragover', onDocDragOver);
672
- document.addEventListener('drop', onDocDrop);
673
- document.addEventListener('dragleave', onDocDragLeave);
853
+
854
+ // Attach to document for global drag detection
855
+ document.addEventListener('dragenter', onDragEnter, false);
856
+ document.addEventListener('dragover', onDragOver, false);
857
+ document.addEventListener('drop', onDrop, false);
858
+ document.addEventListener('dragleave', onDragLeave, false);
674
859
  return () => {
675
- document.removeEventListener('dragenter', onDocDragEnter);
676
- document.removeEventListener('dragover', onDocDragOver);
677
- document.removeEventListener('drop', onDocDrop);
678
- document.removeEventListener('dragleave', onDocDragLeave);
860
+ document.removeEventListener('dragenter', onDragEnter, false);
861
+ document.removeEventListener('dragover', onDragOver, false);
862
+ document.removeEventListener('drop', onDrop, false);
863
+ document.removeEventListener('dragleave', onDragLeave, false);
679
864
  };
680
- }, [user?.id, targetUserId]);
681
- const handleDrop = async e => {
682
- if (_reactNative.Platform.OS === 'web' && user?.id === targetUserId) {
683
- e.preventDefault();
684
- setIsDragging(false);
685
- uploadStartRef.current = Date.now();
686
- storeSetUploading(true);
687
- try {
688
- const files = Array.from(e.dataTransfer.files);
689
- if (files.length > 0) storeSetUploadProgress({
690
- current: 0,
691
- total: files.length
692
- });
693
- await processFileUploads(files);
694
- } catch (error) {
695
- _sonner.toast.error(error.message || 'Failed to upload files');
696
- } finally {
697
- endUpload();
698
- }
699
- }
700
- };
865
+ }, [user?.id, targetUserId, handleFileSelection]);
701
866
  const handleFileDownload = async (fileId, filename) => {
702
867
  try {
703
868
  if (_reactNative.Platform.OS === 'web') {
704
- console.log('Downloading file:', {
705
- fileId,
706
- filename
707
- });
708
-
709
869
  // Use the public download URL method
710
870
  const downloadUrl = oxyServices.getFileDownloadUrl(fileId);
711
- console.log('Download URL:', downloadUrl);
712
871
  try {
713
872
  // Method 1: Try simple link download first
714
873
  const link = document.createElement('a');
@@ -720,8 +879,6 @@ const FileManagementScreen = ({
720
879
  document.body.removeChild(link);
721
880
  _sonner.toast.success('File download started');
722
881
  } catch (linkError) {
723
- console.warn('Link download failed, trying fetch method:', linkError);
724
-
725
882
  // Method 2: Fallback to authenticated download
726
883
  const blob = await oxyServices.getFileContentAsBlob(fileId);
727
884
  const url = window.URL.createObjectURL(blob);
@@ -740,7 +897,6 @@ const FileManagementScreen = ({
740
897
  _sonner.toast.info('File download not implemented for mobile yet');
741
898
  }
742
899
  } catch (error) {
743
- console.error('Download error:', error);
744
900
  _sonner.toast.error(error.message || 'Failed to download file');
745
901
  }
746
902
  };
@@ -783,7 +939,6 @@ const FileManagementScreen = ({
783
939
  setFileContent(content);
784
940
  }
785
941
  } catch (error) {
786
- console.error('Failed to load file content:', error);
787
942
  if (error.message?.includes('404') || error.message?.includes('not found')) {
788
943
  _sonner.toast.error('File not found. It may have been deleted.');
789
944
  } else {
@@ -796,7 +951,6 @@ const FileManagementScreen = ({
796
951
  setFileContent(null);
797
952
  }
798
953
  } catch (error) {
799
- console.error('Failed to open file:', error);
800
954
  _sonner.toast.error(error.message || 'Failed to open file');
801
955
  } finally {
802
956
  setLoadingFileContent(false);
@@ -847,8 +1001,8 @@ const FileManagementScreen = ({
847
1001
  contentFit: "cover",
848
1002
  transition: 120,
849
1003
  cachePolicy: "memory-disk",
850
- onError: e => {
851
- console.error('Photo failed to load:', e?.nativeEvent ?? e);
1004
+ onError: () => {
1005
+ // Photo failed to load, will show placeholder
852
1006
  },
853
1007
  accessibilityLabel: photo.filename
854
1008
  }), selectMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -888,8 +1042,8 @@ const FileManagementScreen = ({
888
1042
  contentFit: "cover",
889
1043
  transition: 120,
890
1044
  cachePolicy: "memory-disk",
891
- onError: e => {
892
- console.error('Photo failed to load:', e?.nativeEvent ?? e);
1045
+ onError: () => {
1046
+ // Photo failed to load, will show placeholder
893
1047
  },
894
1048
  accessibilityLabel: photo.filename
895
1049
  }), selectMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -948,8 +1102,8 @@ const FileManagementScreen = ({
948
1102
  contentFit: "cover",
949
1103
  transition: 120,
950
1104
  cachePolicy: "memory-disk",
951
- onError: _ => {
952
- console.warn('Failed to load image preview.');
1105
+ onError: () => {
1106
+ // Image preview failed to load
953
1107
  },
954
1108
  accessibilityLabel: file.filename
955
1109
  }), isPDF && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -1087,7 +1241,14 @@ const FileManagementScreen = ({
1087
1241
 
1088
1242
  // GroupedSection-based file items (for 'all' view) replacing legacy flat list look
1089
1243
  const groupedFileItems = (0, _react.useMemo)(() => {
1090
- return filteredFiles.filter(f => true).sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime()).map(file => {
1244
+ // filteredFiles is already sorted, so just use it directly
1245
+ const sortedFiles = filteredFiles;
1246
+
1247
+ // Store file positions for scrolling
1248
+ sortedFiles.forEach((file, index) => {
1249
+ itemRefs.current.set(file.id, index);
1250
+ });
1251
+ return sortedFiles.map(file => {
1091
1252
  const isImage = file.contentType.startsWith('image/');
1092
1253
  const isVideo = file.contentType.startsWith('video/');
1093
1254
  const hasPreview = isImage || isVideo;
@@ -1102,13 +1263,29 @@ const FileManagementScreen = ({
1102
1263
  title: file.filename,
1103
1264
  subtitle: `${formatFileSize(file.length)} • ${new Date(file.uploadDate).toLocaleDateString()}`,
1104
1265
  theme: theme,
1105
- onPress: () => handleFileOpen(file),
1266
+ onPress: () => {
1267
+ // Support selection in regular mode with long press or if already selecting
1268
+ if (!selectMode && selectedIds.size > 0) {
1269
+ // If already in selection mode (some files selected), toggle selection
1270
+ toggleSelect(file);
1271
+ } else {
1272
+ handleFileOpen(file);
1273
+ }
1274
+ },
1275
+ onLongPress: !selectMode ? () => {
1276
+ // Enable selection mode on long press
1277
+ if (selectedIds.size === 0) {
1278
+ setSelectedIds(new Set([file.id]));
1279
+ } else {
1280
+ toggleSelect(file);
1281
+ }
1282
+ } : undefined,
1106
1283
  showChevron: false,
1107
1284
  dense: true,
1108
1285
  multiRow: !!file.metadata?.description,
1109
- selected: selectMode && isSelected,
1110
- // Hide action buttons when selecting
1111
- customContent: !selectMode ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1286
+ selected: (selectMode || selectedIds.size > 0) && isSelected,
1287
+ // Hide action buttons when selecting (in selectMode or bulk operations mode)
1288
+ customContent: !selectMode && selectedIds.size === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1112
1289
  style: styles.groupedActions,
1113
1290
  children: [(isImage || isVideo || file.contentType.includes('pdf')) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1114
1291
  style: [styles.groupedActionBtn, {
@@ -1156,6 +1333,88 @@ const FileManagementScreen = ({
1156
1333
  };
1157
1334
  });
1158
1335
  }, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl, selectMode, selectedIds]);
1336
+
1337
+ // Scroll to selected file after selection
1338
+ (0, _react.useEffect)(() => {
1339
+ if (lastSelectedFileId && selectMode) {
1340
+ if (viewMode === 'all' && scrollViewRef.current) {
1341
+ // Find the index of the selected file
1342
+ const itemIndex = itemRefs.current.get(lastSelectedFileId);
1343
+ if (itemIndex !== undefined && itemIndex >= 0) {
1344
+ // Estimate item height (GroupedItem with dense mode is approximately 60-70px)
1345
+ // Account for description rows which add extra height
1346
+ const baseItemHeight = 65;
1347
+ const descriptionHeight = 30; // Approximate height for description
1348
+ // Use filteredFiles which is already sorted according to user's selection
1349
+ const sortedFiles = filteredFiles;
1350
+
1351
+ // Calculate total height up to this item
1352
+ let scrollPosition = 0;
1353
+ for (let i = 0; i <= itemIndex && i < sortedFiles.length; i++) {
1354
+ const file = sortedFiles[i];
1355
+ scrollPosition += baseItemHeight;
1356
+ if (file.metadata?.description) {
1357
+ scrollPosition += descriptionHeight;
1358
+ }
1359
+ }
1360
+
1361
+ // Add header, controls, search, and stats height (approximately 250px)
1362
+ const headerHeight = 250;
1363
+ const finalScrollPosition = headerHeight + scrollPosition - 150; // Offset to show item near top
1364
+
1365
+ // Use requestAnimationFrame to ensure DOM is updated before scrolling
1366
+ requestAnimationFrame(() => {
1367
+ requestAnimationFrame(() => {
1368
+ scrollViewRef.current?.scrollTo({
1369
+ y: Math.max(0, finalScrollPosition),
1370
+ animated: true
1371
+ });
1372
+ });
1373
+ });
1374
+ }
1375
+ } else if (viewMode === 'photos' && photoScrollViewRef.current) {
1376
+ // For photo grid, find the photo index
1377
+ const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
1378
+ const photoIndex = photos.findIndex(p => p.id === lastSelectedFileId);
1379
+ if (photoIndex >= 0) {
1380
+ // Estimate photo item height based on grid layout
1381
+ // Calculate items per row
1382
+ let itemsPerRow = 3;
1383
+ if (containerWidth > 768) itemsPerRow = 6;else if (containerWidth > 480) itemsPerRow = 4;
1384
+ const scrollContainerPadding = 32;
1385
+ const gaps = (itemsPerRow - 1) * 4;
1386
+ const availableWidth = containerWidth - scrollContainerPadding;
1387
+ const itemWidth = (availableWidth - gaps) / itemsPerRow;
1388
+
1389
+ // Calculate row and approximate scroll position
1390
+ const row = Math.floor(photoIndex / itemsPerRow);
1391
+ const headerHeight = 250;
1392
+ const finalScrollPosition = headerHeight + row * (itemWidth + 4) - 150;
1393
+
1394
+ // Use requestAnimationFrame to ensure DOM is updated before scrolling
1395
+ requestAnimationFrame(() => {
1396
+ requestAnimationFrame(() => {
1397
+ photoScrollViewRef.current?.scrollTo({
1398
+ y: Math.max(0, finalScrollPosition),
1399
+ animated: true
1400
+ });
1401
+ });
1402
+ });
1403
+ }
1404
+ }
1405
+ }
1406
+ }, [lastSelectedFileId, selectMode, viewMode, filteredFiles, containerWidth]);
1407
+
1408
+ // Clear selected file ID after scroll animation completes
1409
+ (0, _react.useEffect)(() => {
1410
+ if (lastSelectedFileId && scrollViewRef.current) {
1411
+ const timeoutId = setTimeout(() => {
1412
+ setLastSelectedFileId(null);
1413
+ }, 600); // Allow time for scroll animation to complete
1414
+
1415
+ return () => clearTimeout(timeoutId);
1416
+ }
1417
+ }, [lastSelectedFileId]);
1159
1418
  const renderPhotoItem = (photo, index) => {
1160
1419
  const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
1161
1420
 
@@ -1186,8 +1445,8 @@ const FileManagementScreen = ({
1186
1445
  contentFit: "cover",
1187
1446
  transition: 120,
1188
1447
  cachePolicy: "memory-disk",
1189
- onError: _ => {
1190
- console.warn('Failed to load image preview for photo:', photo.id);
1448
+ onError: () => {
1449
+ // Image preview failed to load
1191
1450
  },
1192
1451
  accessibilityLabel: photo.filename
1193
1452
  })
@@ -1236,6 +1495,7 @@ const FileManagementScreen = ({
1236
1495
  });
1237
1496
  }
1238
1497
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
1498
+ ref: photoScrollViewRef,
1239
1499
  style: styles.scrollView,
1240
1500
  contentContainerStyle: styles.photoScrollContainer,
1241
1501
  refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, {
@@ -1659,8 +1919,8 @@ const FileManagementScreen = ({
1659
1919
  contentFit: "contain",
1660
1920
  transition: 120,
1661
1921
  cachePolicy: "memory-disk",
1662
- onError: e => {
1663
- console.error('Image failed to load:', e?.nativeEvent ?? e);
1922
+ onError: () => {
1923
+ // Image failed to load
1664
1924
  },
1665
1925
  accessibilityLabel: openedFile.filename
1666
1926
  })
@@ -1801,21 +2061,219 @@ const FileManagementScreen = ({
1801
2061
  })
1802
2062
  })]
1803
2063
  });
1804
- if (loading) {
2064
+
2065
+ // Professional Skeleton Loading Component with Advanced Shimmer Effect
2066
+ const SkeletonLoader = /*#__PURE__*/_react.default.memo(() => {
2067
+ const shimmerAnim = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
2068
+ const skeletonContainerWidth = containerWidth || 400;
2069
+ (0, _react.useEffect)(() => {
2070
+ const shimmer = _reactNative.Animated.loop(_reactNative.Animated.timing(shimmerAnim, {
2071
+ toValue: 1,
2072
+ duration: 2000,
2073
+ easing: _reactNative.Easing.linear,
2074
+ useNativeDriver: true
2075
+ }));
2076
+ shimmer.start();
2077
+ return () => shimmer.stop();
2078
+ }, [shimmerAnim]);
2079
+
2080
+ // Create a sweeping shimmer effect
2081
+ const shimmerTranslateX = shimmerAnim.interpolate({
2082
+ inputRange: [0, 1],
2083
+ outputRange: [-skeletonContainerWidth * 2, skeletonContainerWidth * 2]
2084
+ });
2085
+ const SkeletonBox = ({
2086
+ width,
2087
+ height,
2088
+ borderRadius = 8,
2089
+ style,
2090
+ delay = 0
2091
+ }) => {
2092
+ const delayedTranslateX = shimmerAnim.interpolate({
2093
+ inputRange: [0, 1],
2094
+ outputRange: [-skeletonContainerWidth * 2 + delay, skeletonContainerWidth * 2 + delay]
2095
+ });
2096
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2097
+ style: [{
2098
+ width,
2099
+ height,
2100
+ borderRadius,
2101
+ backgroundColor: themeStyles.isDarkTheme ? '#1E1E1E' : '#F5F5F5',
2102
+ overflow: 'hidden',
2103
+ position: 'relative'
2104
+ }, style],
2105
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2106
+ style: {
2107
+ position: 'absolute',
2108
+ top: 0,
2109
+ left: 0,
2110
+ right: 0,
2111
+ bottom: 0,
2112
+ backgroundColor: themeStyles.isDarkTheme ? '#1E1E1E' : '#F5F5F5'
2113
+ }
2114
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
2115
+ style: {
2116
+ position: 'absolute',
2117
+ top: 0,
2118
+ left: 0,
2119
+ width: '100%',
2120
+ height: '100%',
2121
+ transform: [{
2122
+ translateX: delayedTranslateX
2123
+ }]
2124
+ },
2125
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2126
+ style: {
2127
+ width: skeletonContainerWidth,
2128
+ height: '100%',
2129
+ backgroundColor: themeStyles.isDarkTheme ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.8)',
2130
+ shadowColor: themeStyles.isDarkTheme ? '#000' : '#FFF',
2131
+ shadowOffset: {
2132
+ width: 0,
2133
+ height: 0
2134
+ },
2135
+ shadowOpacity: 0.3,
2136
+ shadowRadius: 10
2137
+ }
2138
+ })
2139
+ })]
2140
+ });
2141
+ };
2142
+
2143
+ // Skeleton file item matching GroupedSection structure
2144
+ const SkeletonFileItem = ({
2145
+ index
2146
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2147
+ style: [{
2148
+ flexDirection: 'row',
2149
+ alignItems: 'center',
2150
+ paddingHorizontal: 16,
2151
+ paddingVertical: 12,
2152
+ backgroundColor: themeStyles.isDarkTheme ? '#121212' : '#FFFFFF',
2153
+ borderBottomWidth: _reactNative.StyleSheet.hairlineWidth,
2154
+ borderBottomColor: themeStyles.borderColor
2155
+ }],
2156
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2157
+ width: 44,
2158
+ height: 44,
2159
+ borderRadius: 8,
2160
+ delay: index * 50
2161
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2162
+ style: {
2163
+ flex: 1,
2164
+ marginLeft: 12,
2165
+ justifyContent: 'center'
2166
+ },
2167
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2168
+ width: index % 3 === 0 ? '85%' : index % 3 === 1 ? '70%' : '90%',
2169
+ height: 16,
2170
+ style: {
2171
+ marginBottom: 8
2172
+ },
2173
+ delay: index * 50 + 20
2174
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2175
+ width: index % 2 === 0 ? '50%' : '60%',
2176
+ height: 12,
2177
+ delay: index * 50 + 40
2178
+ })]
2179
+ })]
2180
+ });
1805
2181
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1806
- style: [styles.container, styles.centerContent, {
2182
+ style: [styles.container, {
1807
2183
  backgroundColor
1808
2184
  }],
1809
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
1810
- size: "large",
1811
- color: themeStyles.primaryColor
1812
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1813
- style: [styles.loadingText, {
1814
- color: themeStyles.textColor
2185
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2186
+ style: [styles.header, {
2187
+ borderBottomColor: themeStyles.borderColor,
2188
+ borderBottomWidth: _reactNative.StyleSheet.hairlineWidth
2189
+ }],
2190
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2191
+ width: 44,
2192
+ height: 44,
2193
+ borderRadius: 12
2194
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2195
+ style: [styles.headerTitleContainer, {
2196
+ flex: 1
2197
+ }],
2198
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2199
+ width: 140,
2200
+ height: 20,
2201
+ style: {
2202
+ marginBottom: 6
2203
+ }
2204
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2205
+ width: 100,
2206
+ height: 14
2207
+ })]
2208
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2209
+ width: 44,
2210
+ height: 44,
2211
+ borderRadius: 12
2212
+ })]
2213
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2214
+ style: styles.controlsBar,
2215
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2216
+ width: 100,
2217
+ height: 36,
2218
+ borderRadius: 18
2219
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2220
+ width: 44,
2221
+ height: 44,
2222
+ borderRadius: 22
2223
+ })]
2224
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2225
+ style: [styles.searchContainer, {
2226
+ backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
2227
+ borderColor: themeStyles.borderColor,
2228
+ borderWidth: _reactNative.StyleSheet.hairlineWidth
2229
+ }],
2230
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2231
+ width: "100%",
2232
+ height: 44,
2233
+ borderRadius: 12
2234
+ })
2235
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2236
+ style: [styles.statsContainer, {
2237
+ backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
2238
+ borderColor: themeStyles.borderColor,
2239
+ borderWidth: _reactNative.StyleSheet.hairlineWidth
1815
2240
  }],
1816
- children: "Loading files..."
2241
+ children: [1, 2, 3].map(i => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2242
+ style: styles.statItem,
2243
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2244
+ width: 50,
2245
+ height: 20,
2246
+ style: {
2247
+ marginBottom: 4
2248
+ },
2249
+ delay: i * 30
2250
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonBox, {
2251
+ width: 40,
2252
+ height: 14,
2253
+ delay: i * 30 + 15
2254
+ })]
2255
+ }, i))
2256
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
2257
+ style: styles.scrollView,
2258
+ contentContainerStyle: styles.scrollContainer,
2259
+ showsVerticalScrollIndicator: false,
2260
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2261
+ style: {
2262
+ backgroundColor: themeStyles.isDarkTheme ? '#121212' : '#FFFFFF',
2263
+ borderRadius: 12,
2264
+ overflow: 'hidden',
2265
+ marginHorizontal: 16,
2266
+ marginTop: 8
2267
+ },
2268
+ children: [1, 2, 3, 4, 5, 6, 7, 8].map(i => /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonFileItem, {
2269
+ index: i
2270
+ }, i))
2271
+ })
1817
2272
  })]
1818
2273
  });
2274
+ });
2275
+ if (loading) {
2276
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(SkeletonLoader, {});
1819
2277
  }
1820
2278
 
1821
2279
  // If a file is opened, show the file viewer
@@ -1825,13 +2283,8 @@ const FileManagementScreen = ({
1825
2283
  });
1826
2284
  }
1827
2285
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2286
+ ref: containerRef,
1828
2287
  style: [styles.container, isDragging && _reactNative.Platform.OS === 'web' && styles.dragOverlay],
1829
- ...(_reactNative.Platform.OS === 'web' && user?.id === targetUserId ? {
1830
- onDragOver: handleDragOver,
1831
- onDragEnter: handleDragEnter,
1832
- onDragLeave: handleDragLeave,
1833
- onDrop: handleDrop
1834
- } : {}),
1835
2288
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Header.default, {
1836
2289
  title: selectMode ? multiSelect ? `${selectedIds.size}${maxSelection ? '/' + maxSelection : ''} Selected` : 'Select a File' : viewMode === 'photos' ? 'Photos' : 'File Management',
1837
2290
  subtitle: selectMode ? multiSelect ? `${filteredFiles.length} available` : 'Tap to select' : `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`,
@@ -1845,6 +2298,35 @@ const FileManagementScreen = ({
1845
2298
  text: 'Confirm',
1846
2299
  onPress: confirmMultiSelection,
1847
2300
  disabled: selectedIds.size === 0
2301
+ }] : !selectMode && selectedIds.size > 0 ? [{
2302
+ key: 'clear',
2303
+ text: 'Clear',
2304
+ onPress: () => setSelectedIds(new Set())
2305
+ }, {
2306
+ key: 'delete',
2307
+ text: `Delete (${selectedIds.size})`,
2308
+ onPress: handleBulkDelete,
2309
+ icon: 'trash'
2310
+ }, {
2311
+ key: 'visibility',
2312
+ text: 'Visibility',
2313
+ onPress: () => {
2314
+ // Show visibility options menu
2315
+ _reactNative.Alert.alert('Change Visibility', `Change visibility for ${selectedIds.size} file(s)?`, [{
2316
+ text: 'Cancel',
2317
+ style: 'cancel'
2318
+ }, {
2319
+ text: 'Private',
2320
+ onPress: () => handleBulkVisibilityChange('private')
2321
+ }, {
2322
+ text: 'Public',
2323
+ onPress: () => handleBulkVisibilityChange('public')
2324
+ }, {
2325
+ text: 'Unlisted',
2326
+ onPress: () => handleBulkVisibilityChange('unlisted')
2327
+ }]);
2328
+ },
2329
+ icon: 'eye'
1848
2330
  }] : undefined,
1849
2331
  onBack: onClose || goBack,
1850
2332
  theme: theme,
@@ -1854,32 +2336,95 @@ const FileManagementScreen = ({
1854
2336
  titleAlignment: "left"
1855
2337
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1856
2338
  style: styles.controlsBar,
1857
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1858
- style: [styles.viewModeToggle, {
2339
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
2340
+ horizontal: true,
2341
+ showsHorizontalScrollIndicator: false,
2342
+ style: styles.viewModeScroll,
2343
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2344
+ style: [styles.viewModeToggle, {
2345
+ backgroundColor: themeStyles.isDarkTheme ? '#181818' : '#FFFFFF',
2346
+ borderWidth: 1,
2347
+ borderColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#E8E9EA'
2348
+ }],
2349
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
2350
+ style: [styles.viewModeButton, viewMode === 'all' && {
2351
+ backgroundColor: themeStyles.primaryColor
2352
+ }],
2353
+ onPress: () => setViewMode('all'),
2354
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2355
+ name: "folder",
2356
+ size: 18,
2357
+ color: viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor
2358
+ })
2359
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
2360
+ style: [styles.viewModeButton, viewMode === 'photos' && {
2361
+ backgroundColor: themeStyles.primaryColor
2362
+ }],
2363
+ onPress: () => setViewMode('photos'),
2364
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2365
+ name: "images",
2366
+ size: 18,
2367
+ color: viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor
2368
+ })
2369
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
2370
+ style: [styles.viewModeButton, viewMode === 'videos' && {
2371
+ backgroundColor: themeStyles.primaryColor
2372
+ }],
2373
+ onPress: () => setViewMode('videos'),
2374
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2375
+ name: "videocam",
2376
+ size: 18,
2377
+ color: viewMode === 'videos' ? '#FFFFFF' : themeStyles.textColor
2378
+ })
2379
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
2380
+ style: [styles.viewModeButton, viewMode === 'documents' && {
2381
+ backgroundColor: themeStyles.primaryColor
2382
+ }],
2383
+ onPress: () => setViewMode('documents'),
2384
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2385
+ name: "document-text",
2386
+ size: 18,
2387
+ color: viewMode === 'documents' ? '#FFFFFF' : themeStyles.textColor
2388
+ })
2389
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
2390
+ style: [styles.viewModeButton, viewMode === 'audio' && {
2391
+ backgroundColor: themeStyles.primaryColor
2392
+ }],
2393
+ onPress: () => setViewMode('audio'),
2394
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2395
+ name: "musical-notes",
2396
+ size: 18,
2397
+ color: viewMode === 'audio' ? '#FFFFFF' : themeStyles.textColor
2398
+ })
2399
+ })]
2400
+ })
2401
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
2402
+ style: [styles.sortButton, {
1859
2403
  backgroundColor: themeStyles.isDarkTheme ? '#181818' : '#FFFFFF',
1860
- borderWidth: 1,
1861
2404
  borderColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#E8E9EA'
1862
2405
  }],
1863
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1864
- style: [styles.viewModeButton, viewMode === 'all' && {
1865
- backgroundColor: themeStyles.primaryColor
1866
- }],
1867
- onPress: () => setViewMode('all'),
1868
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1869
- name: "folder",
1870
- size: 18,
1871
- color: viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor
1872
- })
1873
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1874
- style: [styles.viewModeButton, viewMode === 'photos' && {
1875
- backgroundColor: themeStyles.primaryColor
1876
- }],
1877
- onPress: () => setViewMode('photos'),
1878
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
1879
- name: "images",
1880
- size: 18,
1881
- color: viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor
1882
- })
2406
+ onPress: () => {
2407
+ // Cycle through sort options: date -> size -> name -> type -> date
2408
+ const sortOrder = ['date', 'size', 'name', 'type'];
2409
+ const currentIndex = sortOrder.indexOf(sortBy);
2410
+ const nextIndex = (currentIndex + 1) % sortOrder.length;
2411
+ setSortBy(sortOrder[nextIndex]);
2412
+ // Toggle order when cycling back to date
2413
+ if (nextIndex === 0) {
2414
+ setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc');
2415
+ }
2416
+ },
2417
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2418
+ name: sortOrder === 'asc' ? 'arrow-up' : 'arrow-down',
2419
+ size: 18,
2420
+ color: themeStyles.textColor
2421
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2422
+ name: sortBy === 'date' ? 'calendar' : sortBy === 'size' ? 'resize' : sortBy === 'name' ? 'text' : 'document',
2423
+ size: 16,
2424
+ color: themeStyles.textColor,
2425
+ style: {
2426
+ marginLeft: 4
2427
+ }
1883
2428
  })]
1884
2429
  }), user?.id === targetUserId && (!selectMode || selectMode && allowUploadInSelectMode) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1885
2430
  style: [styles.uploadButton, {
@@ -1974,6 +2519,7 @@ const FileManagementScreen = ({
1974
2519
  })]
1975
2520
  })]
1976
2521
  }), viewMode === 'photos' ? renderPhotoGrid() : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
2522
+ ref: scrollViewRef,
1977
2523
  style: styles.scrollView,
1978
2524
  contentContainerStyle: styles.scrollContainer,
1979
2525
  refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, {
@@ -2072,21 +2618,29 @@ const FileManagementScreen = ({
2072
2618
  }), isDragging && _reactNative.Platform.OS === 'web' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2073
2619
  style: styles.dragDropOverlay,
2074
2620
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
2075
- style: styles.dragDropContent,
2076
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2077
- name: "cloud-upload",
2078
- size: 64,
2079
- color: themeStyles.primaryColor
2621
+ style: [styles.dragDropContent, {
2622
+ backgroundColor: themeStyles.isDarkTheme ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)',
2623
+ borderColor: themeStyles.primaryColor
2624
+ }],
2625
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
2626
+ style: [styles.dragDropIconContainer, {
2627
+ backgroundColor: `${themeStyles.primaryColor}15`
2628
+ }],
2629
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
2630
+ name: "cloud-upload",
2631
+ size: 64,
2632
+ color: themeStyles.primaryColor
2633
+ })
2080
2634
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
2081
2635
  style: [styles.dragDropTitle, {
2082
2636
  color: themeStyles.primaryColor
2083
2637
  }],
2084
2638
  children: "Drop files to upload"
2085
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
2639
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
2086
2640
  style: [styles.dragDropSubtitle, {
2087
2641
  color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'
2088
2642
  }],
2089
- children: ["Release to upload", uploadProgress ? ` (${uploadProgress.current}/${uploadProgress.total})` : ' multiple files']
2643
+ children: "Release to upload multiple files"
2090
2644
  })]
2091
2645
  })
2092
2646
  })]
@@ -2585,36 +3139,154 @@ const styles = _reactNative.StyleSheet.create({
2585
3139
  fontSize: 16,
2586
3140
  fontWeight: '600'
2587
3141
  },
2588
- // Drag and Drop styles
3142
+ // Drag and Drop styles - Enhanced
2589
3143
  dragDropOverlay: {
2590
3144
  position: 'absolute',
2591
3145
  top: 0,
2592
3146
  left: 0,
2593
3147
  right: 0,
2594
3148
  bottom: 0,
2595
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
3149
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
2596
3150
  justifyContent: 'center',
2597
3151
  alignItems: 'center',
2598
3152
  zIndex: 1000
2599
3153
  },
2600
3154
  dragDropContent: {
2601
3155
  alignItems: 'center',
2602
- backgroundColor: 'rgba(255, 255, 255, 0.95)',
2603
- padding: 20,
2604
- borderRadius: 14,
2605
- borderWidth: 1,
2606
- borderColor: '#66AFFF',
2607
- borderStyle: 'dashed'
3156
+ padding: 32,
3157
+ borderRadius: 20,
3158
+ borderWidth: 3,
3159
+ borderStyle: 'dashed',
3160
+ minWidth: 280,
3161
+ shadowColor: '#000',
3162
+ shadowOpacity: 0.3,
3163
+ shadowRadius: 20,
3164
+ shadowOffset: {
3165
+ width: 0,
3166
+ height: 10
3167
+ },
3168
+ elevation: 10
3169
+ },
3170
+ dragDropIconContainer: {
3171
+ padding: 24,
3172
+ borderRadius: 50,
3173
+ marginBottom: 16
2608
3174
  },
2609
3175
  dragDropTitle: {
2610
- fontSize: 20,
2611
- fontWeight: 'bold',
2612
- marginTop: 12,
2613
- marginBottom: 6
3176
+ fontSize: 24,
3177
+ fontWeight: '700',
3178
+ fontFamily: _fonts.fontFamilies.phuduBold,
3179
+ marginBottom: 8
2614
3180
  },
2615
3181
  dragDropSubtitle: {
2616
3182
  fontSize: 16,
2617
- textAlign: 'center'
3183
+ fontWeight: '500',
3184
+ fontFamily: _fonts.fontFamilies.phuduMedium
3185
+ },
3186
+ // Upload Preview Modal styles
3187
+ uploadPreviewContainer: {
3188
+ flex: 1
3189
+ },
3190
+ uploadPreviewHeader: {
3191
+ flexDirection: 'row',
3192
+ alignItems: 'center',
3193
+ justifyContent: 'space-between',
3194
+ paddingHorizontal: 16,
3195
+ paddingVertical: 16,
3196
+ borderBottomWidth: 1
3197
+ },
3198
+ uploadPreviewTitle: {
3199
+ fontSize: 20,
3200
+ fontWeight: '700',
3201
+ fontFamily: _fonts.fontFamilies.phuduBold
3202
+ },
3203
+ uploadPreviewList: {
3204
+ flex: 1,
3205
+ padding: 16
3206
+ },
3207
+ uploadPreviewItem: {
3208
+ flexDirection: 'row',
3209
+ alignItems: 'center',
3210
+ padding: 12,
3211
+ borderRadius: 12,
3212
+ borderWidth: 1,
3213
+ marginBottom: 12,
3214
+ gap: 12
3215
+ },
3216
+ uploadPreviewThumbnail: {
3217
+ width: 60,
3218
+ height: 60,
3219
+ borderRadius: 8
3220
+ },
3221
+ uploadPreviewIconContainer: {
3222
+ width: 60,
3223
+ height: 60,
3224
+ borderRadius: 8,
3225
+ alignItems: 'center',
3226
+ justifyContent: 'center'
3227
+ },
3228
+ uploadPreviewInfo: {
3229
+ flex: 1,
3230
+ minWidth: 0
3231
+ },
3232
+ uploadPreviewName: {
3233
+ fontSize: 16,
3234
+ fontWeight: '600',
3235
+ fontFamily: _fonts.fontFamilies.phuduSemiBold,
3236
+ marginBottom: 4
3237
+ },
3238
+ uploadPreviewMeta: {
3239
+ fontSize: 13,
3240
+ fontFamily: _fonts.fontFamilies.phudu
3241
+ },
3242
+ uploadPreviewRemove: {
3243
+ padding: 4
3244
+ },
3245
+ uploadPreviewFooter: {
3246
+ padding: 16,
3247
+ borderTopWidth: 1
3248
+ },
3249
+ uploadPreviewStats: {
3250
+ flexDirection: 'row',
3251
+ justifyContent: 'space-between',
3252
+ marginBottom: 16
3253
+ },
3254
+ uploadPreviewStatsText: {
3255
+ fontSize: 15,
3256
+ fontWeight: '600',
3257
+ fontFamily: _fonts.fontFamilies.phuduSemiBold
3258
+ },
3259
+ uploadPreviewActions: {
3260
+ flexDirection: 'row',
3261
+ gap: 12
3262
+ },
3263
+ uploadPreviewCancelButton: {
3264
+ flex: 1,
3265
+ paddingVertical: 14,
3266
+ borderRadius: 12,
3267
+ borderWidth: 1,
3268
+ alignItems: 'center',
3269
+ justifyContent: 'center'
3270
+ },
3271
+ uploadPreviewCancelText: {
3272
+ fontSize: 16,
3273
+ fontWeight: '600',
3274
+ fontFamily: _fonts.fontFamilies.phuduSemiBold
3275
+ },
3276
+ uploadPreviewConfirmButton: {
3277
+ flex: 2,
3278
+ flexDirection: 'row',
3279
+ alignItems: 'center',
3280
+ justifyContent: 'center',
3281
+ paddingVertical: 14,
3282
+ borderRadius: 12,
3283
+ gap: 8
3284
+ },
3285
+ uploadPreviewConfirmText: {
3286
+ color: '#FFFFFF',
3287
+ fontSize: 16,
3288
+ fontWeight: '600',
3289
+ fontFamily: _fonts.fontFamilies.phuduSemiBold
2618
3290
  },
2619
3291
  // File Viewer styles
2620
3292
  fileViewerContainer: {
@@ -2788,6 +3460,10 @@ const styles = _reactNative.StyleSheet.create({
2788
3460
  paddingBottom: 4,
2789
3461
  gap: 12
2790
3462
  },
3463
+ viewModeScroll: {
3464
+ flex: 1,
3465
+ maxWidth: '80%'
3466
+ },
2791
3467
  viewModeToggle: {
2792
3468
  flexDirection: 'row',
2793
3469
  borderRadius: 24,
@@ -2803,6 +3479,16 @@ const styles = _reactNative.StyleSheet.create({
2803
3479
  justifyContent: 'center',
2804
3480
  marginHorizontal: 1
2805
3481
  },
3482
+ sortButton: {
3483
+ flexDirection: 'row',
3484
+ alignItems: 'center',
3485
+ justifyContent: 'center',
3486
+ paddingHorizontal: 12,
3487
+ paddingVertical: 10,
3488
+ borderRadius: 20,
3489
+ borderWidth: 1,
3490
+ minWidth: 44
3491
+ },
2806
3492
  // Photo Grid styles
2807
3493
  photoScrollContainer: {
2808
3494
  padding: 10
@@ -2904,6 +3590,18 @@ const styles = _reactNative.StyleSheet.create({
2904
3590
  aspectRatio: 1,
2905
3591
  borderRadius: 8,
2906
3592
  marginBottom: 4
3593
+ },
3594
+ skeletonFileItem: {
3595
+ flexDirection: 'row',
3596
+ alignItems: 'center',
3597
+ paddingHorizontal: 16,
3598
+ paddingVertical: 12,
3599
+ borderBottomWidth: 1,
3600
+ gap: 12
3601
+ },
3602
+ skeletonFileInfo: {
3603
+ flex: 1,
3604
+ justifyContent: 'center'
2907
3605
  }
2908
3606
  });
2909
3607
  var _default = exports.default = FileManagementScreen;