@oxyhq/services 22.6.2 → 22.6.4
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/components/fileManagement/FileViewer.js +99 -77
- package/lib/commonjs/ui/components/fileManagement/FileViewer.js.map +1 -1
- package/lib/commonjs/ui/components/fileManagement/UploadPreview.js +33 -40
- package/lib/commonjs/ui/components/fileManagement/UploadPreview.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +92 -88
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +175 -360
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +9 -2
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/components/fileManagement/FileViewer.js +100 -78
- package/lib/module/ui/components/fileManagement/FileViewer.js.map +1 -1
- package/lib/module/ui/components/fileManagement/UploadPreview.js +34 -41
- package/lib/module/ui/components/fileManagement/UploadPreview.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +94 -90
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +177 -363
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +9 -2
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/fileManagement/UploadPreview.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts +0 -1
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/fileManagement/UploadPreview.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts +0 -1
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/components/fileManagement/FileViewer.tsx +82 -68
- package/src/ui/components/fileManagement/UploadPreview.tsx +34 -29
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +14 -13
- package/src/ui/screens/FileManagementScreen.tsx +135 -354
- package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +6 -2
- package/lib/commonjs/ui/components/fileManagement/styles.js +0 -832
- package/lib/commonjs/ui/components/fileManagement/styles.js.map +0 -1
- package/lib/module/ui/components/fileManagement/styles.js +0 -828
- package/lib/module/ui/components/fileManagement/styles.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/fileManagement/styles.d.ts +0 -825
- package/lib/typescript/commonjs/ui/components/fileManagement/styles.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/fileManagement/styles.d.ts +0 -825
- package/lib/typescript/module/ui/components/fileManagement/styles.d.ts.map +0 -1
- package/src/ui/components/fileManagement/styles.ts +0 -823
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
Animated,
|
|
13
13
|
Easing,
|
|
14
14
|
AccessibilityInfo,
|
|
15
|
+
Platform,
|
|
16
|
+
useWindowDimensions,
|
|
15
17
|
} from 'react-native';
|
|
16
18
|
import { Image as ExpoImage } from 'expo-image';
|
|
17
19
|
import type { FileManagementScreenProps } from '../types/fileManagement';
|
|
@@ -33,13 +35,29 @@ import { useResolvedFileUrls, fileThumbSource } from '../hooks/useResolvedFileUr
|
|
|
33
35
|
import { FileViewer } from '../components/fileManagement/FileViewer';
|
|
34
36
|
import { FileDetailsModal } from '../components/fileManagement/FileDetailsModal';
|
|
35
37
|
import { UploadPreview } from '../components/fileManagement/UploadPreview';
|
|
36
|
-
import { fileManagementStyles } from '../components/fileManagement/styles';
|
|
37
38
|
import { getErrorMessage } from './fileManagement/shared';
|
|
38
39
|
import { useFileUploadState } from './fileManagement/hooks/useFileUploadState';
|
|
39
40
|
import PhotoPickerView from './fileManagement/PhotoPickerSection';
|
|
40
41
|
import FileListSection, { type FileListItem } from './fileManagement/FileListSection';
|
|
41
42
|
import UploadBar from './fileManagement/UploadBar';
|
|
42
43
|
|
|
44
|
+
// Genuinely-inline-only styles: `viewModeButton` is spread into an Animated.View
|
|
45
|
+
// style array (interpolated backgroundColor), and the photo tiles are
|
|
46
|
+
// `expo-image` (no className remap). Everything else uses NativeWind classNames.
|
|
47
|
+
const screenStyles = StyleSheet.create({
|
|
48
|
+
viewModeButton: {
|
|
49
|
+
paddingHorizontal: 10,
|
|
50
|
+
paddingVertical: 6,
|
|
51
|
+
borderRadius: 999,
|
|
52
|
+
minWidth: 36,
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
justifyContent: 'center',
|
|
55
|
+
marginHorizontal: 1,
|
|
56
|
+
},
|
|
57
|
+
photoImage: { width: '100%', height: '100%', borderRadius: 8 },
|
|
58
|
+
justifiedPhotoImage: { width: '100%', height: '100%', borderRadius: 6 },
|
|
59
|
+
});
|
|
60
|
+
|
|
43
61
|
// Animated button component for smooth transitions
|
|
44
62
|
const AnimatedButton: React.FC<{
|
|
45
63
|
isSelected: boolean;
|
|
@@ -100,7 +118,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
100
118
|
goBack,
|
|
101
119
|
navigate,
|
|
102
120
|
userId,
|
|
103
|
-
containerWidth
|
|
121
|
+
containerWidth,
|
|
104
122
|
selectMode = false,
|
|
105
123
|
multiSelect = false,
|
|
106
124
|
onSelect,
|
|
@@ -126,8 +144,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
126
144
|
const visibilityChangeDialog = useDialogControl();
|
|
127
145
|
const [pendingDeleteFile, setPendingDeleteFile] = useState<{ id: string; name: string } | null>(null);
|
|
128
146
|
const files = useFiles();
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
const { width: windowWidth } = useWindowDimensions();
|
|
148
|
+
// Prefer an explicit sheet width from the router; fall back to the window
|
|
149
|
+
// so photo grids never size against a hardcoded 400px default.
|
|
150
|
+
const safeContainerWidth: number =
|
|
151
|
+
typeof containerWidth === 'number' && containerWidth > 0
|
|
152
|
+
? containerWidth
|
|
153
|
+
: windowWidth;
|
|
131
154
|
const uploading = useUploadingStore();
|
|
132
155
|
const uploadProgress = useUploadAggregateProgress();
|
|
133
156
|
const deleting = useDeletingStore();
|
|
@@ -782,79 +805,26 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
782
805
|
fileDetailsControl.open();
|
|
783
806
|
};
|
|
784
807
|
|
|
785
|
-
const renderSimplePhotoItem = useCallback((photo: FileMetadata, index: number) => {
|
|
786
|
-
const downloadUrl = thumbSourceFor(photo);
|
|
787
|
-
|
|
788
|
-
// Calculate photo item width based on actual container size from bottom sheet
|
|
789
|
-
let itemsPerRow = 3; // Default for mobile
|
|
790
|
-
if (safeContainerWidth > 768) itemsPerRow = 4; // Desktop/tablet
|
|
791
|
-
else if (safeContainerWidth > 480) itemsPerRow = 3; // Large mobile
|
|
792
|
-
|
|
793
|
-
// Account for the photoScrollContainer padding (16px on each side = 32px total)
|
|
794
|
-
const scrollContainerPadding = 32; // Total horizontal padding from photoScrollContainer
|
|
795
|
-
const gaps = (itemsPerRow - 1) * 4; // Gap between items (4px)
|
|
796
|
-
const availableWidth = safeContainerWidth - scrollContainerPadding;
|
|
797
|
-
const itemWidth = (availableWidth - gaps) / itemsPerRow;
|
|
798
|
-
|
|
799
|
-
return (
|
|
800
|
-
<TouchableOpacity
|
|
801
|
-
key={photo.id}
|
|
802
|
-
style={[
|
|
803
|
-
fileManagementStyles.simplePhotoItem,
|
|
804
|
-
{
|
|
805
|
-
width: itemWidth,
|
|
806
|
-
height: itemWidth,
|
|
807
|
-
marginRight: (index + 1) % itemsPerRow === 0 ? 0 : 4,
|
|
808
|
-
...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: colors.primary } : {})
|
|
809
|
-
}
|
|
810
|
-
]}
|
|
811
|
-
onPress={() => handleFileOpen(photo)}
|
|
812
|
-
activeOpacity={0.8}
|
|
813
|
-
>
|
|
814
|
-
<View style={fileManagementStyles.simplePhotoContainer}>
|
|
815
|
-
<ExpoImage
|
|
816
|
-
source={{ uri: downloadUrl }}
|
|
817
|
-
style={fileManagementStyles.simplePhotoImage}
|
|
818
|
-
contentFit="cover"
|
|
819
|
-
transition={120}
|
|
820
|
-
cachePolicy="memory-disk"
|
|
821
|
-
onError={() => {
|
|
822
|
-
// Photo failed to load, will show placeholder
|
|
823
|
-
}}
|
|
824
|
-
accessibilityLabel={photo.filename}
|
|
825
|
-
/>
|
|
826
|
-
{selectMode && (
|
|
827
|
-
<View style={fileManagementStyles.selectionBadge}>
|
|
828
|
-
<Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? colors.primary : colors.text} />
|
|
829
|
-
</View>
|
|
830
|
-
)}
|
|
831
|
-
</View>
|
|
832
|
-
</TouchableOpacity>
|
|
833
|
-
);
|
|
834
|
-
}, [thumbSourceFor, safeContainerWidth, selectMode, selectedIds, colors.primary, colors.text]);
|
|
835
|
-
|
|
836
808
|
const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
|
|
837
809
|
const downloadUrl = thumbSourceFor(photo);
|
|
838
810
|
|
|
839
811
|
return (
|
|
840
812
|
<TouchableOpacity
|
|
841
813
|
key={photo.id}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
},
|
|
850
|
-
]}
|
|
814
|
+
className="rounded-[6px] overflow-hidden relative"
|
|
815
|
+
style={{
|
|
816
|
+
width,
|
|
817
|
+
height,
|
|
818
|
+
...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: colors.primary } : {}),
|
|
819
|
+
...(selectMode && multiSelect && selectedIds.size > 0 && !selectedIds.has(photo.id) ? { opacity: 0.4 } : {}),
|
|
820
|
+
}}
|
|
851
821
|
onPress={() => handleFileOpen(photo)}
|
|
852
822
|
activeOpacity={0.8}
|
|
853
823
|
>
|
|
854
|
-
<View
|
|
824
|
+
<View className="w-full h-full relative rounded-[6px] overflow-hidden">
|
|
855
825
|
<ExpoImage
|
|
856
826
|
source={{ uri: downloadUrl }}
|
|
857
|
-
style={
|
|
827
|
+
style={screenStyles.justifiedPhotoImage}
|
|
858
828
|
contentFit="cover"
|
|
859
829
|
transition={120}
|
|
860
830
|
cachePolicy="memory-disk"
|
|
@@ -864,7 +834,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
864
834
|
accessibilityLabel={photo.filename}
|
|
865
835
|
/>
|
|
866
836
|
{selectMode && (
|
|
867
|
-
<View style={
|
|
837
|
+
<View className="absolute top-[4px] right-[4px] rounded-[12px] p-[2px]" style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}>
|
|
868
838
|
<Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? colors.primary : colors.text} />
|
|
869
839
|
</View>
|
|
870
840
|
)}
|
|
@@ -884,147 +854,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
884
854
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
885
855
|
}, [targetUserId]);
|
|
886
856
|
|
|
887
|
-
const renderFileItem = (file: FileMetadata) => {
|
|
888
|
-
const isImage = file.contentType.startsWith('image/');
|
|
889
|
-
const isPDF = file.contentType.includes('pdf');
|
|
890
|
-
const isVideo = file.contentType.startsWith('video/');
|
|
891
|
-
const isAudio = file.contentType.startsWith('audio/');
|
|
892
|
-
const hasPreview = isImage || isPDF || isVideo;
|
|
893
|
-
const borderColor = colors.border;
|
|
894
|
-
|
|
895
|
-
return (
|
|
896
|
-
<View
|
|
897
|
-
key={file.id}
|
|
898
|
-
style={[fileManagementStyles.fileItem, { backgroundColor: colors.backgroundSecondary, borderColor }, selectMode && selectedIds.has(file.id) && { borderColor: colors.primary, borderWidth: 2 }]}
|
|
899
|
-
>
|
|
900
|
-
<TouchableOpacity
|
|
901
|
-
style={fileManagementStyles.fileContent}
|
|
902
|
-
onPress={() => handleFileOpen(file)}
|
|
903
|
-
>
|
|
904
|
-
{/* Preview Thumbnail */}
|
|
905
|
-
<View style={fileManagementStyles.filePreviewContainer}>
|
|
906
|
-
{hasPreview ? (
|
|
907
|
-
<View style={fileManagementStyles.filePreview}>
|
|
908
|
-
{isImage && (
|
|
909
|
-
<ExpoImage
|
|
910
|
-
source={{ uri: thumbSourceFor(file) }}
|
|
911
|
-
style={fileManagementStyles.previewImage}
|
|
912
|
-
contentFit="cover"
|
|
913
|
-
transition={120}
|
|
914
|
-
cachePolicy="memory-disk"
|
|
915
|
-
onError={() => {
|
|
916
|
-
// Image preview failed to load
|
|
917
|
-
}}
|
|
918
|
-
accessibilityLabel={file.filename}
|
|
919
|
-
/>
|
|
920
|
-
)}
|
|
921
|
-
{isPDF && (
|
|
922
|
-
<View style={fileManagementStyles.pdfPreview}>
|
|
923
|
-
<Ionicons name="document" size={32} color={colors.primary} />
|
|
924
|
-
<Text style={[fileManagementStyles.pdfLabel, { color: colors.primary }]}>PDF</Text>
|
|
925
|
-
</View>
|
|
926
|
-
)}
|
|
927
|
-
{isVideo && (
|
|
928
|
-
<View style={fileManagementStyles.videoPreviewWrapper}>
|
|
929
|
-
<ExpoImage
|
|
930
|
-
source={{ uri: thumbSourceFor(file) }}
|
|
931
|
-
style={fileManagementStyles.videoPosterImage}
|
|
932
|
-
contentFit="cover"
|
|
933
|
-
transition={120}
|
|
934
|
-
cachePolicy="memory-disk"
|
|
935
|
-
onError={(_: unknown) => {
|
|
936
|
-
// If thumbnail not available, we still show icon overlay
|
|
937
|
-
}}
|
|
938
|
-
accessibilityLabel={`${file.filename} video thumbnail`}
|
|
939
|
-
/>
|
|
940
|
-
<View style={fileManagementStyles.videoOverlay}>
|
|
941
|
-
<Ionicons name="play" size={24} color="#FFFFFF" />
|
|
942
|
-
</View>
|
|
943
|
-
</View>
|
|
944
|
-
)}
|
|
945
|
-
{/* Fallback icon (hidden by default for images) */}
|
|
946
|
-
<View
|
|
947
|
-
style={[fileManagementStyles.fallbackIcon, { display: isImage ? 'none' : 'flex' }]}
|
|
948
|
-
>
|
|
949
|
-
<Ionicons
|
|
950
|
-
name={getFileIcon(file.contentType)}
|
|
951
|
-
size={32}
|
|
952
|
-
color={colors.primary}
|
|
953
|
-
/>
|
|
954
|
-
</View>
|
|
955
|
-
|
|
956
|
-
{selectMode && (
|
|
957
|
-
<View style={fileManagementStyles.selectionBadge}>
|
|
958
|
-
<Ionicons name={selectedIds.has(file.id) ? 'checkmark-circle' : 'ellipse-outline'} size={22} color={selectedIds.has(file.id) ? colors.primary : colors.text} />
|
|
959
|
-
</View>
|
|
960
|
-
)}
|
|
961
|
-
</View>
|
|
962
|
-
) : (
|
|
963
|
-
<View style={fileManagementStyles.fileIconContainer}>
|
|
964
|
-
<Ionicons
|
|
965
|
-
name={getFileIcon(file.contentType)}
|
|
966
|
-
size={32}
|
|
967
|
-
color={colors.primary}
|
|
968
|
-
/>
|
|
969
|
-
</View>
|
|
970
|
-
)}
|
|
971
|
-
</View>
|
|
972
|
-
|
|
973
|
-
<View style={fileManagementStyles.fileInfo}>
|
|
974
|
-
<Text style={[fileManagementStyles.fileName, { color: colors.text }]} numberOfLines={1}>
|
|
975
|
-
{file.filename}
|
|
976
|
-
</Text>
|
|
977
|
-
<Text style={[fileManagementStyles.fileDetails, { color: colors.textSecondary }]}>
|
|
978
|
-
{formatFileSize(file.length)} • {new Date(file.uploadDate).toLocaleDateString()}
|
|
979
|
-
</Text>
|
|
980
|
-
{file.metadata?.description && (
|
|
981
|
-
<Text
|
|
982
|
-
style={[fileManagementStyles.fileDescription, { color: colors.textTertiary }]}
|
|
983
|
-
numberOfLines={2}
|
|
984
|
-
>
|
|
985
|
-
{file.metadata.description}
|
|
986
|
-
</Text>
|
|
987
|
-
)}
|
|
988
|
-
</View>
|
|
989
|
-
</TouchableOpacity>
|
|
990
|
-
|
|
991
|
-
{!selectMode && (
|
|
992
|
-
<View style={fileManagementStyles.fileActions}>
|
|
993
|
-
{/* Preview button for supported files */}
|
|
994
|
-
{hasPreview && (
|
|
995
|
-
<TouchableOpacity
|
|
996
|
-
style={[fileManagementStyles.actionButton, { backgroundColor: colors.backgroundSecondary }]}
|
|
997
|
-
onPress={() => handleFileOpen(file)}
|
|
998
|
-
>
|
|
999
|
-
<Ionicons name="eye" size={20} color={colors.primary} />
|
|
1000
|
-
</TouchableOpacity>
|
|
1001
|
-
)}
|
|
1002
|
-
|
|
1003
|
-
<TouchableOpacity
|
|
1004
|
-
style={[fileManagementStyles.actionButton, { backgroundColor: colors.backgroundSecondary }]}
|
|
1005
|
-
onPress={() => handleFileDownload(file.id, file.filename)}
|
|
1006
|
-
>
|
|
1007
|
-
<Ionicons name="download" size={20} color={colors.primary} />
|
|
1008
|
-
</TouchableOpacity>
|
|
1009
|
-
|
|
1010
|
-
{/* Always show delete button for debugging */}
|
|
1011
|
-
<TouchableOpacity
|
|
1012
|
-
style={[fileManagementStyles.actionButton, { backgroundColor: colors.negativeSubtle }]}
|
|
1013
|
-
onPress={() => confirmFileDelete(file.id, file.filename)}
|
|
1014
|
-
disabled={deleting === file.id}
|
|
1015
|
-
>
|
|
1016
|
-
{deleting === file.id ? (
|
|
1017
|
-
<ActivityIndicator size="small" color={colors.error} />
|
|
1018
|
-
) : (
|
|
1019
|
-
<Ionicons name="trash" size={20} color={colors.error} />
|
|
1020
|
-
)}
|
|
1021
|
-
</TouchableOpacity>
|
|
1022
|
-
</View>
|
|
1023
|
-
)}
|
|
1024
|
-
</View>
|
|
1025
|
-
);
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
857
|
// SettingsListItem-based file items (for 'all' view)
|
|
1029
858
|
const groupedFileItems: FileListItem[] = useMemo(() => {
|
|
1030
859
|
// filteredFiles is already sorted, so just use it directly
|
|
@@ -1100,23 +929,26 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1100
929
|
},
|
|
1101
930
|
// Hide action buttons when selecting (in selectMode or bulk operations mode)
|
|
1102
931
|
rightElement: (!selectMode && selectedIds.size === 0) ? (
|
|
1103
|
-
<View
|
|
932
|
+
<View className="flex-row items-center gap-[6px] ml-[12px]">
|
|
1104
933
|
{(isImage || isVideo || file.contentType.includes('pdf')) && (
|
|
1105
934
|
<TouchableOpacity
|
|
1106
|
-
|
|
935
|
+
className="w-[34px] h-[34px] rounded-[8px] items-center justify-center"
|
|
936
|
+
style={{ backgroundColor: colors.backgroundSecondary }}
|
|
1107
937
|
onPress={() => handleFileOpen(file)}
|
|
1108
938
|
>
|
|
1109
939
|
<Ionicons name="eye" size={18} color={colors.primary} />
|
|
1110
940
|
</TouchableOpacity>
|
|
1111
941
|
)}
|
|
1112
942
|
<TouchableOpacity
|
|
1113
|
-
|
|
943
|
+
className="w-[34px] h-[34px] rounded-[8px] items-center justify-center"
|
|
944
|
+
style={{ backgroundColor: colors.backgroundSecondary }}
|
|
1114
945
|
onPress={() => handleFileDownload(file.id, file.filename)}
|
|
1115
946
|
>
|
|
1116
947
|
<Ionicons name="download" size={18} color={colors.primary} />
|
|
1117
948
|
</TouchableOpacity>
|
|
1118
949
|
<TouchableOpacity
|
|
1119
|
-
|
|
950
|
+
className="w-[34px] h-[34px] rounded-[8px] items-center justify-center"
|
|
951
|
+
style={{ backgroundColor: colors.negativeSubtle }}
|
|
1120
952
|
onPress={() => confirmFileDelete(file.id, file.filename)}
|
|
1121
953
|
disabled={deleting === file.id}
|
|
1122
954
|
>
|
|
@@ -1177,21 +1009,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1177
1009
|
const photoIndex = photos.findIndex(p => p.id === lastSelectedFileId);
|
|
1178
1010
|
|
|
1179
1011
|
if (photoIndex >= 0) {
|
|
1180
|
-
//
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
else if (safeContainerWidth > 480) itemsPerRow = 4;
|
|
1185
|
-
|
|
1186
|
-
const scrollContainerPadding = 32;
|
|
1187
|
-
const gaps = (itemsPerRow - 1) * 4;
|
|
1188
|
-
const availableWidth = safeContainerWidth - scrollContainerPadding;
|
|
1189
|
-
const itemWidth = (availableWidth - gaps) / itemsPerRow;
|
|
1190
|
-
|
|
1191
|
-
// Calculate row and approximate scroll position
|
|
1012
|
+
// Rough scroll estimate for the justified grid (3 photos/row; variable row heights).
|
|
1013
|
+
const itemsPerRow = 3;
|
|
1014
|
+
const estimatedRowHeight = 150;
|
|
1015
|
+
const rowGap = 4;
|
|
1192
1016
|
const row = Math.floor(photoIndex / itemsPerRow);
|
|
1193
|
-
const
|
|
1194
|
-
const finalScrollPosition = headerHeight + (row * (itemWidth + 4)) - 150;
|
|
1017
|
+
const finalScrollPosition = row * (estimatedRowHeight + rowGap) - 100;
|
|
1195
1018
|
|
|
1196
1019
|
// Use requestAnimationFrame to ensure DOM is updated before scrolling
|
|
1197
1020
|
requestAnimationFrame(() => {
|
|
@@ -1218,66 +1041,23 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1218
1041
|
}
|
|
1219
1042
|
}, [lastSelectedFileId]);
|
|
1220
1043
|
|
|
1221
|
-
const renderPhotoItem = (photo: FileMetadata, index: number) => {
|
|
1222
|
-
const downloadUrl = thumbSourceFor(photo);
|
|
1223
|
-
|
|
1224
|
-
// Calculate photo item width based on actual container size from bottom sheet
|
|
1225
|
-
let itemsPerRow = 3; // Default for mobile
|
|
1226
|
-
if (safeContainerWidth > 768) itemsPerRow = 6; // Tablet/Desktop
|
|
1227
|
-
else if (safeContainerWidth > 480) itemsPerRow = 4; // Large mobile
|
|
1228
|
-
|
|
1229
|
-
// Account for the photoScrollContainer padding (16px on each side = 32px total)
|
|
1230
|
-
const scrollContainerPadding = 32; // Total horizontal padding from photoScrollContainer
|
|
1231
|
-
const gaps = (itemsPerRow - 1) * 4; // Gap between items
|
|
1232
|
-
const availableWidth = safeContainerWidth - scrollContainerPadding;
|
|
1233
|
-
const itemWidth = (availableWidth - gaps) / itemsPerRow;
|
|
1234
|
-
|
|
1235
|
-
return (
|
|
1236
|
-
<TouchableOpacity
|
|
1237
|
-
key={photo.id}
|
|
1238
|
-
style={[
|
|
1239
|
-
fileManagementStyles.photoItem,
|
|
1240
|
-
{
|
|
1241
|
-
width: itemWidth,
|
|
1242
|
-
height: itemWidth,
|
|
1243
|
-
}
|
|
1244
|
-
]}
|
|
1245
|
-
onPress={() => handleFileOpen(photo)}
|
|
1246
|
-
activeOpacity={0.8}
|
|
1247
|
-
>
|
|
1248
|
-
<View style={fileManagementStyles.photoContainer}>
|
|
1249
|
-
<ExpoImage
|
|
1250
|
-
source={{ uri: downloadUrl }}
|
|
1251
|
-
style={fileManagementStyles.photoImage}
|
|
1252
|
-
contentFit="cover"
|
|
1253
|
-
transition={120}
|
|
1254
|
-
cachePolicy="memory-disk"
|
|
1255
|
-
onError={() => {
|
|
1256
|
-
// Image preview failed to load
|
|
1257
|
-
}}
|
|
1258
|
-
accessibilityLabel={photo.filename}
|
|
1259
|
-
/>
|
|
1260
|
-
</View>
|
|
1261
|
-
</TouchableOpacity>
|
|
1262
|
-
);
|
|
1263
|
-
};
|
|
1264
|
-
|
|
1265
1044
|
const renderPhotoGrid = useCallback(() => {
|
|
1266
1045
|
const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
|
|
1267
1046
|
|
|
1268
1047
|
if (photos.length === 0) {
|
|
1269
1048
|
return (
|
|
1270
|
-
<View
|
|
1049
|
+
<View className="items-center py-[40px] px-[24px]">
|
|
1271
1050
|
<Ionicons name="images-outline" size={64} color={colors.textTertiary} />
|
|
1272
|
-
<Text
|
|
1273
|
-
<Text
|
|
1051
|
+
<Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>{t('fileManagement.emptyPhotos.title')}</Text>
|
|
1052
|
+
<Text className="text-[16px] text-center leading-[24px] mb-[32px]" style={{ color: colors.textSecondary }}> {
|
|
1274
1053
|
user?.id === targetUserId
|
|
1275
1054
|
? t('fileManagement.emptyPhotos.ownDescription')
|
|
1276
1055
|
: t('fileManagement.emptyPhotos.otherDescription')
|
|
1277
1056
|
} </Text>
|
|
1278
1057
|
{user?.id === targetUserId && (
|
|
1279
1058
|
<TouchableOpacity
|
|
1280
|
-
|
|
1059
|
+
className="flex-row items-center px-[24px] py-[12px] rounded-[24px] gap-[8px]"
|
|
1060
|
+
style={{ backgroundColor: colors.primary }}
|
|
1281
1061
|
onPress={handleFileUpload}
|
|
1282
1062
|
disabled={uploading || isPickingDocument}
|
|
1283
1063
|
>
|
|
@@ -1288,7 +1068,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1288
1068
|
) : (
|
|
1289
1069
|
<>
|
|
1290
1070
|
<Ionicons name="cloud-upload" size={20} color="#FFFFFF" />
|
|
1291
|
-
<Text
|
|
1071
|
+
<Text className="text-white text-[16px] font-semibold">{t('fileManagement.uploadPhotos')}</Text>
|
|
1292
1072
|
</>
|
|
1293
1073
|
)}
|
|
1294
1074
|
</TouchableOpacity>
|
|
@@ -1300,8 +1080,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1300
1080
|
return (
|
|
1301
1081
|
<ScrollView
|
|
1302
1082
|
ref={photoScrollViewRef}
|
|
1303
|
-
|
|
1304
|
-
|
|
1083
|
+
className="flex-1"
|
|
1084
|
+
contentContainerClassName="p-[10px]"
|
|
1305
1085
|
refreshControl={
|
|
1306
1086
|
<RefreshControl
|
|
1307
1087
|
refreshing={refreshing}
|
|
@@ -1320,9 +1100,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1320
1100
|
scrollEventThrottle={250}
|
|
1321
1101
|
>
|
|
1322
1102
|
{loadingDimensions && (
|
|
1323
|
-
<View
|
|
1103
|
+
<View className="flex-row items-center justify-center py-[16px] gap-[8px]">
|
|
1324
1104
|
<ActivityIndicator size="small" color={colors.primary} />
|
|
1325
|
-
<Text
|
|
1105
|
+
<Text className="text-[14px] italic" style={{ color: colors.textSecondary }}>{t('fileManagement.loadingPhotoLayout')}</Text>
|
|
1326
1106
|
</View>
|
|
1327
1107
|
)}
|
|
1328
1108
|
|
|
@@ -1332,9 +1112,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1332
1112
|
loadPhotoDimensions={loadPhotoDimensions}
|
|
1333
1113
|
createJustifiedRows={createJustifiedRows}
|
|
1334
1114
|
renderJustifiedPhotoItem={renderJustifiedPhotoItem}
|
|
1335
|
-
renderSimplePhotoItem={renderPhotoItem}
|
|
1336
1115
|
textColor={colors.text}
|
|
1337
|
-
containerWidth={
|
|
1116
|
+
containerWidth={
|
|
1117
|
+
typeof containerWidth === 'number' && containerWidth > 0
|
|
1118
|
+
? containerWidth
|
|
1119
|
+
: undefined
|
|
1120
|
+
}
|
|
1338
1121
|
/>
|
|
1339
1122
|
</ScrollView>
|
|
1340
1123
|
);
|
|
@@ -1352,8 +1135,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1352
1135
|
loadPhotoDimensions,
|
|
1353
1136
|
createJustifiedRows,
|
|
1354
1137
|
renderJustifiedPhotoItem,
|
|
1355
|
-
|
|
1356
|
-
safeContainerWidth
|
|
1138
|
+
containerWidth,
|
|
1357
1139
|
]);
|
|
1358
1140
|
|
|
1359
1141
|
// Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
|
|
@@ -1361,10 +1143,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1361
1143
|
|
|
1362
1144
|
|
|
1363
1145
|
const renderEmptyState = () => (
|
|
1364
|
-
<View
|
|
1146
|
+
<View className="items-center py-[40px] px-[24px]">
|
|
1365
1147
|
<Ionicons name="folder-open-outline" size={64} color={colors.textTertiary} />
|
|
1366
|
-
<Text
|
|
1367
|
-
<Text
|
|
1148
|
+
<Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>{t('fileManagement.emptyFiles.title')}</Text>
|
|
1149
|
+
<Text className="text-[16px] text-center leading-[24px] mb-[32px]" style={{ color: colors.textSecondary }}>
|
|
1368
1150
|
{user?.id === targetUserId
|
|
1369
1151
|
? t('fileManagement.emptyFiles.ownDescription')
|
|
1370
1152
|
: t('fileManagement.emptyFiles.otherDescription')
|
|
@@ -1372,7 +1154,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1372
1154
|
</Text>
|
|
1373
1155
|
{user?.id === targetUserId && (
|
|
1374
1156
|
<TouchableOpacity
|
|
1375
|
-
|
|
1157
|
+
className="flex-row items-center px-[24px] py-[12px] rounded-[24px] gap-[8px]"
|
|
1158
|
+
style={{ backgroundColor: colors.primary }}
|
|
1376
1159
|
onPress={handleFileUpload}
|
|
1377
1160
|
disabled={uploading || isPickingDocument}
|
|
1378
1161
|
>
|
|
@@ -1383,7 +1166,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1383
1166
|
) : (
|
|
1384
1167
|
<>
|
|
1385
1168
|
<Ionicons name="cloud-upload" size={20} color="#FFFFFF" />
|
|
1386
|
-
<Text
|
|
1169
|
+
<Text className="text-white text-[16px] font-semibold">{t('fileManagement.uploadFiles')}</Text>
|
|
1387
1170
|
</>
|
|
1388
1171
|
)}
|
|
1389
1172
|
</TouchableOpacity>
|
|
@@ -1402,7 +1185,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1402
1185
|
toValue: 1,
|
|
1403
1186
|
duration: 2000,
|
|
1404
1187
|
easing: Easing.linear,
|
|
1405
|
-
|
|
1188
|
+
// Web has no native animated module; forcing the native
|
|
1189
|
+
// driver there warns and falls back to JS. The shimmer only
|
|
1190
|
+
// drives a transform, so JS-driving it on web is fine.
|
|
1191
|
+
useNativeDriver: Platform.OS !== 'web',
|
|
1406
1192
|
})
|
|
1407
1193
|
);
|
|
1408
1194
|
shimmer.start();
|
|
@@ -1511,11 +1297,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1511
1297
|
);
|
|
1512
1298
|
|
|
1513
1299
|
return (
|
|
1514
|
-
<View style={
|
|
1300
|
+
<View className="flex-1" style={{ backgroundColor }}>
|
|
1515
1301
|
{/* Header Skeleton */}
|
|
1516
|
-
<View
|
|
1302
|
+
<View
|
|
1303
|
+
className="flex-row items-center justify-between px-[16px] py-[12px] relative"
|
|
1304
|
+
style={{ borderBottomColor: colors.border, borderBottomWidth: StyleSheet.hairlineWidth }}
|
|
1305
|
+
>
|
|
1517
1306
|
<SkeletonBox width={44} height={44} borderRadius={12} />
|
|
1518
|
-
<View
|
|
1307
|
+
<View className="flex-1 items-center justify-center mx-[16px]">
|
|
1519
1308
|
<SkeletonBox width={140} height={20} style={{ marginBottom: 6 }} />
|
|
1520
1309
|
<SkeletonBox width={100} height={14} />
|
|
1521
1310
|
</View>
|
|
@@ -1523,24 +1312,26 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1523
1312
|
</View>
|
|
1524
1313
|
|
|
1525
1314
|
{/* Controls Bar Skeleton */}
|
|
1526
|
-
<View
|
|
1315
|
+
<View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
|
|
1527
1316
|
<SkeletonBox width={100} height={36} borderRadius={18} />
|
|
1528
1317
|
<SkeletonBox width={44} height={44} borderRadius={22} />
|
|
1529
1318
|
</View>
|
|
1530
1319
|
|
|
1531
1320
|
{/* Search Bar Skeleton */}
|
|
1532
|
-
<View
|
|
1533
|
-
|
|
1534
|
-
|
|
1321
|
+
<View
|
|
1322
|
+
className="flex-row items-center px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-full gap-[10px]"
|
|
1323
|
+
style={{ backgroundColor: colors.card }}
|
|
1324
|
+
>
|
|
1535
1325
|
<SkeletonBox width="100%" height={44} borderRadius={12} />
|
|
1536
1326
|
</View>
|
|
1537
1327
|
|
|
1538
1328
|
{/* Stats Container Skeleton */}
|
|
1539
|
-
<View
|
|
1540
|
-
|
|
1541
|
-
|
|
1329
|
+
<View
|
|
1330
|
+
className="flex-row px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-[18px]"
|
|
1331
|
+
style={{ backgroundColor: colors.card }}
|
|
1332
|
+
>
|
|
1542
1333
|
{[1, 2, 3].map((i) => (
|
|
1543
|
-
<View key={i}
|
|
1334
|
+
<View key={i} className="flex-1 items-center py-[4px]">
|
|
1544
1335
|
<SkeletonBox width={50} height={20} style={{ marginBottom: 4 }} delay={i * 30} />
|
|
1545
1336
|
<SkeletonBox width={40} height={14} delay={i * 30 + 15} />
|
|
1546
1337
|
</View>
|
|
@@ -1549,8 +1340,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1549
1340
|
|
|
1550
1341
|
{/* File List Skeleton - Matching SettingsListItem */}
|
|
1551
1342
|
<ScrollView
|
|
1552
|
-
|
|
1553
|
-
|
|
1343
|
+
className="flex-1"
|
|
1344
|
+
contentContainerClassName="px-[12px] pt-0 pb-[12px]"
|
|
1554
1345
|
showsVerticalScrollIndicator={false}
|
|
1555
1346
|
>
|
|
1556
1347
|
<View style={{
|
|
@@ -1654,7 +1445,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1654
1445
|
// If upload preview is showing, render it inline instead of the file list
|
|
1655
1446
|
if (showUploadPreview) {
|
|
1656
1447
|
return (
|
|
1657
|
-
<View
|
|
1448
|
+
<View className="flex-1">
|
|
1658
1449
|
<Header
|
|
1659
1450
|
title={t('fileManagement.reviewFiles')}
|
|
1660
1451
|
subtitle={t('fileManagement.readyToUpload', { count: pendingFiles.length })}
|
|
@@ -1676,7 +1467,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1676
1467
|
}
|
|
1677
1468
|
|
|
1678
1469
|
return (
|
|
1679
|
-
<View
|
|
1470
|
+
<View className="flex-1">
|
|
1680
1471
|
<Header
|
|
1681
1472
|
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'))}
|
|
1682
1473
|
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 }))}
|
|
@@ -1722,25 +1513,24 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1722
1513
|
titleAlignment="left"
|
|
1723
1514
|
/>
|
|
1724
1515
|
|
|
1725
|
-
<View
|
|
1516
|
+
<View className="flex-row items-center justify-between px-[12px] py-[12px] gap-[12px]">
|
|
1726
1517
|
<ScrollView
|
|
1727
1518
|
horizontal
|
|
1728
1519
|
showsHorizontalScrollIndicator={false}
|
|
1729
|
-
|
|
1520
|
+
className="flex-1"
|
|
1521
|
+
style={{ maxWidth: '80%' }}
|
|
1730
1522
|
>
|
|
1731
|
-
<View
|
|
1732
|
-
|
|
1733
|
-
{
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1736
|
-
]}>
|
|
1523
|
+
<View
|
|
1524
|
+
className="flex-row rounded-full p-[2px] overflow-hidden"
|
|
1525
|
+
style={{ backgroundColor: colors.card }}
|
|
1526
|
+
>
|
|
1737
1527
|
<AnimatedButton
|
|
1738
1528
|
isSelected={viewMode === 'all'}
|
|
1739
1529
|
onPress={() => setViewMode('all')}
|
|
1740
1530
|
icon={viewMode === 'all' ? 'folder' : 'folder-outline'}
|
|
1741
1531
|
primaryColor={colors.primary}
|
|
1742
1532
|
textColor={colors.text}
|
|
1743
|
-
style={
|
|
1533
|
+
style={screenStyles.viewModeButton}
|
|
1744
1534
|
accessibilityLabel={t('fileManagement.a11y.viewAll') || 'Show all files'}
|
|
1745
1535
|
/>
|
|
1746
1536
|
<AnimatedButton
|
|
@@ -1749,7 +1539,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1749
1539
|
icon={viewMode === 'photos' ? 'image-multiple' : 'image-multiple-outline'}
|
|
1750
1540
|
primaryColor={colors.primary}
|
|
1751
1541
|
textColor={colors.text}
|
|
1752
|
-
style={
|
|
1542
|
+
style={screenStyles.viewModeButton}
|
|
1753
1543
|
accessibilityLabel={t('fileManagement.a11y.viewPhotos') || 'Show photos only'}
|
|
1754
1544
|
/>
|
|
1755
1545
|
{!isImageOnlyPicker && (
|
|
@@ -1760,7 +1550,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1760
1550
|
icon={viewMode === 'videos' ? 'video' : 'video-outline'}
|
|
1761
1551
|
primaryColor={colors.primary}
|
|
1762
1552
|
textColor={colors.text}
|
|
1763
|
-
style={
|
|
1553
|
+
style={screenStyles.viewModeButton}
|
|
1764
1554
|
accessibilityLabel={t('fileManagement.a11y.viewVideos') || 'Show videos only'}
|
|
1765
1555
|
/>
|
|
1766
1556
|
<AnimatedButton
|
|
@@ -1769,7 +1559,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1769
1559
|
icon={viewMode === 'documents' ? 'file-document' : 'file-document-outline'}
|
|
1770
1560
|
primaryColor={colors.primary}
|
|
1771
1561
|
textColor={colors.text}
|
|
1772
|
-
style={
|
|
1562
|
+
style={screenStyles.viewModeButton}
|
|
1773
1563
|
accessibilityLabel={t('fileManagement.a11y.viewDocuments') || 'Show documents only'}
|
|
1774
1564
|
/>
|
|
1775
1565
|
<AnimatedButton
|
|
@@ -1778,7 +1568,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1778
1568
|
icon={viewMode === 'audio' ? 'music-note' : 'music-note-outline'}
|
|
1779
1569
|
primaryColor={colors.primary}
|
|
1780
1570
|
textColor={colors.text}
|
|
1781
|
-
style={
|
|
1571
|
+
style={screenStyles.viewModeButton}
|
|
1782
1572
|
accessibilityLabel={t('fileManagement.a11y.viewAudio') || 'Show audio only'}
|
|
1783
1573
|
/>
|
|
1784
1574
|
</>
|
|
@@ -1786,9 +1576,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1786
1576
|
</View>
|
|
1787
1577
|
</ScrollView>
|
|
1788
1578
|
<TouchableOpacity
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
}]}
|
|
1579
|
+
className="flex-row items-center justify-center px-[10px] py-[6px] rounded-full min-w-[36px] gap-[4px]"
|
|
1580
|
+
style={{ backgroundColor: colors.card }}
|
|
1792
1581
|
accessibilityRole="button"
|
|
1793
1582
|
accessibilityLabel={t('fileManagement.a11y.sortBy', {
|
|
1794
1583
|
field: sortBy,
|
|
@@ -1823,11 +1612,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1823
1612
|
</TouchableOpacity>
|
|
1824
1613
|
{user?.id === targetUserId && (!selectMode || (selectMode && allowUploadInSelectMode)) && (
|
|
1825
1614
|
<TouchableOpacity
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
isImageOnlyPicker && fileManagementStyles.uploadButtonExtended,
|
|
1829
|
-
{ backgroundColor: colors.primary },
|
|
1830
|
-
]}
|
|
1615
|
+
className={`h-[44px] rounded-[22px] items-center justify-center ${isImageOnlyPicker ? 'w-auto min-w-[44px] px-[14px]' : 'w-[44px]'}`}
|
|
1616
|
+
style={{ backgroundColor: colors.primary }}
|
|
1831
1617
|
onPress={handleFileUpload}
|
|
1832
1618
|
disabled={uploading || isPickingDocument}
|
|
1833
1619
|
accessibilityRole="button"
|
|
@@ -1839,10 +1625,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1839
1625
|
accessibilityState={{ busy: uploading || isPickingDocument }}
|
|
1840
1626
|
>
|
|
1841
1627
|
{uploading ? (
|
|
1842
|
-
<View
|
|
1628
|
+
<View className="items-center justify-center">
|
|
1843
1629
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1844
1630
|
{uploadProgress && (
|
|
1845
|
-
<Text
|
|
1631
|
+
<Text className="text-white text-[10px] font-semibold mt-[2px]">
|
|
1846
1632
|
{uploadProgress.current}/{uploadProgress.total}
|
|
1847
1633
|
</Text>
|
|
1848
1634
|
)}
|
|
@@ -1850,9 +1636,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1850
1636
|
) : isPickingDocument ? (
|
|
1851
1637
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1852
1638
|
) : isImageOnlyPicker ? (
|
|
1853
|
-
<View
|
|
1639
|
+
<View className="flex-row items-center gap-[6px]">
|
|
1854
1640
|
<Ionicons name="cloud-upload" size={18} color="#FFFFFF" />
|
|
1855
|
-
<Text
|
|
1641
|
+
<Text className="text-white text-[14px] font-semibold" numberOfLines={1}>
|
|
1856
1642
|
{t('fileManagement.upload') || 'Upload'}
|
|
1857
1643
|
</Text>
|
|
1858
1644
|
</View>
|
|
@@ -1865,15 +1651,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1865
1651
|
|
|
1866
1652
|
{/* Search Bar */}
|
|
1867
1653
|
{files.length > 0 && (viewMode === 'all' || files.some(f => f.contentType.startsWith('image/'))) && (
|
|
1868
|
-
<View
|
|
1869
|
-
|
|
1870
|
-
{
|
|
1871
|
-
|
|
1872
|
-
}
|
|
1873
|
-
]}>
|
|
1654
|
+
<View
|
|
1655
|
+
className="flex-row items-center px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-full gap-[10px]"
|
|
1656
|
+
style={{ backgroundColor: colors.card }}
|
|
1657
|
+
>
|
|
1874
1658
|
<Ionicons name="search" size={22} color={colors.icon} />
|
|
1875
1659
|
<TextInput
|
|
1876
|
-
|
|
1660
|
+
className="flex-1 text-[16px] leading-[20px]"
|
|
1661
|
+
style={{ color: colors.text }}
|
|
1877
1662
|
placeholder={viewMode === 'photos' ? t('fileManagement.searchPhotos') : t('fileManagement.searchFiles')}
|
|
1878
1663
|
placeholderTextColor={colors.textSecondary}
|
|
1879
1664
|
value={searchQuery}
|
|
@@ -1882,7 +1667,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1882
1667
|
{searchQuery.length > 0 && (
|
|
1883
1668
|
<TouchableOpacity
|
|
1884
1669
|
onPress={() => setSearchQuery('')}
|
|
1885
|
-
|
|
1670
|
+
className="p-[4px] rounded-[12px] items-center justify-center"
|
|
1886
1671
|
>
|
|
1887
1672
|
<Ionicons name="close-circle" size={22} color={colors.icon} />
|
|
1888
1673
|
</TouchableOpacity>
|
|
@@ -1892,30 +1677,28 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1892
1677
|
|
|
1893
1678
|
{/* File Stats */}
|
|
1894
1679
|
{files.length > 0 && (
|
|
1895
|
-
<View
|
|
1896
|
-
|
|
1897
|
-
{
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
<Text style={[fileManagementStyles.statValue, { color: colors.text }]}>{filteredFiles.length}</Text>
|
|
1903
|
-
<Text style={[fileManagementStyles.statLabel, { color: colors.textSecondary }]}>
|
|
1680
|
+
<View
|
|
1681
|
+
className="flex-row px-[14px] py-[10px] mx-[12px] mb-[12px] rounded-[18px]"
|
|
1682
|
+
style={{ backgroundColor: colors.card }}
|
|
1683
|
+
>
|
|
1684
|
+
<View className="flex-1 items-center py-[4px]">
|
|
1685
|
+
<Text className="text-[20px] font-extrabold leading-[24px]" style={{ color: colors.text, letterSpacing: -0.5 }}>{filteredFiles.length}</Text>
|
|
1686
|
+
<Text className="text-[12px] font-medium mt-[2px]" style={{ color: colors.textSecondary, letterSpacing: 0.2 }}>
|
|
1904
1687
|
{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')))}
|
|
1905
1688
|
</Text>
|
|
1906
1689
|
</View>
|
|
1907
|
-
<View
|
|
1908
|
-
<Text
|
|
1690
|
+
<View className="flex-1 items-center py-[4px]">
|
|
1691
|
+
<Text className="text-[20px] font-extrabold leading-[24px]" style={{ color: colors.text, letterSpacing: -0.5 }}>
|
|
1909
1692
|
{formatFileSize(filteredFiles.reduce((total, file) => total + file.length, 0))}
|
|
1910
1693
|
</Text>
|
|
1911
|
-
<Text
|
|
1694
|
+
<Text className="text-[12px] font-medium mt-[2px]" style={{ color: colors.textSecondary, letterSpacing: 0.2 }}>
|
|
1912
1695
|
{searchQuery.length > 0 ? t('fileManagement.size') : t('fileManagement.totalSize')}
|
|
1913
1696
|
</Text>
|
|
1914
1697
|
</View>
|
|
1915
1698
|
{searchQuery.length > 0 && (
|
|
1916
|
-
<View
|
|
1917
|
-
<Text
|
|
1918
|
-
<Text
|
|
1699
|
+
<View className="flex-1 items-center py-[4px]">
|
|
1700
|
+
<Text className="text-[20px] font-extrabold leading-[24px]" style={{ color: colors.text, letterSpacing: -0.5 }}>{files.length}</Text>
|
|
1701
|
+
<Text className="text-[12px] font-medium mt-[2px]" style={{ color: colors.textSecondary, letterSpacing: 0.2 }}>
|
|
1919
1702
|
{t('fileManagement.total')}
|
|
1920
1703
|
</Text>
|
|
1921
1704
|
</View>
|
|
@@ -1998,6 +1781,4 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1998
1781
|
);
|
|
1999
1782
|
};
|
|
2000
1783
|
|
|
2001
|
-
// Styles have been moved to components/fileManagement/styles.ts
|
|
2002
|
-
|
|
2003
1784
|
export default FileManagementScreen;
|