@oxyhq/services 5.4.8 → 5.5.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 (154) hide show
  1. package/lib/commonjs/core/index.js +0 -59
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/index.js +174 -17
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/ui/components/FollowButton.js +8 -23
  6. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  7. package/lib/commonjs/ui/components/OxyProvider.js +49 -38
  8. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxySignInButton.js +2 -8
  10. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/index.js +15 -2
  12. package/lib/commonjs/ui/hooks/index.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/useAuthFetch.js +182 -0
  14. package/lib/commonjs/ui/hooks/useAuthFetch.js.map +1 -0
  15. package/lib/commonjs/ui/hooks/useFollow.js +10 -29
  16. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/useOxyFollow.js +190 -0
  18. package/lib/commonjs/ui/hooks/useOxyFollow.js.map +1 -0
  19. package/lib/commonjs/ui/index.js +183 -0
  20. package/lib/commonjs/ui/index.js.map +1 -1
  21. package/lib/commonjs/ui/screens/AccountCenterScreen.js +18 -14
  22. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  23. package/lib/commonjs/ui/screens/AppInfoScreen.js +37 -19
  24. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  25. package/lib/commonjs/ui/screens/FileManagementScreen.js +27 -9
  26. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  27. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +2 -8
  28. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
  29. package/lib/commonjs/ui/store/index.js +51 -255
  30. package/lib/commonjs/ui/store/index.js.map +1 -1
  31. package/lib/commonjs/ui/store/setupOxyStore.js +63 -0
  32. package/lib/commonjs/ui/store/setupOxyStore.js.map +1 -0
  33. package/lib/commonjs/ui/store/slices/authSlice.js +56 -0
  34. package/lib/commonjs/ui/store/slices/authSlice.js.map +1 -0
  35. package/lib/commonjs/ui/store/slices/followSlice.js +238 -0
  36. package/lib/commonjs/ui/store/slices/followSlice.js.map +1 -0
  37. package/lib/commonjs/ui/store/slices/index.js +129 -0
  38. package/lib/commonjs/ui/store/slices/index.js.map +1 -0
  39. package/lib/commonjs/ui/store/slices/types.js +19 -0
  40. package/lib/commonjs/ui/store/slices/types.js.map +1 -0
  41. package/lib/commonjs/ui/styles/index.js +11 -0
  42. package/lib/commonjs/ui/styles/index.js.map +1 -1
  43. package/lib/commonjs/ui/styles/shadows.js +123 -0
  44. package/lib/commonjs/ui/styles/shadows.js.map +1 -0
  45. package/lib/module/core/index.js +0 -59
  46. package/lib/module/core/index.js.map +1 -1
  47. package/lib/module/index.js +14 -10
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/ui/components/FollowButton.js +8 -23
  50. package/lib/module/ui/components/FollowButton.js.map +1 -1
  51. package/lib/module/ui/components/OxyProvider.js +49 -38
  52. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  53. package/lib/module/ui/components/OxySignInButton.js +2 -8
  54. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  55. package/lib/module/ui/hooks/index.js +2 -1
  56. package/lib/module/ui/hooks/index.js.map +1 -1
  57. package/lib/module/ui/hooks/useAuthFetch.js +177 -0
  58. package/lib/module/ui/hooks/useAuthFetch.js.map +1 -0
  59. package/lib/module/ui/hooks/useFollow.js +10 -29
  60. package/lib/module/ui/hooks/useFollow.js.map +1 -1
  61. package/lib/module/ui/hooks/useOxyFollow.js +186 -0
  62. package/lib/module/ui/hooks/useOxyFollow.js.map +1 -0
  63. package/lib/module/ui/index.js +12 -2
  64. package/lib/module/ui/index.js.map +1 -1
  65. package/lib/module/ui/screens/AccountCenterScreen.js +5 -1
  66. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  67. package/lib/module/ui/screens/AppInfoScreen.js +37 -19
  68. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  69. package/lib/module/ui/screens/FileManagementScreen.js +27 -9
  70. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  71. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +2 -8
  72. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
  73. package/lib/module/ui/store/index.js +23 -249
  74. package/lib/module/ui/store/index.js.map +1 -1
  75. package/lib/module/ui/store/setupOxyStore.js +59 -0
  76. package/lib/module/ui/store/setupOxyStore.js.map +1 -0
  77. package/lib/module/ui/store/slices/authSlice.js +48 -0
  78. package/lib/module/ui/store/slices/authSlice.js.map +1 -0
  79. package/lib/module/ui/store/slices/followSlice.js +232 -0
  80. package/lib/module/ui/store/slices/followSlice.js.map +1 -0
  81. package/lib/module/ui/store/slices/index.js +11 -0
  82. package/lib/module/ui/store/slices/index.js.map +1 -0
  83. package/lib/module/ui/store/slices/types.js +15 -0
  84. package/lib/module/ui/store/slices/types.js.map +1 -0
  85. package/lib/module/ui/styles/index.js +1 -0
  86. package/lib/module/ui/styles/index.js.map +1 -1
  87. package/lib/module/ui/styles/shadows.js +119 -0
  88. package/lib/module/ui/styles/shadows.js.map +1 -0
  89. package/lib/typescript/core/index.d.ts +0 -28
  90. package/lib/typescript/core/index.d.ts.map +1 -1
  91. package/lib/typescript/index.d.ts +3 -5
  92. package/lib/typescript/index.d.ts.map +1 -1
  93. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  94. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  95. package/lib/typescript/ui/components/OxySignInButton.d.ts.map +1 -1
  96. package/lib/typescript/ui/hooks/index.d.ts +2 -1
  97. package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
  98. package/lib/typescript/ui/hooks/useAuthFetch.d.ts +33 -0
  99. package/lib/typescript/ui/hooks/useAuthFetch.d.ts.map +1 -0
  100. package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -1
  101. package/lib/typescript/ui/hooks/useOxyFollow.d.ts +81 -0
  102. package/lib/typescript/ui/hooks/useOxyFollow.d.ts.map +1 -0
  103. package/lib/typescript/ui/index.d.ts +3 -1
  104. package/lib/typescript/ui/index.d.ts.map +1 -1
  105. package/lib/typescript/ui/navigation/types.d.ts +22 -4
  106. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  107. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  108. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  109. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  110. package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -1
  111. package/lib/typescript/ui/store/index.d.ts +19 -58
  112. package/lib/typescript/ui/store/index.d.ts.map +1 -1
  113. package/lib/typescript/ui/store/setupOxyStore.d.ts +29 -0
  114. package/lib/typescript/ui/store/setupOxyStore.d.ts.map +1 -0
  115. package/lib/typescript/ui/store/slices/authSlice.d.ts +32 -0
  116. package/lib/typescript/ui/store/slices/authSlice.d.ts.map +1 -0
  117. package/lib/typescript/ui/store/slices/followSlice.d.ts +120 -0
  118. package/lib/typescript/ui/store/slices/followSlice.d.ts.map +1 -0
  119. package/lib/typescript/ui/store/slices/index.d.ts +9 -0
  120. package/lib/typescript/ui/store/slices/index.d.ts.map +1 -0
  121. package/lib/typescript/ui/store/slices/types.d.ts +16 -0
  122. package/lib/typescript/ui/store/slices/types.d.ts.map +1 -0
  123. package/lib/typescript/ui/styles/index.d.ts +1 -0
  124. package/lib/typescript/ui/styles/index.d.ts.map +1 -1
  125. package/lib/typescript/ui/styles/shadows.d.ts +233 -0
  126. package/lib/typescript/ui/styles/shadows.d.ts.map +1 -0
  127. package/package.json +14 -15
  128. package/src/__tests__/ui/hooks/useOxyFollow.test.tsx +92 -0
  129. package/src/__tests__/ui/store/setupOxyStore.test.ts +50 -0
  130. package/src/__tests__/validate-structure.js +91 -0
  131. package/src/__tests__/validation.js +42 -0
  132. package/src/core/index.ts +0 -66
  133. package/src/index.ts +36 -4
  134. package/src/ui/components/FollowButton.tsx +11 -25
  135. package/src/ui/components/OxyProvider.tsx +48 -33
  136. package/src/ui/components/OxySignInButton.tsx +2 -6
  137. package/src/ui/hooks/index.ts +2 -1
  138. package/src/ui/hooks/useAuthFetch.ts +200 -0
  139. package/src/ui/hooks/useFollow.ts +10 -30
  140. package/src/ui/hooks/useOxyFollow.ts +188 -0
  141. package/src/ui/index.ts +34 -2
  142. package/src/ui/navigation/types.ts +24 -4
  143. package/src/ui/screens/AccountCenterScreen.tsx +5 -7
  144. package/src/ui/screens/AppInfoScreen.tsx +40 -23
  145. package/src/ui/screens/FileManagementScreen.tsx +268 -248
  146. package/src/ui/screens/karma/KarmaRewardsScreen.tsx +2 -5
  147. package/src/ui/store/index.ts +31 -245
  148. package/src/ui/store/setupOxyStore.ts +58 -0
  149. package/src/ui/store/slices/authSlice.ts +43 -0
  150. package/src/ui/store/slices/followSlice.ts +207 -0
  151. package/src/ui/store/slices/index.ts +31 -0
  152. package/src/ui/store/slices/types.ts +33 -0
  153. package/src/ui/styles/index.ts +1 -0
  154. package/src/ui/styles/shadows.ts +112 -0
@@ -33,7 +33,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
33
33
  containerWidth = 400, // Fallback for when not provided by the router
34
34
  }) => {
35
35
  const { user, oxyServices } = useOxy();
36
-
36
+
37
37
  // Debug: log the actual container width
38
38
  useEffect(() => {
39
39
  console.log('[FileManagementScreen] Container width (full):', containerWidth);
@@ -49,7 +49,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
49
49
  const [loading, setLoading] = useState(true);
50
50
  const [refreshing, setRefreshing] = useState(false);
51
51
  const [uploading, setUploading] = useState(false);
52
- const [uploadProgress, setUploadProgress] = useState<{ current: number, total: number } | null>(null);
52
+ const [uploadProgress, setUploadProgress] = useState<{current: number, total: number} | null>(null);
53
53
  const [deleting, setDeleting] = useState<string | null>(null);
54
54
  const [selectedFile, setSelectedFile] = useState<FileMetadata | null>(null);
55
55
  const [showFileDetails, setShowFileDetails] = useState(false);
@@ -61,7 +61,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
61
61
  const [searchQuery, setSearchQuery] = useState('');
62
62
  const [filteredFiles, setFilteredFiles] = useState<FileMetadata[]>([]);
63
63
  const [isDragging, setIsDragging] = useState(false);
64
- const [photoDimensions, setPhotoDimensions] = useState<{ [key: string]: { width: number, height: number } }>({});
64
+ const [photoDimensions, setPhotoDimensions] = useState<{[key: string]: {width: number, height: number}}>({});
65
65
  const [loadingDimensions, setLoadingDimensions] = useState(false);
66
66
  const [hoveredPreview, setHoveredPreview] = useState<string | null>(null);
67
67
 
@@ -110,18 +110,18 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
110
110
  // Filter files based on search query and view mode
111
111
  useEffect(() => {
112
112
  let filteredByMode = files;
113
-
113
+
114
114
  // Filter by view mode first
115
115
  if (viewMode === 'photos') {
116
116
  filteredByMode = files.filter(file => file.contentType.startsWith('image/'));
117
117
  }
118
-
118
+
119
119
  // Then filter by search query
120
120
  if (!searchQuery.trim()) {
121
121
  setFilteredFiles(filteredByMode);
122
122
  } else {
123
123
  const query = searchQuery.toLowerCase();
124
- const filtered = filteredByMode.filter(file =>
124
+ const filtered = filteredByMode.filter(file =>
125
125
  file.filename.toLowerCase().includes(query) ||
126
126
  file.contentType.toLowerCase().includes(query) ||
127
127
  (file.metadata?.description && file.metadata.description.toLowerCase().includes(query))
@@ -133,14 +133,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
133
133
  // Load photo dimensions for justified grid
134
134
  const loadPhotoDimensions = useCallback(async (photos: FileMetadata[]) => {
135
135
  if (photos.length === 0) return;
136
-
136
+
137
137
  setLoadingDimensions(true);
138
- const newDimensions: { [key: string]: { width: number, height: number } } = { ...photoDimensions };
138
+ const newDimensions: {[key: string]: {width: number, height: number}} = { ...photoDimensions };
139
139
  let hasNewDimensions = false;
140
140
 
141
141
  // Only load dimensions for photos we don't have yet
142
142
  const photosToLoad = photos.filter(photo => !newDimensions[photo.id]);
143
-
143
+
144
144
  if (photosToLoad.length === 0) {
145
145
  setLoadingDimensions(false);
146
146
  return;
@@ -151,7 +151,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
151
151
  photosToLoad.map(async (photo) => {
152
152
  try {
153
153
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
154
-
154
+
155
155
  if (Platform.OS === 'web') {
156
156
  const img = new (window as any).Image();
157
157
  await new Promise<void>((resolve, reject) => {
@@ -211,15 +211,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
211
211
  // Create justified rows from photos with responsive algorithm
212
212
  const createJustifiedRows = useCallback((photos: FileMetadata[], containerWidth: number) => {
213
213
  if (photos.length === 0) return [];
214
-
214
+
215
215
  const rows: FileMetadata[][] = [];
216
216
  const photosPerRow = 3; // Fixed 3 photos per row for consistency
217
-
217
+
218
218
  for (let i = 0; i < photos.length; i += photosPerRow) {
219
219
  const rowPhotos = photos.slice(i, i + photosPerRow);
220
220
  rows.push(rowPhotos);
221
221
  }
222
-
222
+
223
223
  return rows;
224
224
  }, []);
225
225
 
@@ -229,11 +229,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
229
229
  try {
230
230
  // Show initial progress
231
231
  setUploadProgress({ current: 0, total: selectedFiles.length });
232
-
232
+
233
233
  // Validate file sizes (example: 50MB limit per file)
234
234
  const maxSize = 50 * 1024 * 1024; // 50MB
235
235
  const oversizedFiles = selectedFiles.filter(file => file.size > maxSize);
236
-
236
+
237
237
  if (oversizedFiles.length > 0) {
238
238
  const fileList = oversizedFiles.map(f => f.name).join('\n');
239
239
  window.alert(`File Size Limit\n\nThe following files are too large (max 50MB):\n${fileList}`);
@@ -244,8 +244,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
244
244
  if (selectedFiles.length <= 5) {
245
245
  const filenames = selectedFiles.map(f => f.name);
246
246
  const response = await oxyServices.uploadFiles(
247
- selectedFiles,
248
- filenames,
247
+ selectedFiles,
248
+ filenames,
249
249
  {
250
250
  userId: targetUserId,
251
251
  uploadDate: new Date().toISOString(),
@@ -283,7 +283,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
283
283
  if (successCount > 0) {
284
284
  toast.success(`${successCount} file(s) uploaded successfully`);
285
285
  }
286
-
286
+
287
287
  if (failureCount > 0) {
288
288
  const errorMessage = `${failureCount} file(s) failed to upload${errors.length > 0 ? ':\n' + errors.slice(0, 3).join('\n') + (errors.length > 3 ? '\n...' : '') : ''}`;
289
289
  toast.error(errorMessage);
@@ -313,7 +313,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
313
313
  input.type = 'file';
314
314
  input.multiple = true;
315
315
  input.accept = '*/*';
316
-
316
+
317
317
  input.onchange = async (e: any) => {
318
318
  const selectedFiles = Array.from(e.target.files) as File[];
319
319
  await processFileUploads(selectedFiles);
@@ -324,7 +324,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
324
324
  // Mobile - show info that file picker can be added
325
325
  const installCommand = 'npm install expo-document-picker';
326
326
  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.`;
327
-
327
+
328
328
  if (window.confirm(`${message}\n\nWould you like to copy the install command?`)) {
329
329
  toast.info(`Install: ${installCommand}`);
330
330
  } else {
@@ -342,7 +342,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
342
342
  const handleFileDelete = async (fileId: string, filename: string) => {
343
343
  // Use web-compatible confirmation dialog
344
344
  const confirmed = window.confirm(`Are you sure you want to delete "${filename}"? This action cannot be undone.`);
345
-
345
+
346
346
  if (!confirmed) {
347
347
  console.log('Delete cancelled by user');
348
348
  return;
@@ -353,12 +353,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
353
353
  console.log('Target user ID:', targetUserId);
354
354
  console.log('Current user ID:', user?.id);
355
355
  setDeleting(fileId);
356
-
356
+
357
357
  const result = await oxyServices.deleteFile(fileId);
358
358
  console.log('Delete result:', result);
359
-
359
+
360
360
  toast.success('File deleted successfully');
361
-
361
+
362
362
  // Reload files after successful deletion
363
363
  setTimeout(async () => {
364
364
  await loadFiles();
@@ -366,7 +366,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
366
366
  } catch (error: any) {
367
367
  console.error('Delete error:', error);
368
368
  console.error('Error details:', error.response?.data || error.message);
369
-
369
+
370
370
  // Provide specific error messages
371
371
  if (error.message?.includes('File not found') || error.message?.includes('404')) {
372
372
  toast.error('File not found. It may have already been deleted.');
@@ -420,11 +420,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
420
420
  try {
421
421
  if (Platform.OS === 'web') {
422
422
  console.log('Downloading file:', { fileId, filename });
423
-
423
+
424
424
  // Use the public download URL method
425
425
  const downloadUrl = oxyServices.getFileDownloadUrl(fileId);
426
426
  console.log('Download URL:', downloadUrl);
427
-
427
+
428
428
  try {
429
429
  // Method 1: Try simple link download first
430
430
  const link = document.createElement('a');
@@ -434,25 +434,34 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
434
434
  document.body.appendChild(link);
435
435
  link.click();
436
436
  document.body.removeChild(link);
437
-
437
+
438
438
  toast.success('File download started');
439
439
  } catch (linkError) {
440
440
  console.warn('Link download failed, trying fetch method:', linkError);
441
-
442
- // Method 2: Fallback to authenticated download
443
- const blob = await oxyServices.getFileContentAsBlob(fileId);
441
+
442
+ // Method 2: Fallback to fetch download
443
+ const response = await fetch(downloadUrl);
444
+ if (!response.ok) {
445
+ if (response.status === 404) {
446
+ throw new Error('File not found. It may have been deleted.');
447
+ } else {
448
+ throw new Error(`Download failed: ${response.status} ${response.statusText}`);
449
+ }
450
+ }
451
+
452
+ const blob = await response.blob();
444
453
  const url = window.URL.createObjectURL(blob);
445
-
454
+
446
455
  const link = document.createElement('a');
447
456
  link.href = url;
448
457
  link.download = filename;
449
458
  document.body.appendChild(link);
450
459
  link.click();
451
460
  document.body.removeChild(link);
452
-
461
+
453
462
  // Clean up the blob URL
454
463
  window.URL.revokeObjectURL(url);
455
-
464
+
456
465
  toast.success('File downloaded successfully');
457
466
  }
458
467
  } else {
@@ -487,10 +496,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
487
496
  try {
488
497
  setLoadingFileContent(true);
489
498
  setOpenedFile(file);
490
-
499
+
491
500
  // For text files, images, and other viewable content, try to load the content
492
- if (file.contentType.startsWith('text/') ||
493
- file.contentType.includes('json') ||
501
+ if (file.contentType.startsWith('text/') ||
502
+ file.contentType.includes('json') ||
494
503
  file.contentType.includes('xml') ||
495
504
  file.contentType.includes('javascript') ||
496
505
  file.contentType.includes('typescript') ||
@@ -498,19 +507,30 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
498
507
  file.contentType.includes('pdf') ||
499
508
  file.contentType.startsWith('video/') ||
500
509
  file.contentType.startsWith('audio/')) {
501
-
510
+
502
511
  try {
503
- if (file.contentType.startsWith('image/') ||
504
- file.contentType.includes('pdf') ||
505
- file.contentType.startsWith('video/') ||
506
- file.contentType.startsWith('audio/')) {
507
- // For images, PDFs, videos, and audio, we'll use the URL directly
508
- const downloadUrl = oxyServices.getFileDownloadUrl(file.id);
509
- setFileContent(downloadUrl);
512
+ const downloadUrl = oxyServices.getFileDownloadUrl(file.id);
513
+ const response = await fetch(downloadUrl);
514
+
515
+ if (response.ok) {
516
+ if (file.contentType.startsWith('image/') ||
517
+ file.contentType.includes('pdf') ||
518
+ file.contentType.startsWith('video/') ||
519
+ file.contentType.startsWith('audio/')) {
520
+ // For images, PDFs, videos, and audio, we'll use the URL directly
521
+ setFileContent(downloadUrl);
522
+ } else {
523
+ // For text files, get the content
524
+ const content = await response.text();
525
+ setFileContent(content);
526
+ }
510
527
  } else {
511
- // For text files, get the content using authenticated request
512
- const content = await oxyServices.getFileContentAsText(file.id);
513
- setFileContent(content);
528
+ if (response.status === 404) {
529
+ toast.error('File not found. It may have been deleted.');
530
+ } else {
531
+ toast.error(`Failed to load file: ${response.status} ${response.statusText}`);
532
+ }
533
+ setFileContent(null);
514
534
  }
515
535
  } catch (error: any) {
516
536
  console.error('Failed to load file content:', error);
@@ -547,18 +567,18 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
547
567
 
548
568
  const renderSimplePhotoItem = useCallback((photo: FileMetadata, index: number) => {
549
569
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
550
-
570
+
551
571
  // Calculate photo item width based on actual container size from bottom sheet
552
572
  let itemsPerRow = 3; // Default for mobile
553
573
  if (containerWidth > 768) itemsPerRow = 4; // Desktop/tablet
554
574
  else if (containerWidth > 480) itemsPerRow = 3; // Large mobile
555
-
575
+
556
576
  // Account for the photoScrollContainer padding (16px on each side = 32px total)
557
577
  const scrollContainerPadding = 32; // Total horizontal padding from photoScrollContainer
558
578
  const gaps = (itemsPerRow - 1) * 4; // Gap between items (4px)
559
579
  const availableWidth = containerWidth - scrollContainerPadding;
560
580
  const itemWidth = (availableWidth - gaps) / itemsPerRow;
561
-
581
+
562
582
  return (
563
583
  <TouchableOpacity
564
584
  key={photo.id}
@@ -613,7 +633,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
613
633
 
614
634
  const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
615
635
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
616
-
636
+
617
637
  return (
618
638
  <TouchableOpacity
619
639
  key={photo.id}
@@ -693,7 +713,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
693
713
  {/* Preview Thumbnail */}
694
714
  <View style={styles.filePreviewContainer}>
695
715
  {hasPreview ? (
696
- <View
716
+ <View
697
717
  style={styles.filePreview}
698
718
  {...(Platform.OS === 'web' && {
699
719
  onMouseEnter: () => setHoveredPreview(file.id),
@@ -746,7 +766,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
746
766
  </View>
747
767
  )}
748
768
  {/* Fallback icon (hidden by default for images) */}
749
- <View
769
+ <View
750
770
  style={[styles.fallbackIcon, { display: isImage ? 'none' : 'flex' }]}
751
771
  {...(Platform.OS === 'web' && { 'data-fallback': 'true' })}
752
772
  >
@@ -756,7 +776,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
756
776
  color={themeStyles.primaryColor}
757
777
  />
758
778
  </View>
759
-
779
+
760
780
  {/* Preview overlay for hover effect */}
761
781
  {Platform.OS === 'web' && hoveredPreview === file.id && isImage && (
762
782
  <View style={styles.previewOverlay}>
@@ -774,7 +794,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
774
794
  </View>
775
795
  )}
776
796
  </View>
777
-
797
+
778
798
  <View style={styles.fileInfo}>
779
799
  <Text style={[styles.fileName, { color: themeStyles.textColor }]} numberOfLines={1}>
780
800
  {file.filename}
@@ -803,7 +823,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
803
823
  <Ionicons name="eye" size={20} color={themeStyles.primaryColor} />
804
824
  </TouchableOpacity>
805
825
  )}
806
-
826
+
807
827
  <TouchableOpacity
808
828
  style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
809
829
  onPress={() => handleFileDownload(file.id, file.filename)}
@@ -832,18 +852,18 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
832
852
 
833
853
  const renderPhotoItem = (photo: FileMetadata, index: number) => {
834
854
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
835
-
855
+
836
856
  // Calculate photo item width based on actual container size from bottom sheet
837
857
  let itemsPerRow = 3; // Default for mobile
838
858
  if (containerWidth > 768) itemsPerRow = 6; // Tablet/Desktop
839
859
  else if (containerWidth > 480) itemsPerRow = 4; // Large mobile
840
-
860
+
841
861
  // Account for the photoScrollContainer padding (16px on each side = 32px total)
842
862
  const scrollContainerPadding = 32; // Total horizontal padding from photoScrollContainer
843
863
  const gaps = (itemsPerRow - 1) * 4; // Gap between items
844
864
  const availableWidth = containerWidth - scrollContainerPadding;
845
865
  const itemWidth = (availableWidth - gaps) / itemsPerRow;
846
-
866
+
847
867
  return (
848
868
  <TouchableOpacity
849
869
  key={photo.id}
@@ -898,14 +918,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
898
918
 
899
919
  const renderPhotoGrid = useCallback(() => {
900
920
  const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
901
-
921
+
902
922
  if (photos.length === 0) {
903
923
  return (
904
924
  <View style={styles.emptyState}>
905
925
  <Ionicons name="images-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
906
926
  <Text style={[styles.emptyStateTitle, { color: themeStyles.textColor }]}>No Photos Yet</Text>
907
927
  <Text style={[styles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
908
- {user?.id === targetUserId
928
+ {user?.id === targetUserId
909
929
  ? `Upload photos to get started. You can select multiple photos at once${Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}`
910
930
  : "This user hasn't uploaded any photos yet"
911
931
  }
@@ -951,8 +971,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
951
971
  </Text>
952
972
  </View>
953
973
  )}
954
-
955
- <JustifiedPhotoGrid
974
+
975
+ <JustifiedPhotoGrid
956
976
  photos={photos}
957
977
  photoDimensions={photoDimensions}
958
978
  loadPhotoDimensions={loadPhotoDimensions}
@@ -965,36 +985,36 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
965
985
  </ScrollView>
966
986
  );
967
987
  }, [
968
- filteredFiles,
969
- themeStyles,
970
- user?.id,
971
- targetUserId,
972
- uploading,
973
- handleFileUpload,
974
- refreshing,
975
- loadFiles,
976
- loadingDimensions,
977
- photoDimensions,
978
- loadPhotoDimensions,
979
- createJustifiedRows,
980
- renderJustifiedPhotoItem,
981
- renderPhotoItem,
988
+ filteredFiles,
989
+ themeStyles,
990
+ user?.id,
991
+ targetUserId,
992
+ uploading,
993
+ handleFileUpload,
994
+ refreshing,
995
+ loadFiles,
996
+ loadingDimensions,
997
+ photoDimensions,
998
+ loadPhotoDimensions,
999
+ createJustifiedRows,
1000
+ renderJustifiedPhotoItem,
1001
+ renderPhotoItem,
982
1002
  containerWidth
983
1003
  ]);
984
1004
 
985
1005
  // Separate component for the photo grid to optimize rendering
986
- const JustifiedPhotoGrid = React.memo(({
987
- photos,
988
- photoDimensions,
989
- loadPhotoDimensions,
990
- createJustifiedRows,
991
- renderJustifiedPhotoItem,
992
- renderSimplePhotoItem,
1006
+ const JustifiedPhotoGrid = React.memo(({
1007
+ photos,
1008
+ photoDimensions,
1009
+ loadPhotoDimensions,
1010
+ createJustifiedRows,
1011
+ renderJustifiedPhotoItem,
1012
+ renderSimplePhotoItem,
993
1013
  textColor,
994
1014
  containerWidth
995
1015
  }: {
996
1016
  photos: FileMetadata[];
997
- photoDimensions: { [key: string]: { width: number, height: number } };
1017
+ photoDimensions: {[key: string]: {width: number, height: number}};
998
1018
  loadPhotoDimensions: (photos: FileMetadata[]) => Promise<void>;
999
1019
  createJustifiedRows: (photos: FileMetadata[], containerWidth: number) => FileMetadata[][];
1000
1020
  renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) => JSX.Element;
@@ -1009,7 +1029,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1009
1029
 
1010
1030
  // Group photos by date
1011
1031
  const photosByDate = React.useMemo(() => {
1012
- return photos.reduce((groups: { [key: string]: FileMetadata[] }, photo) => {
1032
+ return photos.reduce((groups: {[key: string]: FileMetadata[]}, photo) => {
1013
1033
  const date = new Date(photo.uploadDate).toDateString();
1014
1034
  if (!groups[date]) {
1015
1035
  groups[date] = [];
@@ -1020,7 +1040,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1020
1040
  }, [photos]);
1021
1041
 
1022
1042
  const sortedDates = React.useMemo(() => {
1023
- return Object.keys(photosByDate).sort((a, b) =>
1043
+ return Object.keys(photosByDate).sort((a, b) =>
1024
1044
  new Date(b).getTime() - new Date(a).getTime()
1025
1045
  );
1026
1046
  }, [photosByDate]);
@@ -1030,15 +1050,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1030
1050
  {sortedDates.map(date => {
1031
1051
  const dayPhotos = photosByDate[date];
1032
1052
  const justifiedRows = createJustifiedRows(dayPhotos, containerWidth);
1033
-
1053
+
1034
1054
  return (
1035
1055
  <View key={date} style={styles.photoDateSection}>
1036
1056
  <Text style={[styles.photoDateHeader, { color: themeStyles.textColor }]}>
1037
- {new Date(date).toLocaleDateString('en-US', {
1038
- weekday: 'long',
1039
- year: 'numeric',
1040
- month: 'long',
1041
- day: 'numeric'
1057
+ {new Date(date).toLocaleDateString('en-US', {
1058
+ weekday: 'long',
1059
+ year: 'numeric',
1060
+ month: 'long',
1061
+ day: 'numeric'
1042
1062
  })}
1043
1063
  </Text>
1044
1064
  <View style={styles.justifiedPhotoGrid}>
@@ -1046,31 +1066,31 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1046
1066
  // Calculate row height based on available width
1047
1067
  const gap = 4;
1048
1068
  let totalAspectRatio = 0;
1049
-
1069
+
1050
1070
  // Calculate total aspect ratio for this row
1051
1071
  row.forEach(photo => {
1052
1072
  const dimensions = photoDimensions[photo.id];
1053
- const aspectRatio = dimensions ?
1054
- (dimensions.width / dimensions.height) :
1073
+ const aspectRatio = dimensions ?
1074
+ (dimensions.width / dimensions.height) :
1055
1075
  1.33; // Default 4:3 ratio
1056
1076
  totalAspectRatio += aspectRatio;
1057
1077
  });
1058
-
1078
+
1059
1079
  // Calculate the height that makes the row fill the available width
1060
1080
  // Account for photoScrollContainer padding (32px total) and gaps between photos
1061
1081
  const scrollContainerPadding = 32;
1062
1082
  const availableWidth = containerWidth - scrollContainerPadding - (gap * (row.length - 1));
1063
1083
  const calculatedHeight = availableWidth / totalAspectRatio;
1064
-
1084
+
1065
1085
  // Clamp height for visual consistency
1066
1086
  const rowHeight = Math.max(120, Math.min(calculatedHeight, 300));
1067
-
1087
+
1068
1088
  return (
1069
- <View
1070
- key={`row-${rowIndex}`}
1089
+ <View
1090
+ key={`row-${rowIndex}`}
1071
1091
  style={[
1072
- styles.justifiedPhotoRow,
1073
- {
1092
+ styles.justifiedPhotoRow,
1093
+ {
1074
1094
  height: rowHeight,
1075
1095
  maxWidth: containerWidth - 32, // Account for scroll container padding
1076
1096
  gap: 4, // Add horizontal gap between photos in row
@@ -1079,17 +1099,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1079
1099
  >
1080
1100
  {row.map((photo, photoIndex) => {
1081
1101
  const dimensions = photoDimensions[photo.id];
1082
- const aspectRatio = dimensions ?
1083
- (dimensions.width / dimensions.height) :
1102
+ const aspectRatio = dimensions ?
1103
+ (dimensions.width / dimensions.height) :
1084
1104
  1.33; // Default 4:3 ratio
1085
-
1105
+
1086
1106
  const photoWidth = rowHeight * aspectRatio;
1087
1107
  const isLast = photoIndex === row.length - 1;
1088
-
1108
+
1089
1109
  return renderJustifiedPhotoItem(
1090
- photo,
1091
- photoWidth,
1092
- rowHeight,
1110
+ photo,
1111
+ photoWidth,
1112
+ rowHeight,
1093
1113
  isLast
1094
1114
  );
1095
1115
  })}
@@ -1107,7 +1127,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1107
1127
  const renderFileDetailsModal = () => {
1108
1128
  const backgroundColor = themeStyles.backgroundColor;
1109
1129
  const borderColor = themeStyles.borderColor;
1110
-
1130
+
1111
1131
  return (
1112
1132
  <Modal
1113
1133
  visible={showFileDetails}
@@ -1121,97 +1141,97 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1121
1141
  style={styles.modalCloseButton}
1122
1142
  onPress={() => setShowFileDetails(false)}
1123
1143
  >
1124
- <Ionicons name="close" size={24} color={themeStyles.textColor} />
1125
- </TouchableOpacity>
1126
- <Text style={[styles.modalTitle, { color: themeStyles.textColor }]}>File Details</Text>
1127
- <View style={styles.modalPlaceholder} />
1128
- </View>
1144
+ <Ionicons name="close" size={24} color={themeStyles.textColor} />
1145
+ </TouchableOpacity>
1146
+ <Text style={[styles.modalTitle, { color: themeStyles.textColor }]}>File Details</Text>
1147
+ <View style={styles.modalPlaceholder} />
1148
+ </View>
1129
1149
 
1130
- {selectedFile && (
1131
- <ScrollView style={styles.modalContent}>
1132
- <View style={[styles.fileDetailCard, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }]}>
1133
- <View style={styles.fileDetailIcon}>
1134
- <Ionicons
1135
- name={getFileIcon(selectedFile.contentType) as any}
1136
- size={64}
1137
- color={themeStyles.primaryColor}
1138
- />
1139
- </View>
1150
+ {selectedFile && (
1151
+ <ScrollView style={styles.modalContent}>
1152
+ <View style={[styles.fileDetailCard, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }]}>
1153
+ <View style={styles.fileDetailIcon}>
1154
+ <Ionicons
1155
+ name={getFileIcon(selectedFile.contentType) as any}
1156
+ size={64}
1157
+ color={themeStyles.primaryColor}
1158
+ />
1159
+ </View>
1140
1160
 
1141
- <Text style={[styles.fileDetailName, { color: themeStyles.textColor }]}>
1142
- {selectedFile.filename}
1143
- </Text>
1161
+ <Text style={[styles.fileDetailName, { color: themeStyles.textColor }]}>
1162
+ {selectedFile.filename}
1163
+ </Text>
1144
1164
 
1145
- <View style={styles.fileDetailInfo}>
1146
- <View style={styles.detailRow}>
1147
- <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1148
- Size:
1149
- </Text>
1150
- <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1151
- {formatFileSize(selectedFile.length)}
1152
- </Text>
1153
- </View>
1165
+ <View style={styles.fileDetailInfo}>
1166
+ <View style={styles.detailRow}>
1167
+ <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1168
+ Size:
1169
+ </Text>
1170
+ <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1171
+ {formatFileSize(selectedFile.length)}
1172
+ </Text>
1173
+ </View>
1154
1174
 
1155
- <View style={styles.detailRow}>
1156
- <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1157
- Type:
1158
- </Text>
1159
- <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1160
- {selectedFile.contentType}
1161
- </Text>
1162
- </View>
1175
+ <View style={styles.detailRow}>
1176
+ <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1177
+ Type:
1178
+ </Text>
1179
+ <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1180
+ {selectedFile.contentType}
1181
+ </Text>
1182
+ </View>
1163
1183
 
1184
+ <View style={styles.detailRow}>
1185
+ <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1186
+ Uploaded:
1187
+ </Text>
1188
+ <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1189
+ {new Date(selectedFile.uploadDate).toLocaleString()}
1190
+ </Text>
1191
+ </View>
1192
+
1193
+ {selectedFile.metadata?.description && (
1164
1194
  <View style={styles.detailRow}>
1165
1195
  <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1166
- Uploaded:
1196
+ Description:
1167
1197
  </Text>
1168
1198
  <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1169
- {new Date(selectedFile.uploadDate).toLocaleString()}
1199
+ {selectedFile.metadata.description}
1170
1200
  </Text>
1171
1201
  </View>
1202
+ )}
1203
+ </View>
1172
1204
 
1173
- {selectedFile.metadata?.description && (
1174
- <View style={styles.detailRow}>
1175
- <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1176
- Description:
1177
- </Text>
1178
- <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1179
- {selectedFile.metadata.description}
1180
- </Text>
1181
- </View>
1182
- )}
1183
- </View>
1205
+ <View style={styles.modalActions}>
1206
+ <TouchableOpacity
1207
+ style={[styles.modalActionButton, { backgroundColor: themeStyles.primaryColor }]}
1208
+ onPress={() => {
1209
+ handleFileDownload(selectedFile.id, selectedFile.filename);
1210
+ setShowFileDetails(false);
1211
+ }}
1212
+ >
1213
+ <Ionicons name="download" size={20} color="#FFFFFF" />
1214
+ <Text style={styles.modalActionText}>Download</Text>
1215
+ </TouchableOpacity>
1184
1216
 
1185
- <View style={styles.modalActions}>
1217
+ {(user?.id === targetUserId) && (
1186
1218
  <TouchableOpacity
1187
- style={[styles.modalActionButton, { backgroundColor: themeStyles.primaryColor }]}
1219
+ style={[styles.modalActionButton, { backgroundColor: themeStyles.dangerColor }]}
1188
1220
  onPress={() => {
1189
- handleFileDownload(selectedFile.id, selectedFile.filename);
1190
1221
  setShowFileDetails(false);
1222
+ handleFileDelete(selectedFile.id, selectedFile.filename);
1191
1223
  }}
1192
1224
  >
1193
- <Ionicons name="download" size={20} color="#FFFFFF" />
1194
- <Text style={styles.modalActionText}>Download</Text>
1225
+ <Ionicons name="trash" size={20} color="#FFFFFF" />
1226
+ <Text style={styles.modalActionText}>Delete</Text>
1195
1227
  </TouchableOpacity>
1196
-
1197
- {(user?.id === targetUserId) && (
1198
- <TouchableOpacity
1199
- style={[styles.modalActionButton, { backgroundColor: themeStyles.dangerColor }]}
1200
- onPress={() => {
1201
- setShowFileDetails(false);
1202
- handleFileDelete(selectedFile.id, selectedFile.filename);
1203
- }}
1204
- >
1205
- <Ionicons name="trash" size={20} color="#FFFFFF" />
1206
- <Text style={styles.modalActionText}>Delete</Text>
1207
- </TouchableOpacity>
1208
- )}
1209
- </View>
1228
+ )}
1210
1229
  </View>
1211
- </ScrollView>
1212
- )}
1213
- </View>
1214
- </Modal>
1230
+ </View>
1231
+ </ScrollView>
1232
+ )}
1233
+ </View>
1234
+ </Modal>
1215
1235
  );
1216
1236
  };
1217
1237
 
@@ -1222,11 +1242,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1222
1242
  const borderColor = themeStyles.borderColor;
1223
1243
 
1224
1244
  const isImage = openedFile.contentType.startsWith('image/');
1225
- const isText = openedFile.contentType.startsWith('text/') ||
1226
- openedFile.contentType.includes('json') ||
1227
- openedFile.contentType.includes('xml') ||
1228
- openedFile.contentType.includes('javascript') ||
1229
- openedFile.contentType.includes('typescript');
1245
+ const isText = openedFile.contentType.startsWith('text/') ||
1246
+ openedFile.contentType.includes('json') ||
1247
+ openedFile.contentType.includes('xml') ||
1248
+ openedFile.contentType.includes('javascript') ||
1249
+ openedFile.contentType.includes('typescript');
1230
1250
  const isPDF = openedFile.contentType.includes('pdf');
1231
1251
  const isVideo = openedFile.contentType.startsWith('video/');
1232
1252
  const isAudio = openedFile.contentType.startsWith('audio/');
@@ -1258,19 +1278,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1258
1278
  </TouchableOpacity>
1259
1279
  <TouchableOpacity
1260
1280
  style={[
1261
- styles.actionButton,
1262
- {
1263
- backgroundColor: showFileDetailsInViewer
1264
- ? themeStyles.primaryColor
1265
- : (themeStyles.isDarkTheme ? '#333333' : '#F0F0F0')
1281
+ styles.actionButton,
1282
+ {
1283
+ backgroundColor: showFileDetailsInViewer
1284
+ ? themeStyles.primaryColor
1285
+ : (themeStyles.isDarkTheme ? '#333333' : '#F0F0F0')
1266
1286
  }
1267
1287
  ]}
1268
1288
  onPress={() => setShowFileDetailsInViewer(!showFileDetailsInViewer)}
1269
1289
  >
1270
- <Ionicons
1271
- name={showFileDetailsInViewer ? "chevron-up" : "information-circle"}
1272
- size={20}
1273
- color={showFileDetailsInViewer ? "#FFFFFF" : themeStyles.primaryColor}
1290
+ <Ionicons
1291
+ name={showFileDetailsInViewer ? "chevron-up" : "information-circle"}
1292
+ size={20}
1293
+ color={showFileDetailsInViewer ? "#FFFFFF" : themeStyles.primaryColor}
1274
1294
  />
1275
1295
  </TouchableOpacity>
1276
1296
  </View>
@@ -1290,7 +1310,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1290
1310
  <Ionicons name="chevron-up" size={20} color={themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'} />
1291
1311
  </TouchableOpacity>
1292
1312
  </View>
1293
-
1313
+
1294
1314
  <View style={styles.fileDetailInfo}>
1295
1315
  <View style={styles.detailRow}>
1296
1316
  <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
@@ -1375,11 +1395,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1375
1395
  )}
1376
1396
 
1377
1397
  {/* File Content */}
1378
- <ScrollView
1398
+ <ScrollView
1379
1399
  style={[
1380
1400
  styles.fileViewerContent,
1381
1401
  showFileDetailsInViewer && styles.fileViewerContentWithDetails
1382
- ]}
1402
+ ]}
1383
1403
  contentContainerStyle={styles.fileViewerContentContainer}
1384
1404
  >
1385
1405
  {loadingFileContent ? (
@@ -1392,8 +1412,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1392
1412
  ) : isImage && fileContent ? (
1393
1413
  <View style={styles.imageContainer}>
1394
1414
  {Platform.OS === 'web' ? (
1395
- <img
1396
- src={fileContent}
1415
+ <img
1416
+ src={fileContent}
1397
1417
  alt={openedFile.filename}
1398
1418
  style={{
1399
1419
  maxWidth: '100%',
@@ -1479,10 +1499,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1479
1499
  </View>
1480
1500
  ) : (
1481
1501
  <View style={styles.unsupportedFileContainer}>
1482
- <Ionicons
1483
- name={getFileIcon(openedFile.contentType) as any}
1484
- size={64}
1485
- color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'}
1502
+ <Ionicons
1503
+ name={getFileIcon(openedFile.contentType) as any}
1504
+ size={64}
1505
+ color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'}
1486
1506
  />
1487
1507
  <Text style={[styles.unsupportedFileTitle, { color: themeStyles.textColor }]}>
1488
1508
  Preview Not Available
@@ -1510,7 +1530,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1510
1530
  <Ionicons name="folder-open-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
1511
1531
  <Text style={[styles.emptyStateTitle, { color: themeStyles.textColor }]}>No Files Yet</Text>
1512
1532
  <Text style={[styles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1513
- {user?.id === targetUserId
1533
+ {user?.id === targetUserId
1514
1534
  ? `Upload files to get started. You can select multiple files at once${Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}`
1515
1535
  : "This user hasn't uploaded any files yet"
1516
1536
  }
@@ -1554,9 +1574,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1554
1574
  }
1555
1575
 
1556
1576
  return (
1557
- <View
1577
+ <View
1558
1578
  style={[
1559
- styles.container,
1579
+ styles.container,
1560
1580
  { backgroundColor },
1561
1581
  isDragging && Platform.OS === 'web' && styles.dragOverlay
1562
1582
  ]}
@@ -1568,8 +1588,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1568
1588
  >
1569
1589
  {/* Header */}
1570
1590
  <View style={[
1571
- styles.header,
1572
- {
1591
+ styles.header,
1592
+ {
1573
1593
  borderBottomColor: borderColor,
1574
1594
  backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1575
1595
  shadowColor: '#000000',
@@ -1582,19 +1602,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1582
1602
  elevation: 4,
1583
1603
  }
1584
1604
  ]}>
1585
- <TouchableOpacity
1605
+ <TouchableOpacity
1586
1606
  style={[
1587
- styles.backButton,
1588
- {
1607
+ styles.backButton,
1608
+ {
1589
1609
  backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
1590
1610
  borderRadius: 12,
1591
1611
  }
1592
- ]}
1612
+ ]}
1593
1613
  onPress={onClose || goBack}
1594
1614
  >
1595
1615
  <Ionicons name="arrow-back" size={22} color={themeStyles.textColor} />
1596
1616
  </TouchableOpacity>
1597
-
1617
+
1598
1618
  <View style={styles.headerTitleContainer}>
1599
1619
  <Text style={[styles.headerTitle, { color: themeStyles.textColor }]}>
1600
1620
  {viewMode === 'photos' ? 'Photos' : 'File Management'}
@@ -1603,12 +1623,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1603
1623
  {filteredFiles.length} {filteredFiles.length === 1 ? 'item' : 'items'}
1604
1624
  </Text>
1605
1625
  </View>
1606
-
1626
+
1607
1627
  <View style={styles.headerActions}>
1608
1628
  {/* View Mode Toggle */}
1609
1629
  <View style={[
1610
- styles.viewModeToggle,
1611
- {
1630
+ styles.viewModeToggle,
1631
+ {
1612
1632
  backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
1613
1633
  borderWidth: 1,
1614
1634
  borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
@@ -1625,7 +1645,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1625
1645
  <TouchableOpacity
1626
1646
  style={[
1627
1647
  styles.viewModeButton,
1628
- viewMode === 'all' && {
1648
+ viewMode === 'all' && {
1629
1649
  backgroundColor: themeStyles.primaryColor,
1630
1650
  shadowColor: themeStyles.primaryColor,
1631
1651
  shadowOffset: {
@@ -1639,16 +1659,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1639
1659
  ]}
1640
1660
  onPress={() => setViewMode('all')}
1641
1661
  >
1642
- <Ionicons
1643
- name="folder"
1644
- size={18}
1645
- color={viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor}
1662
+ <Ionicons
1663
+ name="folder"
1664
+ size={18}
1665
+ color={viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor}
1646
1666
  />
1647
1667
  </TouchableOpacity>
1648
1668
  <TouchableOpacity
1649
1669
  style={[
1650
1670
  styles.viewModeButton,
1651
- viewMode === 'photos' && {
1671
+ viewMode === 'photos' && {
1652
1672
  backgroundColor: themeStyles.primaryColor,
1653
1673
  shadowColor: themeStyles.primaryColor,
1654
1674
  shadowOffset: {
@@ -1662,19 +1682,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1662
1682
  ]}
1663
1683
  onPress={() => setViewMode('photos')}
1664
1684
  >
1665
- <Ionicons
1666
- name="images"
1667
- size={18}
1668
- color={viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor}
1685
+ <Ionicons
1686
+ name="images"
1687
+ size={18}
1688
+ color={viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor}
1669
1689
  />
1670
1690
  </TouchableOpacity>
1671
1691
  </View>
1672
-
1692
+
1673
1693
  {user?.id === targetUserId && (
1674
1694
  <TouchableOpacity
1675
1695
  style={[
1676
- styles.uploadButton,
1677
- {
1696
+ styles.uploadButton,
1697
+ {
1678
1698
  backgroundColor: themeStyles.primaryColor,
1679
1699
  shadowColor: themeStyles.primaryColor,
1680
1700
  shadowOffset: {
@@ -1710,9 +1730,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1710
1730
  {/* Search Bar */}
1711
1731
  {files.length > 0 && (viewMode === 'all' || files.some(f => f.contentType.startsWith('image/'))) && (
1712
1732
  <View style={[
1713
- styles.searchContainer,
1714
- {
1715
- backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1733
+ styles.searchContainer,
1734
+ {
1735
+ backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1716
1736
  borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1717
1737
  shadowColor: '#000000',
1718
1738
  shadowOffset: {
@@ -1733,7 +1753,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1733
1753
  onChangeText={setSearchQuery}
1734
1754
  />
1735
1755
  {searchQuery.length > 0 && (
1736
- <TouchableOpacity
1756
+ <TouchableOpacity
1737
1757
  onPress={() => setSearchQuery('')}
1738
1758
  style={styles.searchClearButton}
1739
1759
  >
@@ -1746,9 +1766,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1746
1766
  {/* File Stats */}
1747
1767
  {files.length > 0 && (
1748
1768
  <View style={[
1749
- styles.statsContainer,
1750
- {
1751
- backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1769
+ styles.statsContainer,
1770
+ {
1771
+ backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1752
1772
  borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1753
1773
  shadowColor: '#000000',
1754
1774
  shadowOffset: {
@@ -2119,7 +2139,7 @@ const styles = StyleSheet.create({
2119
2139
  fontSize: 16,
2120
2140
  marginTop: 16,
2121
2141
  },
2122
-
2142
+
2123
2143
  // Modal styles
2124
2144
  modalContainer: {
2125
2145
  flex: 1,
@@ -2204,7 +2224,7 @@ const styles = StyleSheet.create({
2204
2224
  fontSize: 16,
2205
2225
  fontWeight: '600',
2206
2226
  },
2207
-
2227
+
2208
2228
  // Drag and Drop styles
2209
2229
  dragDropOverlay: {
2210
2230
  position: 'absolute',
@@ -2236,7 +2256,7 @@ const styles = StyleSheet.create({
2236
2256
  fontSize: 16,
2237
2257
  textAlign: 'center',
2238
2258
  },
2239
-
2259
+
2240
2260
  // File Viewer styles
2241
2261
  fileViewerContainer: {
2242
2262
  flex: 1,
@@ -2352,7 +2372,7 @@ const styles = StyleSheet.create({
2352
2372
  textAlign: 'center',
2353
2373
  fontStyle: 'italic',
2354
2374
  },
2355
-
2375
+
2356
2376
  // File Details in Viewer styles
2357
2377
  fileDetailsSection: {
2358
2378
  margin: 16,
@@ -2395,7 +2415,7 @@ const styles = StyleSheet.create({
2395
2415
  fontSize: 14,
2396
2416
  fontWeight: '600',
2397
2417
  },
2398
-
2418
+
2399
2419
  // Header styles
2400
2420
  headerActions: {
2401
2421
  flexDirection: 'row',
@@ -2417,7 +2437,7 @@ const styles = StyleSheet.create({
2417
2437
  justifyContent: 'center',
2418
2438
  marginHorizontal: 1,
2419
2439
  },
2420
-
2440
+
2421
2441
  // Photo Grid styles
2422
2442
  photoScrollContainer: {
2423
2443
  padding: 16,
@@ -2453,7 +2473,7 @@ const styles = StyleSheet.create({
2453
2473
  width: '100%',
2454
2474
  height: '100%',
2455
2475
  },
2456
-
2476
+
2457
2477
  // Justified Grid styles
2458
2478
  dimensionsLoadingIndicator: {
2459
2479
  flexDirection: 'row',
@@ -2490,7 +2510,7 @@ const styles = StyleSheet.create({
2490
2510
  height: '100%',
2491
2511
  borderRadius: 6,
2492
2512
  },
2493
-
2513
+
2494
2514
  // Simple Photo Grid styles
2495
2515
  simplePhotoItem: {
2496
2516
  borderRadius: 8,
@@ -2509,7 +2529,7 @@ const styles = StyleSheet.create({
2509
2529
  height: '100%',
2510
2530
  borderRadius: 8,
2511
2531
  },
2512
-
2532
+
2513
2533
  // Loading skeleton styles
2514
2534
  photoSkeletonGrid: {
2515
2535
  flexDirection: 'row',