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