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