@oxyhq/services 6.9.4 → 6.9.5
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/ui/client.js +0 -7
- package/lib/commonjs/ui/client.js.map +1 -1
- package/lib/commonjs/ui/components/feedback/FormInput.js.map +1 -1
- package/lib/commonjs/ui/components/icon/OxyIcon.js.map +1 -1
- package/lib/commonjs/ui/components/types.js +4 -0
- package/lib/commonjs/ui/screens/AppInfoScreen.js +66 -60
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +139 -79
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +39 -29
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/client.js +0 -1
- package/lib/module/ui/client.js.map +1 -1
- package/lib/module/ui/components/feedback/FormInput.js.map +1 -1
- package/lib/module/ui/components/icon/OxyIcon.js.map +1 -1
- package/lib/module/ui/components/types.js +2 -0
- package/lib/module/ui/screens/AppInfoScreen.js +66 -60
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +139 -79
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +39 -29
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/typescript/commonjs/ui/client.d.ts +0 -1
- package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/types.d.ts +18 -17
- package/lib/typescript/commonjs/ui/components/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AppInfoScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/client.d.ts +0 -1
- package/lib/typescript/module/ui/client.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/types.d.ts +18 -17
- package/lib/typescript/module/ui/components/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AppInfoScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/client.ts +0 -1
- package/src/ui/components/feedback/FormInput.tsx +1 -1
- package/src/ui/components/icon/OxyIcon.tsx +1 -1
- package/src/ui/components/types.tsx +19 -17
- package/src/ui/screens/AppInfoScreen.tsx +63 -61
- package/src/ui/screens/FileManagementScreen.tsx +130 -121
- package/src/ui/screens/SessionManagementScreen.tsx +30 -28
- package/lib/commonjs/ui/components/AnimationExample.js +0 -213
- package/lib/commonjs/ui/components/AnimationExample.js.map +0 -1
- package/lib/commonjs/ui/components/ErrorBoundary.js +0 -145
- package/lib/commonjs/ui/components/ErrorBoundary.js.map +0 -1
- package/lib/commonjs/ui/components/WebOxyProvider.js +0 -106
- package/lib/commonjs/ui/components/WebOxyProvider.js.map +0 -1
- package/lib/module/ui/components/AnimationExample.js +0 -209
- package/lib/module/ui/components/AnimationExample.js.map +0 -1
- package/lib/module/ui/components/ErrorBoundary.js +0 -139
- package/lib/module/ui/components/ErrorBoundary.js.map +0 -1
- package/lib/module/ui/components/WebOxyProvider.js +0 -102
- package/lib/module/ui/components/WebOxyProvider.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/AnimationExample.d.ts +0 -4
- package/lib/typescript/commonjs/ui/components/AnimationExample.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/ErrorBoundary.d.ts +0 -31
- package/lib/typescript/commonjs/ui/components/ErrorBoundary.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts +0 -52
- package/lib/typescript/commonjs/ui/components/WebOxyProvider.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/AnimationExample.d.ts +0 -4
- package/lib/typescript/module/ui/components/AnimationExample.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/ErrorBoundary.d.ts +0 -31
- package/lib/typescript/module/ui/components/ErrorBoundary.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/WebOxyProvider.d.ts +0 -52
- package/lib/typescript/module/ui/components/WebOxyProvider.d.ts.map +0 -1
- package/src/ui/components/AnimationExample.tsx +0 -195
- package/src/ui/components/ErrorBoundary.tsx +0 -154
- package/src/ui/components/WebOxyProvider.tsx +0 -117
|
@@ -41,6 +41,7 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
|
|
|
41
41
|
import { useColorScheme } from '../hooks/useColorScheme';
|
|
42
42
|
import { normalizeTheme } from '../utils/themeUtils';
|
|
43
43
|
import { useOxy } from '../context/OxyContext';
|
|
44
|
+
import { useI18n } from '../hooks/useI18n';
|
|
44
45
|
import { useUploadFile } from '../hooks/mutations/useAccountMutations';
|
|
45
46
|
import {
|
|
46
47
|
confirmAction,
|
|
@@ -55,6 +56,10 @@ import { UploadPreview } from '../components/fileManagement/UploadPreview';
|
|
|
55
56
|
import { fileManagementStyles } from '../components/fileManagement/styles';
|
|
56
57
|
import type { OnConfirmFileSelection } from '../types/fileManagement';
|
|
57
58
|
|
|
59
|
+
/** Extract error message from unknown error type */
|
|
60
|
+
const getErrorMessage = (error: unknown): string | undefined =>
|
|
61
|
+
error instanceof Error ? getErrorMessage(error) : typeof error === 'string' ? error : undefined;
|
|
62
|
+
|
|
58
63
|
// Animated button component for smooth transitions
|
|
59
64
|
const AnimatedButton: React.FC<{
|
|
60
65
|
isSelected: boolean;
|
|
@@ -62,7 +67,7 @@ const AnimatedButton: React.FC<{
|
|
|
62
67
|
icon: string;
|
|
63
68
|
primaryColor: string;
|
|
64
69
|
textColor: string;
|
|
65
|
-
style:
|
|
70
|
+
style: Record<string, unknown>;
|
|
66
71
|
}> = ({ isSelected, onPress, icon, primaryColor, textColor, style }) => {
|
|
67
72
|
const animatedValue = useRef(new Animated.Value(isSelected ? 1 : 0)).current;
|
|
68
73
|
|
|
@@ -97,7 +102,7 @@ const AnimatedButton: React.FC<{
|
|
|
97
102
|
>
|
|
98
103
|
<Animated.View>
|
|
99
104
|
<MaterialCommunityIcons
|
|
100
|
-
name={icon as
|
|
105
|
+
name={icon as React.ComponentProps<typeof MaterialCommunityIcons>['name']}
|
|
101
106
|
size={16}
|
|
102
107
|
color={isSelected ? '#FFFFFF' : textColor}
|
|
103
108
|
/>
|
|
@@ -128,6 +133,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
128
133
|
}) => {
|
|
129
134
|
// Use useOxy() hook for OxyContext values
|
|
130
135
|
const { user, oxyServices } = useOxy();
|
|
136
|
+
const { t } = useI18n();
|
|
131
137
|
const uploadFileMutation = useUploadFile();
|
|
132
138
|
const files = useFiles();
|
|
133
139
|
// Ensure containerWidth is a number (TypeScript guard)
|
|
@@ -226,13 +232,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
226
232
|
if (disabledMimeTypes.length) {
|
|
227
233
|
const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
|
|
228
234
|
if (blocked) {
|
|
229
|
-
toast.error('
|
|
235
|
+
toast.error(t('fileManagement.toasts.fileTypeBlocked'));
|
|
230
236
|
return;
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
// Update file visibility if it differs from defaultVisibility
|
|
235
|
-
const fileVisibility = (file.metadata as
|
|
241
|
+
const fileVisibility = (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
|
|
236
242
|
if (fileVisibility !== defaultVisibility) {
|
|
237
243
|
try {
|
|
238
244
|
await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
|
|
@@ -253,7 +259,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
253
259
|
linkContext.entityType,
|
|
254
260
|
linkContext.entityId,
|
|
255
261
|
defaultVisibility,
|
|
256
|
-
(linkContext as
|
|
262
|
+
(linkContext as Record<string, unknown>).webhookUrl
|
|
257
263
|
);
|
|
258
264
|
} catch (error) {
|
|
259
265
|
// Continue anyway - selection shouldn't fail if linking fails
|
|
@@ -274,7 +280,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
274
280
|
const already = next.has(file.id);
|
|
275
281
|
if (!already) {
|
|
276
282
|
if (maxSelection && next.size >= maxSelection) {
|
|
277
|
-
toast.error(
|
|
283
|
+
toast.error(t('fileManagement.toasts.maxSelection', { max: maxSelection }));
|
|
278
284
|
return prev;
|
|
279
285
|
}
|
|
280
286
|
next.add(file.id);
|
|
@@ -294,7 +300,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
294
300
|
// Update visibility and link files if needed
|
|
295
301
|
const updatePromises = chosen.map(async (file) => {
|
|
296
302
|
// Update visibility if needed
|
|
297
|
-
const fileVisibility = (file.metadata as
|
|
303
|
+
const fileVisibility = (file.metadata as Record<string, unknown> | undefined)?.visibility || 'private';
|
|
298
304
|
if (fileVisibility !== defaultVisibility) {
|
|
299
305
|
try {
|
|
300
306
|
await oxyServices.assetUpdateVisibility(file.id, defaultVisibility);
|
|
@@ -312,7 +318,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
312
318
|
linkContext.entityType,
|
|
313
319
|
linkContext.entityId,
|
|
314
320
|
defaultVisibility,
|
|
315
|
-
(linkContext as
|
|
321
|
+
(linkContext as Record<string, unknown>).webhookUrl
|
|
316
322
|
);
|
|
317
323
|
} catch (error) {
|
|
318
324
|
// File linking failed, continue with selection
|
|
@@ -381,7 +387,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
381
387
|
const currentPaging = mode === 'more' ? (prevPagingRef.current ?? paging) : paging;
|
|
382
388
|
const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
|
|
383
389
|
const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
|
|
384
|
-
const assets: FileMetadata[] = (response.files || []).map((f:
|
|
390
|
+
const assets: FileMetadata[] = (response.files || []).map((f: Record<string, unknown>) => ({
|
|
385
391
|
id: f.id,
|
|
386
392
|
filename: f.originalName || f.sha256,
|
|
387
393
|
contentType: f.mime,
|
|
@@ -411,8 +417,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
411
417
|
loadingMore: false,
|
|
412
418
|
}));
|
|
413
419
|
}
|
|
414
|
-
} catch (error:
|
|
415
|
-
toast.error(error
|
|
420
|
+
} catch (error: unknown) {
|
|
421
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.loadFailed'));
|
|
416
422
|
} finally {
|
|
417
423
|
setLoading(false);
|
|
418
424
|
setRefreshing(false);
|
|
@@ -508,7 +514,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
508
514
|
const oversizedFiles = selectedFiles.filter(file => file.size > maxSize);
|
|
509
515
|
if (oversizedFiles.length > 0) {
|
|
510
516
|
const fileList = oversizedFiles.map(f => f.name).join(', ');
|
|
511
|
-
toast.error(
|
|
517
|
+
toast.error(t('fileManagement.toasts.filesTooLarge', { files: fileList }));
|
|
512
518
|
return [];
|
|
513
519
|
}
|
|
514
520
|
let successCount = 0;
|
|
@@ -570,16 +576,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
570
576
|
successCount++;
|
|
571
577
|
} else {
|
|
572
578
|
// Fallback: will reconcile on later list refresh
|
|
573
|
-
useFileStore.getState().updateFile(optimisticId, { metadata: { uploading: false } as
|
|
579
|
+
useFileStore.getState().updateFile(optimisticId, { metadata: { uploading: false } as Partial<FileMetadata>['metadata'] });
|
|
574
580
|
if (__DEV__) {
|
|
575
581
|
console.warn('Upload completed but no file data returned:', { fileName, result });
|
|
576
582
|
}
|
|
577
583
|
// Still count as success if upload didn't throw
|
|
578
584
|
successCount++;
|
|
579
585
|
}
|
|
580
|
-
} catch (error:
|
|
586
|
+
} catch (error: unknown) {
|
|
581
587
|
failureCount++;
|
|
582
|
-
const errorMessage = error
|
|
588
|
+
const errorMessage = getErrorMessage(error) || String(error) || 'Upload failed';
|
|
583
589
|
const fullError = `${fileName}: ${errorMessage}`;
|
|
584
590
|
errors.push(fullError);
|
|
585
591
|
if (__DEV__) {
|
|
@@ -588,7 +594,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
588
594
|
fileSize: raw.size,
|
|
589
595
|
fileType: raw.type,
|
|
590
596
|
error: errorMessage,
|
|
591
|
-
stack: error.stack
|
|
597
|
+
stack: (error instanceof Error) ? error.stack : undefined
|
|
592
598
|
});
|
|
593
599
|
}
|
|
594
600
|
|
|
@@ -599,26 +605,27 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
599
605
|
|
|
600
606
|
// Show success/error messages
|
|
601
607
|
if (successCount > 0) {
|
|
602
|
-
toast.success(
|
|
608
|
+
toast.success(t('fileManagement.toasts.uploadSuccess', { count: successCount }));
|
|
603
609
|
}
|
|
604
610
|
if (failureCount > 0) {
|
|
605
611
|
// Show detailed error message with first few errors
|
|
606
612
|
const errorDetails = errors.length > 0
|
|
607
613
|
? `\n${errors.slice(0, 3).join('\n')}${errors.length > 3 ? `\n...and ${errors.length - 3} more` : ''}`
|
|
608
614
|
: '';
|
|
609
|
-
toast.error(`${
|
|
615
|
+
toast.error(`${t('fileManagement.toasts.uploadFailed', { count: failureCount })}${errorDetails}`);
|
|
610
616
|
}
|
|
611
617
|
// Silent background refresh to ensure metadata/variants updated
|
|
612
618
|
setTimeout(() => { loadFiles('silent'); }, 1200);
|
|
613
|
-
} catch (error:
|
|
614
|
-
toast.error(error
|
|
619
|
+
} catch (error: unknown) {
|
|
620
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.uploadError'));
|
|
615
621
|
} finally {
|
|
616
622
|
storeSetUploadProgress(null);
|
|
617
623
|
}
|
|
618
624
|
return uploadedFiles;
|
|
619
625
|
};
|
|
620
626
|
|
|
621
|
-
|
|
627
|
+
// biome-ignore lint/suspicious/noExplicitAny: Files from document picker may have extra properties like uri
|
|
628
|
+
const handleFileSelection = useCallback(async (selectedFiles: Array<File | any>) => {
|
|
622
629
|
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
|
|
623
630
|
const processedFiles: Array<{ file: File | Blob; preview?: string; size: number; name: string; type: string }> = [];
|
|
624
631
|
|
|
@@ -628,7 +635,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
628
635
|
if (__DEV__) {
|
|
629
636
|
console.error('Invalid file: file is null or undefined');
|
|
630
637
|
}
|
|
631
|
-
toast.error('
|
|
638
|
+
toast.error(t('fileManagement.toasts.invalidFileMissing'));
|
|
632
639
|
continue;
|
|
633
640
|
}
|
|
634
641
|
|
|
@@ -636,7 +643,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
636
643
|
if (__DEV__) {
|
|
637
644
|
console.error('Invalid file: missing or invalid name property', file);
|
|
638
645
|
}
|
|
639
|
-
toast.error('
|
|
646
|
+
toast.error(t('fileManagement.toasts.invalidFileName'));
|
|
640
647
|
continue;
|
|
641
648
|
}
|
|
642
649
|
|
|
@@ -644,7 +651,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
644
651
|
if (__DEV__) {
|
|
645
652
|
console.error('Invalid file: missing or invalid size property', file);
|
|
646
653
|
}
|
|
647
|
-
toast.error(
|
|
654
|
+
toast.error(t('fileManagement.toasts.invalidFileSize', { name: file.name || 'unknown' }));
|
|
648
655
|
continue;
|
|
649
656
|
}
|
|
650
657
|
|
|
@@ -652,13 +659,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
652
659
|
if (__DEV__) {
|
|
653
660
|
console.error('Invalid file: file size is zero or negative', file);
|
|
654
661
|
}
|
|
655
|
-
toast.error(
|
|
662
|
+
toast.error(t('fileManagement.toasts.fileEmpty', { name: file.name }));
|
|
656
663
|
continue;
|
|
657
664
|
}
|
|
658
665
|
|
|
659
666
|
// Validate file size
|
|
660
667
|
if (file.size > MAX_FILE_SIZE) {
|
|
661
|
-
toast.error(
|
|
668
|
+
toast.error(t('fileManagement.toasts.fileTooLarge', { name: file.name, maxSize: formatFileSize(MAX_FILE_SIZE) }));
|
|
662
669
|
continue;
|
|
663
670
|
}
|
|
664
671
|
|
|
@@ -669,7 +676,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
669
676
|
let preview: string | undefined;
|
|
670
677
|
if (fileType.startsWith('image/')) {
|
|
671
678
|
// Try to use file URI from expo-document-picker if available (works on all platforms)
|
|
672
|
-
const fileUri = (file as
|
|
679
|
+
const fileUri = (file as File & { uri?: string }).uri;
|
|
673
680
|
if (fileUri && typeof fileUri === 'string' &&
|
|
674
681
|
(fileUri.startsWith('file://') || fileUri.startsWith('content://') ||
|
|
675
682
|
fileUri.startsWith('http://') || fileUri.startsWith('https://') ||
|
|
@@ -678,10 +685,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
678
685
|
} else {
|
|
679
686
|
// Fallback: create blob URL if possible (works on web)
|
|
680
687
|
try {
|
|
681
|
-
if (file instanceof File || file instanceof Blob) {
|
|
688
|
+
if ((file as object) instanceof File || (file as object) instanceof Blob) {
|
|
682
689
|
preview = URL.createObjectURL(file);
|
|
683
690
|
}
|
|
684
|
-
} catch (error:
|
|
691
|
+
} catch (error: unknown) {
|
|
685
692
|
if (__DEV__) {
|
|
686
693
|
console.warn('Failed to create preview URL:', error);
|
|
687
694
|
}
|
|
@@ -700,7 +707,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
700
707
|
}
|
|
701
708
|
|
|
702
709
|
if (processedFiles.length === 0) {
|
|
703
|
-
toast.error('
|
|
710
|
+
toast.error(t('fileManagement.toasts.noValidFiles'));
|
|
704
711
|
return;
|
|
705
712
|
}
|
|
706
713
|
|
|
@@ -755,8 +762,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
755
762
|
}
|
|
756
763
|
|
|
757
764
|
endUpload();
|
|
758
|
-
} catch (error:
|
|
759
|
-
toast.error(error
|
|
765
|
+
} catch (error: unknown) {
|
|
766
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.uploadError'));
|
|
760
767
|
endUpload();
|
|
761
768
|
}
|
|
762
769
|
};
|
|
@@ -791,7 +798,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
791
798
|
const handleFileUpload = async () => {
|
|
792
799
|
// Prevent concurrent document picker calls
|
|
793
800
|
if (isPickingDocument) {
|
|
794
|
-
toast.error('
|
|
801
|
+
toast.error(t('fileManagement.toasts.waitForSelection'));
|
|
795
802
|
return;
|
|
796
803
|
}
|
|
797
804
|
|
|
@@ -816,7 +823,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
816
823
|
|
|
817
824
|
if (!result.assets || result.assets.length === 0) {
|
|
818
825
|
setIsPickingDocument(false);
|
|
819
|
-
toast.error('
|
|
826
|
+
toast.error(t('fileManagement.toasts.noFilesSelected'));
|
|
820
827
|
return;
|
|
821
828
|
}
|
|
822
829
|
|
|
@@ -845,8 +852,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
845
852
|
}
|
|
846
853
|
return null;
|
|
847
854
|
})
|
|
848
|
-
.catch((error:
|
|
849
|
-
errors.push(`File "${doc.name || 'file'}": ${error
|
|
855
|
+
.catch((error: unknown) => {
|
|
856
|
+
errors.push(`File "${doc.name || 'file'}": ${getErrorMessage(error) || 'Failed to process'}`);
|
|
850
857
|
return null;
|
|
851
858
|
})
|
|
852
859
|
);
|
|
@@ -863,7 +870,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
863
870
|
// Show errors if any
|
|
864
871
|
if (errors.length > 0) {
|
|
865
872
|
const errorMessage = errors.slice(0, 3).join('\n') + (errors.length > 3 ? `\n...and ${errors.length - 3} more` : '');
|
|
866
|
-
toast.error(
|
|
873
|
+
toast.error(t('fileManagement.toasts.loadSomeFailed', { errors: errorMessage }));
|
|
867
874
|
}
|
|
868
875
|
|
|
869
876
|
// Process successfully converted files
|
|
@@ -871,20 +878,20 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
871
878
|
await handleFileSelection(files);
|
|
872
879
|
} else {
|
|
873
880
|
// Files were selected but none could be converted
|
|
874
|
-
toast.error('
|
|
881
|
+
toast.error(t('fileManagement.toasts.noFilesProcessed'));
|
|
875
882
|
}
|
|
876
|
-
} catch (error:
|
|
883
|
+
} catch (error: unknown) {
|
|
877
884
|
if (__DEV__) {
|
|
878
885
|
console.error('File upload error:', error);
|
|
879
886
|
}
|
|
880
|
-
if (error
|
|
881
|
-
if (error
|
|
882
|
-
toast.error('
|
|
887
|
+
if (getErrorMessage(error)?.includes('expo-document-picker') || getErrorMessage(error)?.includes('Different document picking in progress')) {
|
|
888
|
+
if (getErrorMessage(error)?.includes('Different document picking in progress')) {
|
|
889
|
+
toast.error(t('fileManagement.toasts.waitForSelection'));
|
|
883
890
|
} else {
|
|
884
|
-
toast.error('
|
|
891
|
+
toast.error(t('fileManagement.toasts.filePickerNotAvailable'));
|
|
885
892
|
}
|
|
886
893
|
} else {
|
|
887
|
-
toast.error(error
|
|
894
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.selectFilesFailed'));
|
|
888
895
|
}
|
|
889
896
|
} finally {
|
|
890
897
|
// Always reset the picking state, even if there was an error
|
|
@@ -895,10 +902,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
895
902
|
const handleFileDelete = async (fileId: string, filename: string) => {
|
|
896
903
|
// Use platform-aware confirmation dialog
|
|
897
904
|
const confirmed = await confirmAction(
|
|
898
|
-
|
|
899
|
-
'
|
|
900
|
-
'
|
|
901
|
-
'
|
|
905
|
+
t('fileManagement.confirms.deleteFile', { filename }),
|
|
906
|
+
t('fileManagement.deleteFile'),
|
|
907
|
+
t('fileManagement.confirm'),
|
|
908
|
+
t('common.cancel')
|
|
902
909
|
);
|
|
903
910
|
|
|
904
911
|
if (!confirmed) {
|
|
@@ -909,24 +916,24 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
909
916
|
storeSetDeleting(fileId);
|
|
910
917
|
await oxyServices.deleteFile(fileId);
|
|
911
918
|
|
|
912
|
-
toast.success('
|
|
919
|
+
toast.success(t('fileManagement.toasts.deleteSuccess'));
|
|
913
920
|
|
|
914
921
|
// Reload files after successful deletion
|
|
915
922
|
// Optimistic remove
|
|
916
923
|
useFileStore.getState().removeFile(fileId);
|
|
917
924
|
// Silent background reconcile
|
|
918
925
|
setTimeout(() => loadFiles('silent'), 800);
|
|
919
|
-
} catch (error:
|
|
926
|
+
} catch (error: unknown) {
|
|
920
927
|
|
|
921
928
|
// Provide specific error messages
|
|
922
|
-
if (error
|
|
923
|
-
toast.error('
|
|
929
|
+
if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
|
|
930
|
+
toast.error(t('fileManagement.toasts.fileNotFound'));
|
|
924
931
|
// Still reload files to refresh the list
|
|
925
932
|
setTimeout(() => loadFiles('silent'), 800);
|
|
926
|
-
} else if (error
|
|
927
|
-
toast.error('
|
|
933
|
+
} else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
|
|
934
|
+
toast.error(t('fileManagement.toasts.noPermission'));
|
|
928
935
|
} else {
|
|
929
|
-
toast.error(error
|
|
936
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
|
|
930
937
|
}
|
|
931
938
|
} finally {
|
|
932
939
|
storeSetDeleting(null);
|
|
@@ -941,10 +948,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
941
948
|
const selectedFiles = Array.from(selectedIds).map(id => fileMap[id]).filter(Boolean);
|
|
942
949
|
|
|
943
950
|
const confirmed = await confirmAction(
|
|
944
|
-
|
|
945
|
-
'
|
|
946
|
-
'
|
|
947
|
-
'
|
|
951
|
+
t('fileManagement.confirms.deleteFiles', { count: selectedFiles.length }),
|
|
952
|
+
t('fileManagement.deleteFiles'),
|
|
953
|
+
t('fileManagement.confirm'),
|
|
954
|
+
t('common.cancel')
|
|
948
955
|
);
|
|
949
956
|
|
|
950
957
|
if (!confirmed) return;
|
|
@@ -955,7 +962,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
955
962
|
await oxyServices.deleteFile(fileId);
|
|
956
963
|
useFileStore.getState().removeFile(fileId);
|
|
957
964
|
return { success: true, fileId };
|
|
958
|
-
} catch (error:
|
|
965
|
+
} catch (error: unknown) {
|
|
959
966
|
return { success: false, fileId, error };
|
|
960
967
|
}
|
|
961
968
|
});
|
|
@@ -965,16 +972,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
965
972
|
const failed = results.length - successful;
|
|
966
973
|
|
|
967
974
|
if (successful > 0) {
|
|
968
|
-
toast.success(
|
|
975
|
+
toast.success(t('fileManagement.toasts.bulkDeleteSuccess', { count: successful }));
|
|
969
976
|
}
|
|
970
977
|
if (failed > 0) {
|
|
971
|
-
toast.error(
|
|
978
|
+
toast.error(t('fileManagement.toasts.bulkDeleteFailed', { count: failed }));
|
|
972
979
|
}
|
|
973
980
|
|
|
974
981
|
setSelectedIds(new Set());
|
|
975
982
|
setTimeout(() => loadFiles('silent'), 800);
|
|
976
|
-
} catch (error:
|
|
977
|
-
toast.error(error
|
|
983
|
+
} catch (error: unknown) {
|
|
984
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
|
|
978
985
|
}
|
|
979
986
|
}, [selectedIds, files, oxyServices, loadFiles]);
|
|
980
987
|
|
|
@@ -986,7 +993,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
986
993
|
try {
|
|
987
994
|
await oxyServices.assetUpdateVisibility(fileId, visibility);
|
|
988
995
|
return { success: true, fileId };
|
|
989
|
-
} catch (error:
|
|
996
|
+
} catch (error: unknown) {
|
|
990
997
|
return { success: false, fileId, error };
|
|
991
998
|
}
|
|
992
999
|
});
|
|
@@ -996,21 +1003,21 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
996
1003
|
const failed = results.length - successful;
|
|
997
1004
|
|
|
998
1005
|
if (successful > 0) {
|
|
999
|
-
toast.success(
|
|
1006
|
+
toast.success(t('fileManagement.toasts.visibilitySuccess', { count: successful, visibility }));
|
|
1000
1007
|
// Update file metadata in store
|
|
1001
1008
|
Array.from(selectedIds).forEach(fileId => {
|
|
1002
1009
|
useFileStore.getState().updateFile(fileId, {
|
|
1003
|
-
metadata: { ...files.find(f => f.id === fileId)?.metadata, visibility }
|
|
1004
|
-
}
|
|
1010
|
+
metadata: { ...files.find(f => f.id === fileId)?.metadata, visibility } as Partial<FileMetadata>['metadata']
|
|
1011
|
+
});
|
|
1005
1012
|
});
|
|
1006
1013
|
}
|
|
1007
1014
|
if (failed > 0) {
|
|
1008
|
-
toast.error(
|
|
1015
|
+
toast.error(t('fileManagement.toasts.visibilityFailed', { count: failed }));
|
|
1009
1016
|
}
|
|
1010
1017
|
|
|
1011
1018
|
setTimeout(() => loadFiles('silent'), 800);
|
|
1012
|
-
} catch (error:
|
|
1013
|
-
toast.error(error
|
|
1019
|
+
} catch (error: unknown) {
|
|
1020
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
|
|
1014
1021
|
}
|
|
1015
1022
|
}, [selectedIds, oxyServices, files, loadFiles]);
|
|
1016
1023
|
|
|
@@ -1032,7 +1039,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1032
1039
|
document.body.appendChild(link);
|
|
1033
1040
|
link.click();
|
|
1034
1041
|
document.body.removeChild(link);
|
|
1035
|
-
toast.success('
|
|
1042
|
+
toast.success(t('fileManagement.toasts.downloadStarted'));
|
|
1036
1043
|
} catch (linkError) {
|
|
1037
1044
|
// Fallback to authenticated download
|
|
1038
1045
|
const blob = await oxyServices.getFileContentAsBlob(fileId);
|
|
@@ -1047,16 +1054,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1047
1054
|
|
|
1048
1055
|
// Clean up the blob URL
|
|
1049
1056
|
URL.revokeObjectURL(url);
|
|
1050
|
-
toast.success('
|
|
1057
|
+
toast.success(t('fileManagement.toasts.downloadSuccess'));
|
|
1051
1058
|
}
|
|
1052
1059
|
} else {
|
|
1053
1060
|
// For mobile, open the URL (user can save from browser)
|
|
1054
1061
|
// Note: This is a simplified approach - for full mobile support,
|
|
1055
1062
|
// consider using expo-file-system or react-native-fs
|
|
1056
|
-
toast.info('
|
|
1063
|
+
toast.info(t('fileManagement.toasts.downloadMobile'));
|
|
1057
1064
|
}
|
|
1058
|
-
} catch (error:
|
|
1059
|
-
toast.error(error
|
|
1065
|
+
} catch (error: unknown) {
|
|
1066
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
|
|
1060
1067
|
}
|
|
1061
1068
|
};
|
|
1062
1069
|
|
|
@@ -1094,11 +1101,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1094
1101
|
const content = await oxyServices.getFileContentAsText(file.id);
|
|
1095
1102
|
setFileContent(content);
|
|
1096
1103
|
}
|
|
1097
|
-
} catch (error:
|
|
1098
|
-
if (error
|
|
1099
|
-
toast.error('
|
|
1104
|
+
} catch (error: unknown) {
|
|
1105
|
+
if (getErrorMessage(error)?.includes('404') || getErrorMessage(error)?.includes('not found')) {
|
|
1106
|
+
toast.error(t('fileManagement.toasts.fileNotFoundContent'));
|
|
1100
1107
|
} else {
|
|
1101
|
-
toast.error('
|
|
1108
|
+
toast.error(t('fileManagement.toasts.loadContentFailed'));
|
|
1102
1109
|
}
|
|
1103
1110
|
setFileContent(null);
|
|
1104
1111
|
}
|
|
@@ -1106,8 +1113,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1106
1113
|
// For non-viewable files, don't load content
|
|
1107
1114
|
setFileContent(null);
|
|
1108
1115
|
}
|
|
1109
|
-
} catch (error:
|
|
1110
|
-
toast.error(error
|
|
1116
|
+
} catch (error: unknown) {
|
|
1117
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.openFailed'));
|
|
1111
1118
|
} finally {
|
|
1112
1119
|
setLoadingFileContent(false);
|
|
1113
1120
|
}
|
|
@@ -1275,7 +1282,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1275
1282
|
contentFit="cover"
|
|
1276
1283
|
transition={120}
|
|
1277
1284
|
cachePolicy="memory-disk"
|
|
1278
|
-
onError={(_:
|
|
1285
|
+
onError={(_: unknown) => {
|
|
1279
1286
|
// If thumbnail not available, we still show icon overlay
|
|
1280
1287
|
}}
|
|
1281
1288
|
accessibilityLabel={`${file.filename} video thumbnail`}
|
|
@@ -1290,7 +1297,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1290
1297
|
style={[fileManagementStyles.fallbackIcon, { display: isImage ? 'none' : 'flex' }]}
|
|
1291
1298
|
>
|
|
1292
1299
|
<Ionicons
|
|
1293
|
-
name={getFileIcon(file.contentType) as
|
|
1300
|
+
name={getFileIcon(file.contentType) as React.ComponentProps<typeof Ionicons>['name']}
|
|
1294
1301
|
size={32}
|
|
1295
1302
|
color={themeStyles.primaryColor}
|
|
1296
1303
|
/>
|
|
@@ -1305,7 +1312,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1305
1312
|
) : (
|
|
1306
1313
|
<View style={fileManagementStyles.fileIconContainer}>
|
|
1307
1314
|
<Ionicons
|
|
1308
|
-
name={getFileIcon(file.contentType) as
|
|
1315
|
+
name={getFileIcon(file.contentType) as React.ComponentProps<typeof Ionicons>['name']}
|
|
1309
1316
|
size={32}
|
|
1310
1317
|
color={themeStyles.primaryColor}
|
|
1311
1318
|
/>
|
|
@@ -1371,7 +1378,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1371
1378
|
};
|
|
1372
1379
|
|
|
1373
1380
|
// GroupedSection-based file items (for 'all' view) replacing legacy flat list look
|
|
1374
|
-
|
|
1381
|
+
// biome-ignore lint/suspicious/noExplicitAny: GroupedSection items have dynamic props
|
|
1382
|
+
const groupedFileItems: any[] = useMemo(() => {
|
|
1375
1383
|
// filteredFiles is already sorted, so just use it directly
|
|
1376
1384
|
const sortedFiles = filteredFiles;
|
|
1377
1385
|
|
|
@@ -1415,7 +1423,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1415
1423
|
contentFit="cover"
|
|
1416
1424
|
transition={120}
|
|
1417
1425
|
cachePolicy="memory-disk"
|
|
1418
|
-
onError={(_:
|
|
1426
|
+
onError={(_: unknown) => {
|
|
1419
1427
|
// If thumbnail not available, we still show icon overlay
|
|
1420
1428
|
}}
|
|
1421
1429
|
accessibilityLabel={`${file.filename} video thumbnail`}
|
|
@@ -1498,6 +1506,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1498
1506
|
{file.metadata.description}
|
|
1499
1507
|
</Text>
|
|
1500
1508
|
) : undefined,
|
|
1509
|
+
// biome-ignore lint/suspicious/noExplicitAny: GroupedSectionItem has dynamic properties
|
|
1501
1510
|
} as any;
|
|
1502
1511
|
});
|
|
1503
1512
|
}, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrlCallback, selectMode, selectedIds]);
|
|
@@ -1639,11 +1648,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1639
1648
|
return (
|
|
1640
1649
|
<View style={fileManagementStyles.emptyState}>
|
|
1641
1650
|
<Ionicons name="images-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
|
|
1642
|
-
<Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>
|
|
1651
|
+
<Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>{t('fileManagement.emptyPhotos.title')}</Text>
|
|
1643
1652
|
<Text style={[fileManagementStyles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}> {
|
|
1644
1653
|
user?.id === targetUserId
|
|
1645
|
-
?
|
|
1646
|
-
:
|
|
1654
|
+
? t('fileManagement.emptyPhotos.ownDescription')
|
|
1655
|
+
: t('fileManagement.emptyPhotos.otherDescription')
|
|
1647
1656
|
} </Text>
|
|
1648
1657
|
{user?.id === targetUserId && (
|
|
1649
1658
|
<TouchableOpacity
|
|
@@ -1658,7 +1667,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1658
1667
|
) : (
|
|
1659
1668
|
<>
|
|
1660
1669
|
<Ionicons name="cloud-upload" size={20} color="#FFFFFF" />
|
|
1661
|
-
<Text style={fileManagementStyles.emptyStateButtonText}>
|
|
1670
|
+
<Text style={fileManagementStyles.emptyStateButtonText}>{t('fileManagement.uploadPhotos')}</Text>
|
|
1662
1671
|
</>
|
|
1663
1672
|
)}
|
|
1664
1673
|
</TouchableOpacity>
|
|
@@ -1692,7 +1701,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1692
1701
|
{loadingDimensions && (
|
|
1693
1702
|
<View style={fileManagementStyles.dimensionsLoadingIndicator}>
|
|
1694
1703
|
<ActivityIndicator size="small" color={themeStyles.primaryColor} />
|
|
1695
|
-
<Text style={[fileManagementStyles.dimensionsLoadingText, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
|
|
1704
|
+
<Text style={[fileManagementStyles.dimensionsLoadingText, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>{t('fileManagement.loadingPhotoLayout')}</Text>
|
|
1696
1705
|
</View>
|
|
1697
1706
|
)}
|
|
1698
1707
|
|
|
@@ -1733,11 +1742,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1733
1742
|
const renderEmptyState = () => (
|
|
1734
1743
|
<View style={fileManagementStyles.emptyState}>
|
|
1735
1744
|
<Ionicons name="folder-open-outline" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
|
|
1736
|
-
<Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>
|
|
1745
|
+
<Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>{t('fileManagement.emptyFiles.title')}</Text>
|
|
1737
1746
|
<Text style={[fileManagementStyles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
|
|
1738
1747
|
{user?.id === targetUserId
|
|
1739
|
-
?
|
|
1740
|
-
:
|
|
1748
|
+
? t('fileManagement.emptyFiles.ownDescription')
|
|
1749
|
+
: t('fileManagement.emptyFiles.otherDescription')
|
|
1741
1750
|
}
|
|
1742
1751
|
</Text>
|
|
1743
1752
|
{user?.id === targetUserId && (
|
|
@@ -1753,7 +1762,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1753
1762
|
) : (
|
|
1754
1763
|
<>
|
|
1755
1764
|
<Ionicons name="cloud-upload" size={20} color="#FFFFFF" />
|
|
1756
|
-
<Text style={fileManagementStyles.emptyStateButtonText}>
|
|
1765
|
+
<Text style={fileManagementStyles.emptyStateButtonText}>{t('fileManagement.uploadFiles')}</Text>
|
|
1757
1766
|
</>
|
|
1758
1767
|
)}
|
|
1759
1768
|
</TouchableOpacity>
|
|
@@ -1785,7 +1794,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1785
1794
|
outputRange: [-skeletonContainerWidth * 2, skeletonContainerWidth * 2],
|
|
1786
1795
|
});
|
|
1787
1796
|
|
|
1788
|
-
const SkeletonBox = ({ width, height, borderRadius = 8, style, delay = 0 }: { width: number | string; height: number; borderRadius?: number; style?:
|
|
1797
|
+
const SkeletonBox = ({ width, height, borderRadius = 8, style, delay = 0 }: { width: number | string; height: number; borderRadius?: number; style?: Record<string, unknown>; delay?: number }) => {
|
|
1789
1798
|
const delayedTranslateX = shimmerAnim.interpolate({
|
|
1790
1799
|
inputRange: [0, 1],
|
|
1791
1800
|
outputRange: [-skeletonContainerWidth * 2 + delay, skeletonContainerWidth * 2 + delay],
|
|
@@ -1976,8 +1985,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1976
1985
|
return (
|
|
1977
1986
|
<View style={fileManagementStyles.container}>
|
|
1978
1987
|
<Header
|
|
1979
|
-
title=
|
|
1980
|
-
subtitle={
|
|
1988
|
+
title={t('fileManagement.reviewFiles')}
|
|
1989
|
+
subtitle={t('fileManagement.readyToUpload', { count: pendingFiles.length })}
|
|
1981
1990
|
onBack={handleCancelUpload}
|
|
1982
1991
|
showBackButton
|
|
1983
1992
|
variant="minimal"
|
|
@@ -2000,46 +2009,46 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2000
2009
|
return (
|
|
2001
2010
|
<View style={fileManagementStyles.container}>
|
|
2002
2011
|
<Header
|
|
2003
|
-
title={selectMode ? (multiSelect ?
|
|
2004
|
-
subtitle={selectMode ? (multiSelect ?
|
|
2012
|
+
title={selectMode ? (multiSelect ? (maxSelection ? t('fileManagement.selectedWithMax', { count: selectedIds.size, max: maxSelection }) : t('fileManagement.selected', { count: selectedIds.size })) : t('fileManagement.selectFile')) : (viewMode === 'photos' ? t('fileManagement.photos') : t('fileManagement.title'))}
|
|
2013
|
+
subtitle={selectMode ? (multiSelect ? t('fileManagement.available', { count: filteredFiles.length }) : t('fileManagement.tapToSelect')) : (filteredFiles.length === 1 ? t('fileManagement.itemCount', { count: filteredFiles.length }) : t('fileManagement.itemCount_plural', { count: filteredFiles.length }))}
|
|
2005
2014
|
rightActions={selectMode && multiSelect ? [
|
|
2006
2015
|
{
|
|
2007
2016
|
key: 'clear',
|
|
2008
|
-
text: '
|
|
2017
|
+
text: t('fileManagement.clear'),
|
|
2009
2018
|
onPress: () => setSelectedIds(new Set()),
|
|
2010
2019
|
disabled: selectedIds.size === 0,
|
|
2011
2020
|
},
|
|
2012
2021
|
{
|
|
2013
2022
|
key: 'confirm',
|
|
2014
|
-
text: '
|
|
2023
|
+
text: t('fileManagement.confirm'),
|
|
2015
2024
|
onPress: confirmMultiSelection,
|
|
2016
2025
|
disabled: selectedIds.size === 0,
|
|
2017
2026
|
}
|
|
2018
2027
|
] : !selectMode && selectedIds.size > 0 ? [
|
|
2019
2028
|
{
|
|
2020
2029
|
key: 'clear',
|
|
2021
|
-
text: '
|
|
2030
|
+
text: t('fileManagement.clear'),
|
|
2022
2031
|
onPress: () => setSelectedIds(new Set()),
|
|
2023
2032
|
},
|
|
2024
2033
|
{
|
|
2025
2034
|
key: 'delete',
|
|
2026
|
-
text:
|
|
2035
|
+
text: t('fileManagement.delete', { count: selectedIds.size }),
|
|
2027
2036
|
onPress: handleBulkDelete,
|
|
2028
2037
|
icon: 'delete',
|
|
2029
2038
|
},
|
|
2030
2039
|
{
|
|
2031
2040
|
key: 'visibility',
|
|
2032
|
-
text: '
|
|
2041
|
+
text: t('fileManagement.visibility'),
|
|
2033
2042
|
onPress: () => {
|
|
2034
2043
|
// Show visibility options menu
|
|
2035
2044
|
Alert.alert(
|
|
2036
|
-
'
|
|
2037
|
-
|
|
2045
|
+
t('fileManagement.changeVisibility'),
|
|
2046
|
+
t('fileManagement.changeVisibilityConfirm', { count: selectedIds.size }),
|
|
2038
2047
|
[
|
|
2039
|
-
{ text: '
|
|
2040
|
-
{ text: '
|
|
2041
|
-
{ text: '
|
|
2042
|
-
{ text: '
|
|
2048
|
+
{ text: t('common.cancel'), style: 'cancel' },
|
|
2049
|
+
{ text: t('fileManagement.private'), onPress: () => handleBulkVisibilityChange('private') },
|
|
2050
|
+
{ text: t('fileManagement.public'), onPress: () => handleBulkVisibilityChange('public') },
|
|
2051
|
+
{ text: t('fileManagement.unlisted'), onPress: () => handleBulkVisibilityChange('unlisted') },
|
|
2043
2052
|
]
|
|
2044
2053
|
);
|
|
2045
2054
|
},
|
|
@@ -2174,7 +2183,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2174
2183
|
<Ionicons name="search" size={22} color={themeStyles.colors.icon} />
|
|
2175
2184
|
<TextInput
|
|
2176
2185
|
style={[fileManagementStyles.searchInput, { color: themeStyles.textColor }]}
|
|
2177
|
-
placeholder={viewMode === 'photos' ? '
|
|
2186
|
+
placeholder={viewMode === 'photos' ? t('fileManagement.searchPhotos') : t('fileManagement.searchFiles')}
|
|
2178
2187
|
placeholderTextColor={themeStyles.colors.secondaryText}
|
|
2179
2188
|
value={searchQuery}
|
|
2180
2189
|
onChangeText={setSearchQuery}
|
|
@@ -2201,7 +2210,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2201
2210
|
<View style={fileManagementStyles.statItem}>
|
|
2202
2211
|
<Text style={[fileManagementStyles.statValue, { color: themeStyles.textColor }]}>{filteredFiles.length}</Text>
|
|
2203
2212
|
<Text style={[fileManagementStyles.statLabel, { color: themeStyles.colors.secondaryText }]}>
|
|
2204
|
-
{searchQuery.length > 0 ? '
|
|
2213
|
+
{searchQuery.length > 0 ? t('fileManagement.found') : (filteredFiles.length === 1 ? (viewMode === 'photos' ? t('fileManagement.photo') : t('fileManagement.file')) : (viewMode === 'photos' ? t('fileManagement.photos_stat') : t('fileManagement.files')))}
|
|
2205
2214
|
</Text>
|
|
2206
2215
|
</View>
|
|
2207
2216
|
<View style={fileManagementStyles.statItem}>
|
|
@@ -2209,14 +2218,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2209
2218
|
{formatFileSize(filteredFiles.reduce((total, file) => total + file.length, 0))}
|
|
2210
2219
|
</Text>
|
|
2211
2220
|
<Text style={[fileManagementStyles.statLabel, { color: themeStyles.colors.secondaryText }]}>
|
|
2212
|
-
{searchQuery.length > 0 ? '
|
|
2221
|
+
{searchQuery.length > 0 ? t('fileManagement.size') : t('fileManagement.totalSize')}
|
|
2213
2222
|
</Text>
|
|
2214
2223
|
</View>
|
|
2215
2224
|
{searchQuery.length > 0 && (
|
|
2216
2225
|
<View style={fileManagementStyles.statItem}>
|
|
2217
2226
|
<Text style={[fileManagementStyles.statValue, { color: themeStyles.textColor }]}>{files.length}</Text>
|
|
2218
2227
|
<Text style={[fileManagementStyles.statLabel, { color: themeStyles.colors.secondaryText }]}>
|
|
2219
|
-
|
|
2228
|
+
{t('fileManagement.total')}
|
|
2220
2229
|
</Text>
|
|
2221
2230
|
</View>
|
|
2222
2231
|
)}
|
|
@@ -2250,16 +2259,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2250
2259
|
{filteredFiles.length === 0 && searchQuery.length > 0 ? (
|
|
2251
2260
|
<View style={fileManagementStyles.emptyState}>
|
|
2252
2261
|
<Ionicons name="search" size={64} color={themeStyles.isDarkTheme ? '#666666' : '#CCCCCC'} />
|
|
2253
|
-
<Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>
|
|
2262
|
+
<Text style={[fileManagementStyles.emptyStateTitle, { color: themeStyles.textColor }]}>{t('fileManagement.noResults.title')}</Text>
|
|
2254
2263
|
<Text style={[fileManagementStyles.emptyStateDescription, { color: themeStyles.isDarkTheme ? '#BBBBBB' : '#666666' }]}>
|
|
2255
|
-
|
|
2264
|
+
{t('fileManagement.noResults.description', { query: searchQuery })}
|
|
2256
2265
|
</Text>
|
|
2257
2266
|
<TouchableOpacity
|
|
2258
2267
|
style={[fileManagementStyles.emptyStateButton, { backgroundColor: themeStyles.primaryColor }]}
|
|
2259
2268
|
onPress={() => setSearchQuery('')}
|
|
2260
2269
|
>
|
|
2261
2270
|
<Ionicons name="refresh" size={20} color="#FFFFFF" />
|
|
2262
|
-
<Text style={fileManagementStyles.emptyStateButtonText}>
|
|
2271
|
+
<Text style={fileManagementStyles.emptyStateButtonText}>{t('fileManagement.clearSearch')}</Text>
|
|
2263
2272
|
</TouchableOpacity>
|
|
2264
2273
|
</View>
|
|
2265
2274
|
) : filteredFiles.length === 0 ? renderEmptyState() : (
|
|
@@ -2268,7 +2277,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2268
2277
|
{paging.loadingMore && (
|
|
2269
2278
|
<View style={fileManagementStyles.loadingMoreBar}>
|
|
2270
2279
|
<ActivityIndicator size="small" color={themeStyles.primaryColor} />
|
|
2271
|
-
<Text style={[fileManagementStyles.loadingMoreText, { color: themeStyles.textColor }]}>
|
|
2280
|
+
<Text style={[fileManagementStyles.loadingMoreText, { color: themeStyles.textColor }]}>{t('fileManagement.loadingMore')}</Text>
|
|
2272
2281
|
</View>
|
|
2273
2282
|
)}
|
|
2274
2283
|
</>
|
|
@@ -2295,7 +2304,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
2295
2304
|
<Ionicons name="cloud-upload" size={18} color={themeStyles.primaryColor} />
|
|
2296
2305
|
<View style={fileManagementStyles.uploadBannerContent}>
|
|
2297
2306
|
<Text style={[fileManagementStyles.uploadBannerText, { color: themeStyles.textColor }]}>
|
|
2298
|
-
|
|
2307
|
+
{t('fileManagement.uploading')}{uploadProgress ? ` ${uploadProgress.current}/${uploadProgress.total}` : '...'}
|
|
2299
2308
|
</Text>
|
|
2300
2309
|
{uploadProgress && uploadProgress.total > 0 && (
|
|
2301
2310
|
<View style={[fileManagementStyles.uploadProgressBarContainer, { backgroundColor: themeStyles.isDarkTheme ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)' }]}>
|