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