@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.
- package/lib/commonjs/core/index.js +59 -0
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +23 -8
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.js +29 -10
- package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +19 -37
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +9 -27
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/store/index.js +5 -1
- package/lib/commonjs/ui/store/index.js.map +1 -1
- package/lib/module/core/index.js +59 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +23 -8
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.js +29 -10
- package/lib/module/ui/hooks/useFollow.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +19 -37
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +9 -27
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/store/index.js +5 -1
- package/lib/module/ui/store/index.js.map +1 -1
- package/lib/typescript/core/index.d.ts +28 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/store/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/FollowButton.tsx +25 -11
- package/src/ui/hooks/useFollow.ts +30 -10
- package/src/ui/screens/AppInfoScreen.tsx +23 -40
- package/src/ui/screens/FileManagementScreen.tsx +248 -268
- 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
|
|
443
|
-
const
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
{
|
|
1172
|
-
|
|
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
|
-
<
|
|
1176
|
-
|
|
1177
|
-
|
|
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.
|
|
1185
|
-
<
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
{
|
|
1190
|
-
|
|
1191
|
-
|
|
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
|
-
|
|
1157
|
+
Type:
|
|
1197
1158
|
</Text>
|
|
1198
1159
|
<Text style={[styles.detailValue, { color: themeStyles.textColor }]}>
|
|
1199
|
-
{selectedFile.
|
|
1160
|
+
{selectedFile.contentType}
|
|
1200
1161
|
</Text>
|
|
1201
1162
|
</View>
|
|
1202
|
-
)}
|
|
1203
|
-
</View>
|
|
1204
1163
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
{
|
|
1185
|
+
<View style={styles.modalActions}>
|
|
1218
1186
|
<TouchableOpacity
|
|
1219
|
-
style={[styles.modalActionButton, { backgroundColor: themeStyles.
|
|
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="
|
|
1226
|
-
<Text style={styles.modalActionText}>
|
|
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
|
-
</
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
</
|
|
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
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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',
|