@oxyhq/services 5.4.5 → 5.4.6

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 (37) hide show
  1. package/lib/commonjs/core/index.js +59 -0
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/ui/components/FollowButton.js +23 -8
  4. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/useFollow.js +29 -10
  6. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
  7. package/lib/commonjs/ui/screens/AppInfoScreen.js +19 -37
  8. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  9. package/lib/commonjs/ui/screens/FileManagementScreen.js +9 -27
  10. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  11. package/lib/commonjs/ui/store/index.js +5 -1
  12. package/lib/commonjs/ui/store/index.js.map +1 -1
  13. package/lib/module/core/index.js +59 -0
  14. package/lib/module/core/index.js.map +1 -1
  15. package/lib/module/ui/components/FollowButton.js +23 -8
  16. package/lib/module/ui/components/FollowButton.js.map +1 -1
  17. package/lib/module/ui/hooks/useFollow.js +29 -10
  18. package/lib/module/ui/hooks/useFollow.js.map +1 -1
  19. package/lib/module/ui/screens/AppInfoScreen.js +19 -37
  20. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  21. package/lib/module/ui/screens/FileManagementScreen.js +9 -27
  22. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  23. package/lib/module/ui/store/index.js +5 -1
  24. package/lib/module/ui/store/index.js.map +1 -1
  25. package/lib/typescript/core/index.d.ts +28 -0
  26. package/lib/typescript/core/index.d.ts.map +1 -1
  27. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  28. package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -1
  29. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  30. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  31. package/lib/typescript/ui/store/index.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/ui/components/FollowButton.tsx +25 -11
  34. package/src/ui/hooks/useFollow.ts +30 -10
  35. package/src/ui/screens/AppInfoScreen.tsx +23 -40
  36. package/src/ui/screens/FileManagementScreen.tsx +248 -268
  37. package/src/ui/store/index.ts +6 -1
@@ -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,34 +434,25 @@ 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 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();
441
+
442
+ // Method 2: Fallback to authenticated download
443
+ const blob = await oxyServices.getFileContentAsBlob(fileId);
453
444
  const url = window.URL.createObjectURL(blob);
454
-
445
+
455
446
  const link = document.createElement('a');
456
447
  link.href = url;
457
448
  link.download = filename;
458
449
  document.body.appendChild(link);
459
450
  link.click();
460
451
  document.body.removeChild(link);
461
-
452
+
462
453
  // Clean up the blob URL
463
454
  window.URL.revokeObjectURL(url);
464
-
455
+
465
456
  toast.success('File downloaded successfully');
466
457
  }
467
458
  } else {
@@ -496,10 +487,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
496
487
  try {
497
488
  setLoadingFileContent(true);
498
489
  setOpenedFile(file);
499
-
490
+
500
491
  // For text files, images, and other viewable content, try to load the content
501
- if (file.contentType.startsWith('text/') ||
502
- file.contentType.includes('json') ||
492
+ if (file.contentType.startsWith('text/') ||
493
+ file.contentType.includes('json') ||
503
494
  file.contentType.includes('xml') ||
504
495
  file.contentType.includes('javascript') ||
505
496
  file.contentType.includes('typescript') ||
@@ -507,30 +498,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
507
498
  file.contentType.includes('pdf') ||
508
499
  file.contentType.startsWith('video/') ||
509
500
  file.contentType.startsWith('audio/')) {
510
-
501
+
511
502
  try {
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
- }
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);
527
510
  } else {
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);
511
+ // For text files, get the content using authenticated request
512
+ const content = await oxyServices.getFileContentAsText(file.id);
513
+ setFileContent(content);
534
514
  }
535
515
  } catch (error: any) {
536
516
  console.error('Failed to load file content:', error);
@@ -567,18 +547,18 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
567
547
 
568
548
  const renderSimplePhotoItem = useCallback((photo: FileMetadata, index: number) => {
569
549
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
570
-
550
+
571
551
  // Calculate photo item width based on actual container size from bottom sheet
572
552
  let itemsPerRow = 3; // Default for mobile
573
553
  if (containerWidth > 768) itemsPerRow = 4; // Desktop/tablet
574
554
  else if (containerWidth > 480) itemsPerRow = 3; // Large mobile
575
-
555
+
576
556
  // Account for the photoScrollContainer padding (16px on each side = 32px total)
577
557
  const scrollContainerPadding = 32; // Total horizontal padding from photoScrollContainer
578
558
  const gaps = (itemsPerRow - 1) * 4; // Gap between items (4px)
579
559
  const availableWidth = containerWidth - scrollContainerPadding;
580
560
  const itemWidth = (availableWidth - gaps) / itemsPerRow;
581
-
561
+
582
562
  return (
583
563
  <TouchableOpacity
584
564
  key={photo.id}
@@ -633,7 +613,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
633
613
 
634
614
  const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
635
615
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
636
-
616
+
637
617
  return (
638
618
  <TouchableOpacity
639
619
  key={photo.id}
@@ -713,7 +693,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
713
693
  {/* Preview Thumbnail */}
714
694
  <View style={styles.filePreviewContainer}>
715
695
  {hasPreview ? (
716
- <View
696
+ <View
717
697
  style={styles.filePreview}
718
698
  {...(Platform.OS === 'web' && {
719
699
  onMouseEnter: () => setHoveredPreview(file.id),
@@ -766,7 +746,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
766
746
  </View>
767
747
  )}
768
748
  {/* Fallback icon (hidden by default for images) */}
769
- <View
749
+ <View
770
750
  style={[styles.fallbackIcon, { display: isImage ? 'none' : 'flex' }]}
771
751
  {...(Platform.OS === 'web' && { 'data-fallback': 'true' })}
772
752
  >
@@ -776,7 +756,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
776
756
  color={themeStyles.primaryColor}
777
757
  />
778
758
  </View>
779
-
759
+
780
760
  {/* Preview overlay for hover effect */}
781
761
  {Platform.OS === 'web' && hoveredPreview === file.id && isImage && (
782
762
  <View style={styles.previewOverlay}>
@@ -794,7 +774,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
794
774
  </View>
795
775
  )}
796
776
  </View>
797
-
777
+
798
778
  <View style={styles.fileInfo}>
799
779
  <Text style={[styles.fileName, { color: themeStyles.textColor }]} numberOfLines={1}>
800
780
  {file.filename}
@@ -823,7 +803,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
823
803
  <Ionicons name="eye" size={20} color={themeStyles.primaryColor} />
824
804
  </TouchableOpacity>
825
805
  )}
826
-
806
+
827
807
  <TouchableOpacity
828
808
  style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
829
809
  onPress={() => handleFileDownload(file.id, file.filename)}
@@ -852,18 +832,18 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
852
832
 
853
833
  const renderPhotoItem = (photo: FileMetadata, index: number) => {
854
834
  const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
855
-
835
+
856
836
  // Calculate photo item width based on actual container size from bottom sheet
857
837
  let itemsPerRow = 3; // Default for mobile
858
838
  if (containerWidth > 768) itemsPerRow = 6; // Tablet/Desktop
859
839
  else if (containerWidth > 480) itemsPerRow = 4; // Large mobile
860
-
840
+
861
841
  // Account for the photoScrollContainer padding (16px on each side = 32px total)
862
842
  const scrollContainerPadding = 32; // Total horizontal padding from photoScrollContainer
863
843
  const gaps = (itemsPerRow - 1) * 4; // Gap between items
864
844
  const availableWidth = containerWidth - scrollContainerPadding;
865
845
  const itemWidth = (availableWidth - gaps) / itemsPerRow;
866
-
846
+
867
847
  return (
868
848
  <TouchableOpacity
869
849
  key={photo.id}
@@ -918,14 +898,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
918
898
 
919
899
  const renderPhotoGrid = useCallback(() => {
920
900
  const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
921
-
901
+
922
902
  if (photos.length === 0) {
923
903
  return (
924
904
  <View style={styles.emptyState}>
925
905
  <Ionicons name="images-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
926
906
  <Text style={[styles.emptyStateTitle, { color: themeStyles.textColor }]}>No Photos Yet</Text>
927
907
  <Text style={[styles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
928
- {user?.id === targetUserId
908
+ {user?.id === targetUserId
929
909
  ? `Upload photos to get started. You can select multiple photos at once${Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}`
930
910
  : "This user hasn't uploaded any photos yet"
931
911
  }
@@ -971,8 +951,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
971
951
  </Text>
972
952
  </View>
973
953
  )}
974
-
975
- <JustifiedPhotoGrid
954
+
955
+ <JustifiedPhotoGrid
976
956
  photos={photos}
977
957
  photoDimensions={photoDimensions}
978
958
  loadPhotoDimensions={loadPhotoDimensions}
@@ -985,36 +965,36 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
985
965
  </ScrollView>
986
966
  );
987
967
  }, [
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,
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,
1002
982
  containerWidth
1003
983
  ]);
1004
984
 
1005
985
  // Separate component for the photo grid to optimize rendering
1006
- const JustifiedPhotoGrid = React.memo(({
1007
- photos,
1008
- photoDimensions,
1009
- loadPhotoDimensions,
1010
- createJustifiedRows,
1011
- renderJustifiedPhotoItem,
1012
- renderSimplePhotoItem,
986
+ const JustifiedPhotoGrid = React.memo(({
987
+ photos,
988
+ photoDimensions,
989
+ loadPhotoDimensions,
990
+ createJustifiedRows,
991
+ renderJustifiedPhotoItem,
992
+ renderSimplePhotoItem,
1013
993
  textColor,
1014
994
  containerWidth
1015
995
  }: {
1016
996
  photos: FileMetadata[];
1017
- photoDimensions: {[key: string]: {width: number, height: number}};
997
+ photoDimensions: { [key: string]: { width: number, height: number } };
1018
998
  loadPhotoDimensions: (photos: FileMetadata[]) => Promise<void>;
1019
999
  createJustifiedRows: (photos: FileMetadata[], containerWidth: number) => FileMetadata[][];
1020
1000
  renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) => JSX.Element;
@@ -1029,7 +1009,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1029
1009
 
1030
1010
  // Group photos by date
1031
1011
  const photosByDate = React.useMemo(() => {
1032
- return photos.reduce((groups: {[key: string]: FileMetadata[]}, photo) => {
1012
+ return photos.reduce((groups: { [key: string]: FileMetadata[] }, photo) => {
1033
1013
  const date = new Date(photo.uploadDate).toDateString();
1034
1014
  if (!groups[date]) {
1035
1015
  groups[date] = [];
@@ -1040,7 +1020,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1040
1020
  }, [photos]);
1041
1021
 
1042
1022
  const sortedDates = React.useMemo(() => {
1043
- return Object.keys(photosByDate).sort((a, b) =>
1023
+ return Object.keys(photosByDate).sort((a, b) =>
1044
1024
  new Date(b).getTime() - new Date(a).getTime()
1045
1025
  );
1046
1026
  }, [photosByDate]);
@@ -1050,15 +1030,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1050
1030
  {sortedDates.map(date => {
1051
1031
  const dayPhotos = photosByDate[date];
1052
1032
  const justifiedRows = createJustifiedRows(dayPhotos, containerWidth);
1053
-
1033
+
1054
1034
  return (
1055
1035
  <View key={date} style={styles.photoDateSection}>
1056
1036
  <Text style={[styles.photoDateHeader, { color: themeStyles.textColor }]}>
1057
- {new Date(date).toLocaleDateString('en-US', {
1058
- weekday: 'long',
1059
- year: 'numeric',
1060
- month: 'long',
1061
- day: 'numeric'
1037
+ {new Date(date).toLocaleDateString('en-US', {
1038
+ weekday: 'long',
1039
+ year: 'numeric',
1040
+ month: 'long',
1041
+ day: 'numeric'
1062
1042
  })}
1063
1043
  </Text>
1064
1044
  <View style={styles.justifiedPhotoGrid}>
@@ -1066,31 +1046,31 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1066
1046
  // Calculate row height based on available width
1067
1047
  const gap = 4;
1068
1048
  let totalAspectRatio = 0;
1069
-
1049
+
1070
1050
  // Calculate total aspect ratio for this row
1071
1051
  row.forEach(photo => {
1072
1052
  const dimensions = photoDimensions[photo.id];
1073
- const aspectRatio = dimensions ?
1074
- (dimensions.width / dimensions.height) :
1053
+ const aspectRatio = dimensions ?
1054
+ (dimensions.width / dimensions.height) :
1075
1055
  1.33; // Default 4:3 ratio
1076
1056
  totalAspectRatio += aspectRatio;
1077
1057
  });
1078
-
1058
+
1079
1059
  // Calculate the height that makes the row fill the available width
1080
1060
  // Account for photoScrollContainer padding (32px total) and gaps between photos
1081
1061
  const scrollContainerPadding = 32;
1082
1062
  const availableWidth = containerWidth - scrollContainerPadding - (gap * (row.length - 1));
1083
1063
  const calculatedHeight = availableWidth / totalAspectRatio;
1084
-
1064
+
1085
1065
  // Clamp height for visual consistency
1086
1066
  const rowHeight = Math.max(120, Math.min(calculatedHeight, 300));
1087
-
1067
+
1088
1068
  return (
1089
- <View
1090
- key={`row-${rowIndex}`}
1069
+ <View
1070
+ key={`row-${rowIndex}`}
1091
1071
  style={[
1092
- styles.justifiedPhotoRow,
1093
- {
1072
+ styles.justifiedPhotoRow,
1073
+ {
1094
1074
  height: rowHeight,
1095
1075
  maxWidth: containerWidth - 32, // Account for scroll container padding
1096
1076
  gap: 4, // Add horizontal gap between photos in row
@@ -1099,17 +1079,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1099
1079
  >
1100
1080
  {row.map((photo, photoIndex) => {
1101
1081
  const dimensions = photoDimensions[photo.id];
1102
- const aspectRatio = dimensions ?
1103
- (dimensions.width / dimensions.height) :
1082
+ const aspectRatio = dimensions ?
1083
+ (dimensions.width / dimensions.height) :
1104
1084
  1.33; // Default 4:3 ratio
1105
-
1085
+
1106
1086
  const photoWidth = rowHeight * aspectRatio;
1107
1087
  const isLast = photoIndex === row.length - 1;
1108
-
1088
+
1109
1089
  return renderJustifiedPhotoItem(
1110
- photo,
1111
- photoWidth,
1112
- rowHeight,
1090
+ photo,
1091
+ photoWidth,
1092
+ rowHeight,
1113
1093
  isLast
1114
1094
  );
1115
1095
  })}
@@ -1127,7 +1107,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1127
1107
  const renderFileDetailsModal = () => {
1128
1108
  const backgroundColor = themeStyles.backgroundColor;
1129
1109
  const borderColor = themeStyles.borderColor;
1130
-
1110
+
1131
1111
  return (
1132
1112
  <Modal
1133
1113
  visible={showFileDetails}
@@ -1141,97 +1121,97 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1141
1121
  style={styles.modalCloseButton}
1142
1122
  onPress={() => setShowFileDetails(false)}
1143
1123
  >
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>
1149
-
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>
1160
-
1161
- <Text style={[styles.fileDetailName, { color: themeStyles.textColor }]}>
1162
- {selectedFile.filename}
1163
- </Text>
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>
1164
1129
 
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>
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
+ />
1173
1139
  </View>
1174
1140
 
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>
1141
+ <Text style={[styles.fileDetailName, { color: themeStyles.textColor }]}>
1142
+ {selectedFile.filename}
1143
+ </Text>
1183
1144
 
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>
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>
1192
1154
 
1193
- {selectedFile.metadata?.description && (
1194
1155
  <View style={styles.detailRow}>
1195
1156
  <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1196
- Description:
1157
+ Type:
1197
1158
  </Text>
1198
1159
  <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1199
- {selectedFile.metadata.description}
1160
+ {selectedFile.contentType}
1200
1161
  </Text>
1201
1162
  </View>
1202
- )}
1203
- </View>
1204
1163
 
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>
1164
+ <View style={styles.detailRow}>
1165
+ <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1166
+ Uploaded:
1167
+ </Text>
1168
+ <Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
1169
+ {new Date(selectedFile.uploadDate).toLocaleString()}
1170
+ </Text>
1171
+ </View>
1172
+
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>
1216
1184
 
1217
- {(user?.id === targetUserId) && (
1185
+ <View style={styles.modalActions}>
1218
1186
  <TouchableOpacity
1219
- style={[styles.modalActionButton, { backgroundColor: themeStyles.dangerColor }]}
1187
+ style={[styles.modalActionButton, { backgroundColor: themeStyles.primaryColor }]}
1220
1188
  onPress={() => {
1189
+ handleFileDownload(selectedFile.id, selectedFile.filename);
1221
1190
  setShowFileDetails(false);
1222
- handleFileDelete(selectedFile.id, selectedFile.filename);
1223
1191
  }}
1224
1192
  >
1225
- <Ionicons name="trash" size={20} color="#FFFFFF" />
1226
- <Text style={styles.modalActionText}>Delete</Text>
1193
+ <Ionicons name="download" size={20} color="#FFFFFF" />
1194
+ <Text style={styles.modalActionText}>Download</Text>
1227
1195
  </TouchableOpacity>
1228
- )}
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>
1229
1210
  </View>
1230
- </View>
1231
- </ScrollView>
1232
- )}
1233
- </View>
1234
- </Modal>
1211
+ </ScrollView>
1212
+ )}
1213
+ </View>
1214
+ </Modal>
1235
1215
  );
1236
1216
  };
1237
1217
 
@@ -1242,11 +1222,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1242
1222
  const borderColor = themeStyles.borderColor;
1243
1223
 
1244
1224
  const isImage = openedFile.contentType.startsWith('image/');
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');
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');
1250
1230
  const isPDF = openedFile.contentType.includes('pdf');
1251
1231
  const isVideo = openedFile.contentType.startsWith('video/');
1252
1232
  const isAudio = openedFile.contentType.startsWith('audio/');
@@ -1278,19 +1258,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1278
1258
  </TouchableOpacity>
1279
1259
  <TouchableOpacity
1280
1260
  style={[
1281
- styles.actionButton,
1282
- {
1283
- backgroundColor: showFileDetailsInViewer
1284
- ? themeStyles.primaryColor
1285
- : (themeStyles.isDarkTheme ? '#333333' : '#F0F0F0')
1261
+ styles.actionButton,
1262
+ {
1263
+ backgroundColor: showFileDetailsInViewer
1264
+ ? themeStyles.primaryColor
1265
+ : (themeStyles.isDarkTheme ? '#333333' : '#F0F0F0')
1286
1266
  }
1287
1267
  ]}
1288
1268
  onPress={() => setShowFileDetailsInViewer(!showFileDetailsInViewer)}
1289
1269
  >
1290
- <Ionicons
1291
- name={showFileDetailsInViewer ? "chevron-up" : "information-circle"}
1292
- size={20}
1293
- color={showFileDetailsInViewer ? "#FFFFFF" : themeStyles.primaryColor}
1270
+ <Ionicons
1271
+ name={showFileDetailsInViewer ? "chevron-up" : "information-circle"}
1272
+ size={20}
1273
+ color={showFileDetailsInViewer ? "#FFFFFF" : themeStyles.primaryColor}
1294
1274
  />
1295
1275
  </TouchableOpacity>
1296
1276
  </View>
@@ -1310,7 +1290,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1310
1290
  <Ionicons name="chevron-up" size={20} color={themeStyles.isDarkTheme ? '#BBBBBB' : '#666666'} />
1311
1291
  </TouchableOpacity>
1312
1292
  </View>
1313
-
1293
+
1314
1294
  <View style={styles.fileDetailInfo}>
1315
1295
  <View style={styles.detailRow}>
1316
1296
  <Text style={[styles.detailLabel, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
@@ -1395,11 +1375,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1395
1375
  )}
1396
1376
 
1397
1377
  {/* File Content */}
1398
- <ScrollView
1378
+ <ScrollView
1399
1379
  style={[
1400
1380
  styles.fileViewerContent,
1401
1381
  showFileDetailsInViewer && styles.fileViewerContentWithDetails
1402
- ]}
1382
+ ]}
1403
1383
  contentContainerStyle={styles.fileViewerContentContainer}
1404
1384
  >
1405
1385
  {loadingFileContent ? (
@@ -1412,8 +1392,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1412
1392
  ) : isImage && fileContent ? (
1413
1393
  <View style={styles.imageContainer}>
1414
1394
  {Platform.OS === 'web' ? (
1415
- <img
1416
- src={fileContent}
1395
+ <img
1396
+ src={fileContent}
1417
1397
  alt={openedFile.filename}
1418
1398
  style={{
1419
1399
  maxWidth: '100%',
@@ -1499,10 +1479,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1499
1479
  </View>
1500
1480
  ) : (
1501
1481
  <View style={styles.unsupportedFileContainer}>
1502
- <Ionicons
1503
- name={getFileIcon(openedFile.contentType) as any}
1504
- size={64}
1505
- color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'}
1482
+ <Ionicons
1483
+ name={getFileIcon(openedFile.contentType) as any}
1484
+ size={64}
1485
+ color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'}
1506
1486
  />
1507
1487
  <Text style={[styles.unsupportedFileTitle, { color: themeStyles.textColor }]}>
1508
1488
  Preview Not Available
@@ -1530,7 +1510,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1530
1510
  <Ionicons name="folder-open-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
1531
1511
  <Text style={[styles.emptyStateTitle, { color: themeStyles.textColor }]}>No Files Yet</Text>
1532
1512
  <Text style={[styles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
1533
- {user?.id === targetUserId
1513
+ {user?.id === targetUserId
1534
1514
  ? `Upload files to get started. You can select multiple files at once${Platform.OS === 'web' ? ' or drag & drop them here.' : '.'}`
1535
1515
  : "This user hasn't uploaded any files yet"
1536
1516
  }
@@ -1574,9 +1554,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1574
1554
  }
1575
1555
 
1576
1556
  return (
1577
- <View
1557
+ <View
1578
1558
  style={[
1579
- styles.container,
1559
+ styles.container,
1580
1560
  { backgroundColor },
1581
1561
  isDragging && Platform.OS === 'web' && styles.dragOverlay
1582
1562
  ]}
@@ -1588,8 +1568,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1588
1568
  >
1589
1569
  {/* Header */}
1590
1570
  <View style={[
1591
- styles.header,
1592
- {
1571
+ styles.header,
1572
+ {
1593
1573
  borderBottomColor: borderColor,
1594
1574
  backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1595
1575
  shadowColor: '#000000',
@@ -1602,19 +1582,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1602
1582
  elevation: 4,
1603
1583
  }
1604
1584
  ]}>
1605
- <TouchableOpacity
1585
+ <TouchableOpacity
1606
1586
  style={[
1607
- styles.backButton,
1608
- {
1587
+ styles.backButton,
1588
+ {
1609
1589
  backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
1610
1590
  borderRadius: 12,
1611
1591
  }
1612
- ]}
1592
+ ]}
1613
1593
  onPress={onClose || goBack}
1614
1594
  >
1615
1595
  <Ionicons name="arrow-back" size={22} color={themeStyles.textColor} />
1616
1596
  </TouchableOpacity>
1617
-
1597
+
1618
1598
  <View style={styles.headerTitleContainer}>
1619
1599
  <Text style={[styles.headerTitle, { color: themeStyles.textColor }]}>
1620
1600
  {viewMode === 'photos' ? 'Photos' : 'File Management'}
@@ -1623,12 +1603,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1623
1603
  {filteredFiles.length} {filteredFiles.length === 1 ? 'item' : 'items'}
1624
1604
  </Text>
1625
1605
  </View>
1626
-
1606
+
1627
1607
  <View style={styles.headerActions}>
1628
1608
  {/* View Mode Toggle */}
1629
1609
  <View style={[
1630
- styles.viewModeToggle,
1631
- {
1610
+ styles.viewModeToggle,
1611
+ {
1632
1612
  backgroundColor: themeStyles.isDarkTheme ? '#2A2A2A' : '#F8F9FA',
1633
1613
  borderWidth: 1,
1634
1614
  borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
@@ -1645,7 +1625,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1645
1625
  <TouchableOpacity
1646
1626
  style={[
1647
1627
  styles.viewModeButton,
1648
- viewMode === 'all' && {
1628
+ viewMode === 'all' && {
1649
1629
  backgroundColor: themeStyles.primaryColor,
1650
1630
  shadowColor: themeStyles.primaryColor,
1651
1631
  shadowOffset: {
@@ -1659,16 +1639,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1659
1639
  ]}
1660
1640
  onPress={() => setViewMode('all')}
1661
1641
  >
1662
- <Ionicons
1663
- name="folder"
1664
- size={18}
1665
- color={viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor}
1642
+ <Ionicons
1643
+ name="folder"
1644
+ size={18}
1645
+ color={viewMode === 'all' ? '#FFFFFF' : themeStyles.textColor}
1666
1646
  />
1667
1647
  </TouchableOpacity>
1668
1648
  <TouchableOpacity
1669
1649
  style={[
1670
1650
  styles.viewModeButton,
1671
- viewMode === 'photos' && {
1651
+ viewMode === 'photos' && {
1672
1652
  backgroundColor: themeStyles.primaryColor,
1673
1653
  shadowColor: themeStyles.primaryColor,
1674
1654
  shadowOffset: {
@@ -1682,19 +1662,19 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1682
1662
  ]}
1683
1663
  onPress={() => setViewMode('photos')}
1684
1664
  >
1685
- <Ionicons
1686
- name="images"
1687
- size={18}
1688
- color={viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor}
1665
+ <Ionicons
1666
+ name="images"
1667
+ size={18}
1668
+ color={viewMode === 'photos' ? '#FFFFFF' : themeStyles.textColor}
1689
1669
  />
1690
1670
  </TouchableOpacity>
1691
1671
  </View>
1692
-
1672
+
1693
1673
  {user?.id === targetUserId && (
1694
1674
  <TouchableOpacity
1695
1675
  style={[
1696
- styles.uploadButton,
1697
- {
1676
+ styles.uploadButton,
1677
+ {
1698
1678
  backgroundColor: themeStyles.primaryColor,
1699
1679
  shadowColor: themeStyles.primaryColor,
1700
1680
  shadowOffset: {
@@ -1730,9 +1710,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1730
1710
  {/* Search Bar */}
1731
1711
  {files.length > 0 && (viewMode === 'all' || files.some(f => f.contentType.startsWith('image/'))) && (
1732
1712
  <View style={[
1733
- styles.searchContainer,
1734
- {
1735
- backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1713
+ styles.searchContainer,
1714
+ {
1715
+ backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1736
1716
  borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1737
1717
  shadowColor: '#000000',
1738
1718
  shadowOffset: {
@@ -1753,7 +1733,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1753
1733
  onChangeText={setSearchQuery}
1754
1734
  />
1755
1735
  {searchQuery.length > 0 && (
1756
- <TouchableOpacity
1736
+ <TouchableOpacity
1757
1737
  onPress={() => setSearchQuery('')}
1758
1738
  style={styles.searchClearButton}
1759
1739
  >
@@ -1766,9 +1746,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
1766
1746
  {/* File Stats */}
1767
1747
  {files.length > 0 && (
1768
1748
  <View style={[
1769
- styles.statsContainer,
1770
- {
1771
- backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1749
+ styles.statsContainer,
1750
+ {
1751
+ backgroundColor: themeStyles.isDarkTheme ? '#1A1A1A' : '#FFFFFF',
1772
1752
  borderColor: themeStyles.isDarkTheme ? '#3A3A3A' : '#E8E9EA',
1773
1753
  shadowColor: '#000000',
1774
1754
  shadowOffset: {
@@ -2139,7 +2119,7 @@ const styles = StyleSheet.create({
2139
2119
  fontSize: 16,
2140
2120
  marginTop: 16,
2141
2121
  },
2142
-
2122
+
2143
2123
  // Modal styles
2144
2124
  modalContainer: {
2145
2125
  flex: 1,
@@ -2224,7 +2204,7 @@ const styles = StyleSheet.create({
2224
2204
  fontSize: 16,
2225
2205
  fontWeight: '600',
2226
2206
  },
2227
-
2207
+
2228
2208
  // Drag and Drop styles
2229
2209
  dragDropOverlay: {
2230
2210
  position: 'absolute',
@@ -2256,7 +2236,7 @@ const styles = StyleSheet.create({
2256
2236
  fontSize: 16,
2257
2237
  textAlign: 'center',
2258
2238
  },
2259
-
2239
+
2260
2240
  // File Viewer styles
2261
2241
  fileViewerContainer: {
2262
2242
  flex: 1,
@@ -2372,7 +2352,7 @@ const styles = StyleSheet.create({
2372
2352
  textAlign: 'center',
2373
2353
  fontStyle: 'italic',
2374
2354
  },
2375
-
2355
+
2376
2356
  // File Details in Viewer styles
2377
2357
  fileDetailsSection: {
2378
2358
  margin: 16,
@@ -2415,7 +2395,7 @@ const styles = StyleSheet.create({
2415
2395
  fontSize: 14,
2416
2396
  fontWeight: '600',
2417
2397
  },
2418
-
2398
+
2419
2399
  // Header styles
2420
2400
  headerActions: {
2421
2401
  flexDirection: 'row',
@@ -2437,7 +2417,7 @@ const styles = StyleSheet.create({
2437
2417
  justifyContent: 'center',
2438
2418
  marginHorizontal: 1,
2439
2419
  },
2440
-
2420
+
2441
2421
  // Photo Grid styles
2442
2422
  photoScrollContainer: {
2443
2423
  padding: 16,
@@ -2473,7 +2453,7 @@ const styles = StyleSheet.create({
2473
2453
  width: '100%',
2474
2454
  height: '100%',
2475
2455
  },
2476
-
2456
+
2477
2457
  // Justified Grid styles
2478
2458
  dimensionsLoadingIndicator: {
2479
2459
  flexDirection: 'row',
@@ -2510,7 +2490,7 @@ const styles = StyleSheet.create({
2510
2490
  height: '100%',
2511
2491
  borderRadius: 6,
2512
2492
  },
2513
-
2493
+
2514
2494
  // Simple Photo Grid styles
2515
2495
  simplePhotoItem: {
2516
2496
  borderRadius: 8,
@@ -2529,7 +2509,7 @@ const styles = StyleSheet.create({
2529
2509
  height: '100%',
2530
2510
  borderRadius: 8,
2531
2511
  },
2532
-
2512
+
2533
2513
  // Loading skeleton styles
2534
2514
  photoSkeletonGrid: {
2535
2515
  flexDirection: 'row',