@oxyhq/services 5.11.9 → 5.11.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/ui/components/AnimationExample.js +213 -0
- package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
- package/lib/commonjs/ui/components/FollowButton.js +58 -47
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +2 -1
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js +3 -0
- package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
- package/lib/commonjs/ui/components/Header.js +25 -11
- package/lib/commonjs/ui/components/Header.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +69 -33
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileCard.js +5 -1
- package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
- package/lib/commonjs/ui/components/index.js +0 -7
- package/lib/commonjs/ui/components/index.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +8 -4
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +97 -38
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
- package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
- package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +1 -1
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/module/ui/components/AnimationExample.js +209 -0
- package/lib/module/ui/components/AnimationExample.js.map +1 -0
- package/lib/module/ui/components/FollowButton.js +58 -47
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +2 -1
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +3 -0
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/Header.js +25 -11
- package/lib/module/ui/components/Header.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +70 -34
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +5 -1
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/components/index.js +0 -1
- package/lib/module/ui/components/index.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +8 -4
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +97 -39
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.types.js +2 -0
- package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
- package/lib/module/ui/navigation/OxyRouter.js +10 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js +72 -75
- package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +285 -125
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
- package/lib/module/ui/screens/ProfileScreen.js +1 -1
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +44 -53
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +6 -4
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +1 -1
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +1 -5
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -4
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
- package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/ui/components/Header.d.ts +9 -0
- package/lib/typescript/ui/components/Header.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
- package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
- package/lib/typescript/ui/components/index.d.ts +0 -1
- package/lib/typescript/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
- package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
- package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/types.d.ts +5 -0
- 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/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
- package/package.json +10 -2
- package/src/models/interfaces.ts +2 -5
- package/src/models/session.ts +1 -4
- package/src/ui/components/AnimationExample.tsx +194 -0
- package/src/ui/components/FollowButton.tsx +65 -45
- package/src/ui/components/GroupedItem.tsx +1 -0
- package/src/ui/components/GroupedSection.tsx +1 -1
- package/src/ui/components/Header.tsx +36 -12
- package/src/ui/components/OxyProvider.tsx +66 -32
- package/src/ui/components/ProfileCard.tsx +6 -8
- package/src/ui/components/index.ts +0 -1
- package/src/ui/components/internal/TextField.tsx +12 -6
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
- package/src/ui/context/OxyContext.tsx +84 -54
- package/src/ui/hooks/useFollow.types.ts +33 -0
- package/src/ui/navigation/OxyRouter.tsx +10 -0
- package/src/ui/navigation/types.ts +6 -0
- package/src/ui/screens/AccountCenterScreen.tsx +13 -7
- package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
- package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
- package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
- package/src/ui/screens/FeedbackScreen.tsx +57 -80
- package/src/ui/screens/FileManagementScreen.tsx +278 -175
- package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
- package/src/ui/screens/ProfileScreen.tsx +6 -1
- package/src/ui/screens/SessionManagementScreen.tsx +148 -151
- package/src/ui/screens/SignInScreen.tsx +43 -62
- package/src/ui/screens/SignUpScreen.tsx +3 -5
- package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
- package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
- package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
- package/src/ui/styles/authStyles.ts +1 -1
|
@@ -22,10 +22,34 @@ import { Ionicons } from '@expo/vector-icons';
|
|
|
22
22
|
import type { FileMetadata } from '../../models/interfaces';
|
|
23
23
|
import { useFileStore, useFiles, useUploading as useUploadingStore, useUploadAggregateProgress, useDeleting as useDeletingStore } from '../stores/fileStore';
|
|
24
24
|
import Header from '../components/Header';
|
|
25
|
+
import JustifiedPhotoGrid from '../components/photogrid/JustifiedPhotoGrid';
|
|
25
26
|
import { GroupedSection } from '../components';
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
// Exporting props & callback types so external callers (e.g. showBottomSheet config objects) can annotate
|
|
29
|
+
export type OnConfirmFileSelection = (files: FileMetadata[]) => void;
|
|
30
|
+
|
|
31
|
+
export interface FileManagementScreenProps extends BaseScreenProps {
|
|
28
32
|
userId?: string;
|
|
33
|
+
// Enable selection mode (acts like a picker). When true, opening a file selects it instead of showing viewer
|
|
34
|
+
selectMode?: boolean;
|
|
35
|
+
// Allow selecting multiple files; only used if selectMode is true
|
|
36
|
+
multiSelect?: boolean;
|
|
37
|
+
// Callback when a file is selected (single select mode)
|
|
38
|
+
onSelect?: (file: FileMetadata) => void;
|
|
39
|
+
// Callback when confirm pressed in multi-select mode
|
|
40
|
+
onConfirmSelection?: OnConfirmFileSelection;
|
|
41
|
+
// Initial selected file IDs for multi-select
|
|
42
|
+
initialSelectedIds?: string[];
|
|
43
|
+
maxSelection?: number;
|
|
44
|
+
disabledMimeTypes?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* What to do after a single selection (non-multiSelect) is made.
|
|
47
|
+
* 'close' (default) will dismiss the bottom sheet via onClose.
|
|
48
|
+
* 'back' will navigate back to the previous screen (e.g., return to AccountSettings without closing sheet).
|
|
49
|
+
* 'none' will keep the picker open (caller can manually close or navigate).
|
|
50
|
+
*/
|
|
51
|
+
afterSelect?: 'close' | 'back' | 'none';
|
|
52
|
+
allowUploadInSelectMode?: boolean;
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
// Add this helper function near the top (after imports):
|
|
@@ -40,6 +64,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
40
64
|
navigate,
|
|
41
65
|
userId,
|
|
42
66
|
containerWidth = 400, // Fallback for when not provided by the router
|
|
67
|
+
selectMode = false,
|
|
68
|
+
multiSelect = false,
|
|
69
|
+
onSelect,
|
|
70
|
+
onConfirmSelection,
|
|
71
|
+
initialSelectedIds = [],
|
|
72
|
+
maxSelection,
|
|
73
|
+
disabledMimeTypes = [],
|
|
74
|
+
afterSelect = 'close',
|
|
75
|
+
allowUploadInSelectMode = true,
|
|
43
76
|
}) => {
|
|
44
77
|
const { user, oxyServices } = useOxy();
|
|
45
78
|
|
|
@@ -60,8 +93,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
60
93
|
const deleting = useDeletingStore();
|
|
61
94
|
const [loading, setLoading] = useState(true);
|
|
62
95
|
const [refreshing, setRefreshing] = useState(false);
|
|
96
|
+
const [paging, setPaging] = useState({ offset: 0, limit: 40, total: 0, hasMore: true, loadingMore: false });
|
|
63
97
|
const [selectedFile, setSelectedFile] = useState<FileMetadata | null>(null);
|
|
64
98
|
const [showFileDetails, setShowFileDetails] = useState(false);
|
|
99
|
+
// In selectMode we never open the detailed viewer
|
|
65
100
|
const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
|
|
66
101
|
const [fileContent, setFileContent] = useState<string | null>(null);
|
|
67
102
|
const [loadingFileContent, setLoadingFileContent] = useState(false);
|
|
@@ -90,6 +125,57 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
90
125
|
const [hoveredPreview, setHoveredPreview] = useState<string | null>(null);
|
|
91
126
|
const uploadStartRef = useRef<number | null>(null);
|
|
92
127
|
const MIN_BANNER_MS = 600;
|
|
128
|
+
// Selection state
|
|
129
|
+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(initialSelectedIds));
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (initialSelectedIds && initialSelectedIds.length) {
|
|
132
|
+
setSelectedIds(new Set(initialSelectedIds));
|
|
133
|
+
}
|
|
134
|
+
}, [initialSelectedIds]);
|
|
135
|
+
|
|
136
|
+
const toggleSelect = useCallback((file: FileMetadata) => {
|
|
137
|
+
if (!selectMode) return;
|
|
138
|
+
if (disabledMimeTypes.length) {
|
|
139
|
+
const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : mt + '/'));
|
|
140
|
+
if (blocked) {
|
|
141
|
+
toast.error('This file type cannot be selected');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!multiSelect) {
|
|
146
|
+
onSelect?.(file);
|
|
147
|
+
if (afterSelect === 'back') {
|
|
148
|
+
goBack?.();
|
|
149
|
+
} else if (afterSelect === 'close') {
|
|
150
|
+
onClose?.();
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
setSelectedIds(prev => {
|
|
155
|
+
const next = new Set(prev);
|
|
156
|
+
const already = next.has(file.id);
|
|
157
|
+
if (!already) {
|
|
158
|
+
if (maxSelection && next.size >= maxSelection) {
|
|
159
|
+
toast.error(`You can select up to ${maxSelection}`);
|
|
160
|
+
return prev;
|
|
161
|
+
}
|
|
162
|
+
next.add(file.id);
|
|
163
|
+
} else {
|
|
164
|
+
next.delete(file.id);
|
|
165
|
+
}
|
|
166
|
+
return next;
|
|
167
|
+
});
|
|
168
|
+
}, [selectMode, multiSelect, onSelect, onClose, goBack, disabledMimeTypes, maxSelection, afterSelect]);
|
|
169
|
+
|
|
170
|
+
const confirmMultiSelection = useCallback(() => {
|
|
171
|
+
if (!selectMode || !multiSelect) return;
|
|
172
|
+
const map: Record<string, FileMetadata> = {};
|
|
173
|
+
files.forEach(f => { map[f.id] = f; });
|
|
174
|
+
const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
|
|
175
|
+
onConfirmSelection?.(chosen);
|
|
176
|
+
onClose?.();
|
|
177
|
+
}, [selectMode, multiSelect, selectedIds, files, onConfirmSelection, onClose]);
|
|
178
|
+
|
|
93
179
|
const endUpload = useCallback(() => {
|
|
94
180
|
const started = uploadStartRef.current;
|
|
95
181
|
const elapsed = started ? Date.now() - started : MIN_BANNER_MS;
|
|
@@ -162,7 +248,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
162
248
|
const storeSetUploadProgress = useFileStore(s => s.setUploadProgress);
|
|
163
249
|
const storeSetDeleting = useFileStore(s => s.setDeleting);
|
|
164
250
|
|
|
165
|
-
const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' = 'initial') => {
|
|
251
|
+
const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' | 'more' = 'initial') => {
|
|
166
252
|
if (!targetUserId) return;
|
|
167
253
|
|
|
168
254
|
try {
|
|
@@ -170,9 +256,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
170
256
|
setRefreshing(true);
|
|
171
257
|
} else if (mode === 'initial') {
|
|
172
258
|
setLoading(true);
|
|
259
|
+
setPaging(p => ({ ...p, offset: 0, hasMore: true }));
|
|
260
|
+
} else if (mode === 'more') {
|
|
261
|
+
// Prevent duplicate fetches
|
|
262
|
+
setPaging(p => ({ ...p, loadingMore: true }));
|
|
173
263
|
}
|
|
174
|
-
|
|
175
|
-
const
|
|
264
|
+
const currentPaging = mode === 'more' ? (prevPagingRef.current ?? paging) : paging;
|
|
265
|
+
const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
|
|
266
|
+
const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
|
|
176
267
|
const assets: FileMetadata[] = (response.files || []).map((f: any) => ({
|
|
177
268
|
id: f.id,
|
|
178
269
|
filename: f.originalName || f.sha256,
|
|
@@ -183,16 +274,39 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
183
274
|
metadata: f.metadata || {},
|
|
184
275
|
variants: f.variants || [],
|
|
185
276
|
}));
|
|
186
|
-
|
|
187
|
-
|
|
277
|
+
if (mode === 'more') {
|
|
278
|
+
// append
|
|
279
|
+
useFileStore.getState().setFiles(assets, { merge: true });
|
|
280
|
+
setPaging(p => ({
|
|
281
|
+
...p,
|
|
282
|
+
offset: effectiveOffset,
|
|
283
|
+
total: response.total || (effectiveOffset + assets.length),
|
|
284
|
+
hasMore: response.hasMore,
|
|
285
|
+
loadingMore: false,
|
|
286
|
+
}));
|
|
287
|
+
} else {
|
|
288
|
+
useFileStore.getState().setFiles(assets, { merge: false });
|
|
289
|
+
setPaging(p => ({
|
|
290
|
+
...p,
|
|
291
|
+
offset: 0,
|
|
292
|
+
total: response.total || assets.length,
|
|
293
|
+
hasMore: response.hasMore,
|
|
294
|
+
loadingMore: false,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
188
297
|
} catch (error: any) {
|
|
189
298
|
console.error('Failed to load files:', error);
|
|
190
299
|
toast.error(error.message || 'Failed to load files');
|
|
191
300
|
} finally {
|
|
192
301
|
setLoading(false);
|
|
193
302
|
setRefreshing(false);
|
|
303
|
+
setPaging(p => ({ ...p, loadingMore: false }));
|
|
194
304
|
}
|
|
195
|
-
}, [targetUserId, oxyServices]);
|
|
305
|
+
}, [targetUserId, oxyServices, paging]);
|
|
306
|
+
|
|
307
|
+
// Keep a ref to avoid stale closure when calculating next offset
|
|
308
|
+
const prevPagingRef = useRef(paging);
|
|
309
|
+
useEffect(() => { prevPagingRef.current = paging; }, [paging]);
|
|
196
310
|
|
|
197
311
|
// (removed effect; filteredFiles is memoized)
|
|
198
312
|
|
|
@@ -607,6 +721,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
607
721
|
};
|
|
608
722
|
|
|
609
723
|
const handleFileOpen = async (file: FileMetadata) => {
|
|
724
|
+
if (selectMode) {
|
|
725
|
+
toggleSelect(file);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
610
728
|
try {
|
|
611
729
|
setLoadingFileContent(true);
|
|
612
730
|
setOpenedFile(file);
|
|
@@ -691,6 +809,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
691
809
|
width: itemWidth,
|
|
692
810
|
height: itemWidth,
|
|
693
811
|
marginRight: (index + 1) % itemsPerRow === 0 ? 0 : 4,
|
|
812
|
+
...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: themeStyles.primaryColor } : {})
|
|
694
813
|
}
|
|
695
814
|
]}
|
|
696
815
|
onPress={() => handleFileOpen(photo)}
|
|
@@ -708,10 +827,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
708
827
|
}}
|
|
709
828
|
accessibilityLabel={photo.filename}
|
|
710
829
|
/>
|
|
830
|
+
{selectMode && (
|
|
831
|
+
<View style={styles.selectionBadge}>
|
|
832
|
+
<Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? themeStyles.primaryColor : themeStyles.textColor} />
|
|
833
|
+
</View>
|
|
834
|
+
)}
|
|
711
835
|
</View>
|
|
712
836
|
</TouchableOpacity>
|
|
713
837
|
);
|
|
714
|
-
}, [oxyServices, containerWidth]);
|
|
838
|
+
}, [oxyServices, containerWidth, selectMode, selectedIds, themeStyles.primaryColor, themeStyles.textColor]);
|
|
715
839
|
|
|
716
840
|
const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
|
|
717
841
|
const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
|
|
@@ -724,7 +848,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
724
848
|
{
|
|
725
849
|
width,
|
|
726
850
|
height,
|
|
727
|
-
|
|
851
|
+
...(selectMode && selectedIds.has(photo.id) ? { borderWidth: 2, borderColor: themeStyles.primaryColor } : {}),
|
|
852
|
+
...(selectMode && multiSelect && selectedIds.size > 0 && !selectedIds.has(photo.id) ? { opacity: 0.4 } : {}),
|
|
853
|
+
},
|
|
728
854
|
]}
|
|
729
855
|
onPress={() => handleFileOpen(photo)}
|
|
730
856
|
activeOpacity={0.8}
|
|
@@ -741,10 +867,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
741
867
|
}}
|
|
742
868
|
accessibilityLabel={photo.filename}
|
|
743
869
|
/>
|
|
870
|
+
{selectMode && (
|
|
871
|
+
<View style={styles.selectionBadge}>
|
|
872
|
+
<Ionicons name={selectedIds.has(photo.id) ? 'checkmark-circle' : 'ellipse-outline'} size={20} color={selectedIds.has(photo.id) ? themeStyles.primaryColor : themeStyles.textColor} />
|
|
873
|
+
</View>
|
|
874
|
+
)}
|
|
744
875
|
</View>
|
|
745
876
|
</TouchableOpacity>
|
|
746
877
|
);
|
|
747
|
-
}, [oxyServices]);
|
|
878
|
+
}, [oxyServices, selectMode, selectedIds, multiSelect, themeStyles.primaryColor, themeStyles.textColor]);
|
|
748
879
|
|
|
749
880
|
// Run initial load once per targetUserId change to avoid accidental loops
|
|
750
881
|
const lastLoadedFor = useRef<string | undefined>(undefined);
|
|
@@ -768,7 +899,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
768
899
|
return (
|
|
769
900
|
<View
|
|
770
901
|
key={file.id}
|
|
771
|
-
style={[styles.fileItem, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }]}
|
|
902
|
+
style={[styles.fileItem, { backgroundColor: themeStyles.secondaryBackgroundColor, borderColor }, selectMode && selectedIds.has(file.id) && { borderColor: themeStyles.primaryColor, borderWidth: 2 }]}
|
|
772
903
|
>
|
|
773
904
|
<TouchableOpacity
|
|
774
905
|
style={styles.fileContent}
|
|
@@ -834,11 +965,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
834
965
|
</View>
|
|
835
966
|
|
|
836
967
|
{/* Preview overlay for hover effect */}
|
|
837
|
-
{Platform.OS === 'web' && hoveredPreview === file.id && isImage && (
|
|
968
|
+
{!selectMode && Platform.OS === 'web' && hoveredPreview === file.id && isImage && (
|
|
838
969
|
<View style={styles.previewOverlay}>
|
|
839
970
|
<Ionicons name="eye" size={24} color="#FFFFFF" />
|
|
840
971
|
</View>
|
|
841
972
|
)}
|
|
973
|
+
{selectMode && (
|
|
974
|
+
<View style={styles.selectionBadge}>
|
|
975
|
+
<Ionicons name={selectedIds.has(file.id) ? 'checkmark-circle' : 'ellipse-outline'} size={22} color={selectedIds.has(file.id) ? themeStyles.primaryColor : themeStyles.textColor} />
|
|
976
|
+
</View>
|
|
977
|
+
)}
|
|
842
978
|
</View>
|
|
843
979
|
) : (
|
|
844
980
|
<View style={styles.fileIconContainer}>
|
|
@@ -869,39 +1005,41 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
869
1005
|
</View>
|
|
870
1006
|
</TouchableOpacity>
|
|
871
1007
|
|
|
872
|
-
|
|
873
|
-
{
|
|
874
|
-
|
|
1008
|
+
{!selectMode && (
|
|
1009
|
+
<View style={styles.fileActions}>
|
|
1010
|
+
{/* Preview button for supported files */}
|
|
1011
|
+
{hasPreview && (
|
|
1012
|
+
<TouchableOpacity
|
|
1013
|
+
style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
|
|
1014
|
+
onPress={() => handleFileOpen(file)}
|
|
1015
|
+
>
|
|
1016
|
+
<Ionicons name="eye" size={20} color={themeStyles.primaryColor} />
|
|
1017
|
+
</TouchableOpacity>
|
|
1018
|
+
)}
|
|
1019
|
+
|
|
875
1020
|
<TouchableOpacity
|
|
876
1021
|
style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
|
|
877
|
-
onPress={() =>
|
|
1022
|
+
onPress={() => handleFileDownload(file.id, file.filename)}
|
|
878
1023
|
>
|
|
879
|
-
<Ionicons name="
|
|
1024
|
+
<Ionicons name="download" size={20} color={themeStyles.primaryColor} />
|
|
880
1025
|
</TouchableOpacity>
|
|
881
|
-
)}
|
|
882
|
-
|
|
883
|
-
<TouchableOpacity
|
|
884
|
-
style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#333333' : '#F0F0F0' }]}
|
|
885
|
-
onPress={() => handleFileDownload(file.id, file.filename)}
|
|
886
|
-
>
|
|
887
|
-
<Ionicons name="download" size={20} color={themeStyles.primaryColor} />
|
|
888
|
-
</TouchableOpacity>
|
|
889
1026
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1027
|
+
{/* Always show delete button for debugging */}
|
|
1028
|
+
<TouchableOpacity
|
|
1029
|
+
style={[styles.actionButton, { backgroundColor: themeStyles.isDarkTheme ? '#400000' : '#FFEBEE' }]}
|
|
1030
|
+
onPress={() => {
|
|
1031
|
+
handleFileDelete(file.id, file.filename);
|
|
1032
|
+
}}
|
|
1033
|
+
disabled={deleting === file.id}
|
|
1034
|
+
>
|
|
1035
|
+
{deleting === file.id ? (
|
|
1036
|
+
<ActivityIndicator size="small" color={themeStyles.dangerColor} />
|
|
1037
|
+
) : (
|
|
1038
|
+
<Ionicons name="trash" size={20} color={themeStyles.dangerColor} />
|
|
1039
|
+
)}
|
|
1040
|
+
</TouchableOpacity>
|
|
1041
|
+
</View>
|
|
1042
|
+
)}
|
|
905
1043
|
</View>
|
|
906
1044
|
);
|
|
907
1045
|
};
|
|
@@ -909,13 +1047,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
909
1047
|
// GroupedSection-based file items (for 'all' view) replacing legacy flat list look
|
|
910
1048
|
const groupedFileItems = useMemo(() => {
|
|
911
1049
|
return filteredFiles
|
|
912
|
-
.filter(f => true)
|
|
1050
|
+
.filter(f => true)
|
|
913
1051
|
.sort((a, b) => new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime())
|
|
914
1052
|
.map((file) => {
|
|
915
1053
|
const isImage = file.contentType.startsWith('image/');
|
|
916
1054
|
const isVideo = file.contentType.startsWith('video/');
|
|
917
1055
|
const hasPreview = isImage || isVideo;
|
|
918
1056
|
const previewUrl = hasPreview ? (isVideo ? getSafeDownloadUrl(file, 'poster') : getSafeDownloadUrl(file, 'thumb')) : undefined;
|
|
1057
|
+
const isSelected = selectedIds.has(file.id);
|
|
919
1058
|
return {
|
|
920
1059
|
id: file.id,
|
|
921
1060
|
image: previewUrl,
|
|
@@ -929,7 +1068,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
929
1068
|
showChevron: false,
|
|
930
1069
|
dense: true,
|
|
931
1070
|
multiRow: !!file.metadata?.description,
|
|
932
|
-
|
|
1071
|
+
selected: selectMode && isSelected,
|
|
1072
|
+
// Hide action buttons when selecting
|
|
1073
|
+
customContent: !selectMode ? (
|
|
933
1074
|
<View style={styles.groupedActions}>
|
|
934
1075
|
{(isImage || isVideo || file.contentType.includes('pdf')) && (
|
|
935
1076
|
<TouchableOpacity
|
|
@@ -957,15 +1098,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
957
1098
|
)}
|
|
958
1099
|
</TouchableOpacity>
|
|
959
1100
|
</View>
|
|
960
|
-
),
|
|
1101
|
+
) : undefined,
|
|
961
1102
|
customContentBelow: file.metadata?.description ? (
|
|
962
1103
|
<Text style={[styles.groupedDescription, { color: themeStyles.isDarkTheme ? '#AAAAAA' : '#666666' }]} numberOfLines={2}>
|
|
963
1104
|
{file.metadata.description}
|
|
964
1105
|
</Text>
|
|
965
1106
|
) : undefined,
|
|
966
|
-
} as any;
|
|
1107
|
+
} as any;
|
|
967
1108
|
});
|
|
968
|
-
}, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl]);
|
|
1109
|
+
}, [filteredFiles, theme, themeStyles, deleting, handleFileDownload, handleFileDelete, handleFileOpen, getSafeDownloadUrl, selectMode, selectedIds]);
|
|
969
1110
|
|
|
970
1111
|
const renderPhotoItem = (photo: FileMetadata, index: number) => {
|
|
971
1112
|
const downloadUrl = getSafeDownloadUrl(photo, 'thumb');
|
|
@@ -1056,6 +1197,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1056
1197
|
/>
|
|
1057
1198
|
}
|
|
1058
1199
|
showsVerticalScrollIndicator={false}
|
|
1200
|
+
onScroll={({ nativeEvent }) => {
|
|
1201
|
+
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
|
|
1202
|
+
const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
|
|
1203
|
+
if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
|
|
1204
|
+
loadFiles('more');
|
|
1205
|
+
}
|
|
1206
|
+
}}
|
|
1207
|
+
scrollEventThrottle={250}
|
|
1059
1208
|
>
|
|
1060
1209
|
{loadingDimensions && (
|
|
1061
1210
|
<View style={styles.dimensionsLoadingIndicator}>
|
|
@@ -1094,129 +1243,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1094
1243
|
containerWidth
|
|
1095
1244
|
]);
|
|
1096
1245
|
|
|
1097
|
-
//
|
|
1098
|
-
const JustifiedPhotoGrid = React.memo(({
|
|
1099
|
-
photos,
|
|
1100
|
-
photoDimensions,
|
|
1101
|
-
loadPhotoDimensions,
|
|
1102
|
-
createJustifiedRows,
|
|
1103
|
-
renderJustifiedPhotoItem,
|
|
1104
|
-
renderSimplePhotoItem,
|
|
1105
|
-
textColor,
|
|
1106
|
-
containerWidth
|
|
1107
|
-
}: {
|
|
1108
|
-
photos: FileMetadata[];
|
|
1109
|
-
photoDimensions: { [key: string]: { width: number, height: number } };
|
|
1110
|
-
loadPhotoDimensions: (photos: FileMetadata[]) => Promise<void>;
|
|
1111
|
-
createJustifiedRows: (photos: FileMetadata[], containerWidth: number) => FileMetadata[][];
|
|
1112
|
-
renderJustifiedPhotoItem: (photo: FileMetadata, width: number, height: number, isLast: boolean) => React.ReactElement;
|
|
1113
|
-
renderSimplePhotoItem: (photo: FileMetadata, index: number) => React.ReactElement;
|
|
1114
|
-
textColor: string;
|
|
1115
|
-
containerWidth: number;
|
|
1116
|
-
}) => {
|
|
1117
|
-
// Load dimensions for new photos
|
|
1118
|
-
React.useEffect(() => {
|
|
1119
|
-
loadPhotoDimensions(photos);
|
|
1120
|
-
// Depend only on photo IDs to avoid re-running from dimension state changes
|
|
1121
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1122
|
-
}, [photos.map(p => p.id).join(',')]);
|
|
1123
|
-
|
|
1124
|
-
// Group photos by date
|
|
1125
|
-
const photosByDate = React.useMemo(() => {
|
|
1126
|
-
return photos.reduce((groups: { [key: string]: FileMetadata[] }, photo) => {
|
|
1127
|
-
const date = new Date(photo.uploadDate).toDateString();
|
|
1128
|
-
if (!groups[date]) {
|
|
1129
|
-
groups[date] = [];
|
|
1130
|
-
}
|
|
1131
|
-
groups[date].push(photo);
|
|
1132
|
-
return groups;
|
|
1133
|
-
}, {});
|
|
1134
|
-
}, [photos]);
|
|
1135
|
-
|
|
1136
|
-
const sortedDates = React.useMemo(() => {
|
|
1137
|
-
return Object.keys(photosByDate).sort((a, b) =>
|
|
1138
|
-
new Date(b).getTime() - new Date(a).getTime()
|
|
1139
|
-
);
|
|
1140
|
-
}, [photosByDate]);
|
|
1141
|
-
|
|
1142
|
-
return (
|
|
1143
|
-
<>
|
|
1144
|
-
{sortedDates.map(date => {
|
|
1145
|
-
const dayPhotos = photosByDate[date];
|
|
1146
|
-
const justifiedRows = createJustifiedRows(dayPhotos, containerWidth);
|
|
1147
|
-
|
|
1148
|
-
return (
|
|
1149
|
-
<View key={date} style={styles.photoDateSection}>
|
|
1150
|
-
<Text style={[styles.photoDateHeader, { color: themeStyles.textColor }]}>
|
|
1151
|
-
{new Date(date).toLocaleDateString('en-US', {
|
|
1152
|
-
weekday: 'long',
|
|
1153
|
-
year: 'numeric',
|
|
1154
|
-
month: 'long',
|
|
1155
|
-
day: 'numeric'
|
|
1156
|
-
})}
|
|
1157
|
-
</Text>
|
|
1158
|
-
<View style={styles.justifiedPhotoGrid}>
|
|
1159
|
-
{justifiedRows.map((row, rowIndex) => {
|
|
1160
|
-
// Calculate row height based on available width
|
|
1161
|
-
const gap = 4;
|
|
1162
|
-
let totalAspectRatio = 0;
|
|
1163
|
-
|
|
1164
|
-
// Calculate total aspect ratio for this row
|
|
1165
|
-
row.forEach(photo => {
|
|
1166
|
-
const dimensions = photoDimensions[photo.id];
|
|
1167
|
-
const aspectRatio = dimensions ?
|
|
1168
|
-
(dimensions.width / dimensions.height) :
|
|
1169
|
-
1.33; // Default 4:3 ratio
|
|
1170
|
-
totalAspectRatio += aspectRatio;
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
// Calculate the height that makes the row fill the available width
|
|
1174
|
-
// Account for photoScrollContainer padding (32px total) and gaps between photos
|
|
1175
|
-
const scrollContainerPadding = 32;
|
|
1176
|
-
const availableWidth = containerWidth - scrollContainerPadding - (gap * (row.length - 1));
|
|
1177
|
-
const calculatedHeight = availableWidth / totalAspectRatio;
|
|
1178
|
-
|
|
1179
|
-
// Clamp height for visual consistency
|
|
1180
|
-
const rowHeight = Math.max(120, Math.min(calculatedHeight, 300));
|
|
1181
|
-
|
|
1182
|
-
return (
|
|
1183
|
-
<View
|
|
1184
|
-
key={`row-${rowIndex}`}
|
|
1185
|
-
style={[
|
|
1186
|
-
styles.justifiedPhotoRow,
|
|
1187
|
-
{
|
|
1188
|
-
height: rowHeight,
|
|
1189
|
-
maxWidth: containerWidth - 32, // Account for scroll container padding
|
|
1190
|
-
gap: 4, // Add horizontal gap between photos in row
|
|
1191
|
-
}
|
|
1192
|
-
]}
|
|
1193
|
-
>
|
|
1194
|
-
{row.map((photo, photoIndex) => {
|
|
1195
|
-
const dimensions = photoDimensions[photo.id];
|
|
1196
|
-
const aspectRatio = dimensions ?
|
|
1197
|
-
(dimensions.width / dimensions.height) :
|
|
1198
|
-
1.33; // Default 4:3 ratio
|
|
1199
|
-
|
|
1200
|
-
const photoWidth = rowHeight * aspectRatio;
|
|
1201
|
-
const isLast = photoIndex === row.length - 1;
|
|
1202
|
-
|
|
1203
|
-
return renderJustifiedPhotoItem(
|
|
1204
|
-
photo,
|
|
1205
|
-
photoWidth,
|
|
1206
|
-
rowHeight,
|
|
1207
|
-
isLast
|
|
1208
|
-
);
|
|
1209
|
-
})}
|
|
1210
|
-
</View>
|
|
1211
|
-
);
|
|
1212
|
-
})}
|
|
1213
|
-
</View>
|
|
1214
|
-
</View>
|
|
1215
|
-
);
|
|
1216
|
-
})}
|
|
1217
|
-
</>
|
|
1218
|
-
);
|
|
1219
|
-
});
|
|
1246
|
+
// Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
|
|
1220
1247
|
|
|
1221
1248
|
const renderFileDetailsModal = () => {
|
|
1222
1249
|
const backgroundColor = themeStyles.backgroundColor;
|
|
@@ -1641,7 +1668,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1641
1668
|
}
|
|
1642
1669
|
|
|
1643
1670
|
// If a file is opened, show the file viewer
|
|
1644
|
-
if (openedFile) {
|
|
1671
|
+
if (!selectMode && openedFile) {
|
|
1645
1672
|
return (
|
|
1646
1673
|
<>
|
|
1647
1674
|
{renderFileViewer()}
|
|
@@ -1664,8 +1691,22 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1664
1691
|
} : {})}
|
|
1665
1692
|
>
|
|
1666
1693
|
<Header
|
|
1667
|
-
title={viewMode === 'photos' ? 'Photos' : 'File Management'}
|
|
1668
|
-
subtitle={`${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`}
|
|
1694
|
+
title={selectMode ? (multiSelect ? `${selectedIds.size}${maxSelection ? '/' + maxSelection : ''} Selected` : 'Select a File') : (viewMode === 'photos' ? 'Photos' : 'File Management')}
|
|
1695
|
+
subtitle={selectMode ? (multiSelect ? `${filteredFiles.length} available` : 'Tap to select') : `${filteredFiles.length} ${filteredFiles.length === 1 ? 'item' : 'items'}`}
|
|
1696
|
+
rightActions={selectMode && multiSelect ? [
|
|
1697
|
+
{
|
|
1698
|
+
key: 'clear',
|
|
1699
|
+
text: 'Clear',
|
|
1700
|
+
onPress: () => setSelectedIds(new Set()),
|
|
1701
|
+
disabled: selectedIds.size === 0,
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
key: 'confirm',
|
|
1705
|
+
text: 'Confirm',
|
|
1706
|
+
onPress: confirmMultiSelection,
|
|
1707
|
+
disabled: selectedIds.size === 0,
|
|
1708
|
+
}
|
|
1709
|
+
] : undefined}
|
|
1669
1710
|
onBack={onClose || goBack}
|
|
1670
1711
|
theme={theme}
|
|
1671
1712
|
showBackButton
|
|
@@ -1710,7 +1751,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1710
1751
|
/>
|
|
1711
1752
|
</TouchableOpacity>
|
|
1712
1753
|
</View>
|
|
1713
|
-
{user?.id === targetUserId && (
|
|
1754
|
+
{user?.id === targetUserId && (!selectMode || (selectMode && allowUploadInSelectMode)) && (
|
|
1714
1755
|
<TouchableOpacity
|
|
1715
1756
|
style={[styles.uploadButton, { backgroundColor: themeStyles.primaryColor }]}
|
|
1716
1757
|
onPress={handleFileUpload}
|
|
@@ -1808,6 +1849,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1808
1849
|
tintColor={themeStyles.primaryColor}
|
|
1809
1850
|
/>
|
|
1810
1851
|
}
|
|
1852
|
+
onScroll={({ nativeEvent }) => {
|
|
1853
|
+
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
|
|
1854
|
+
const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
|
|
1855
|
+
if (distanceFromBottom < 200 && !paging.loadingMore && paging.hasMore) {
|
|
1856
|
+
loadFiles('more');
|
|
1857
|
+
}
|
|
1858
|
+
}}
|
|
1859
|
+
scrollEventThrottle={250}
|
|
1811
1860
|
>
|
|
1812
1861
|
{filteredFiles.length === 0 && searchQuery.length > 0 ? (
|
|
1813
1862
|
<View style={styles.emptyState}>
|
|
@@ -1825,15 +1874,23 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1825
1874
|
</TouchableOpacity>
|
|
1826
1875
|
</View>
|
|
1827
1876
|
) : filteredFiles.length === 0 ? renderEmptyState() : (
|
|
1828
|
-
|
|
1877
|
+
<>
|
|
1878
|
+
<GroupedSection items={groupedFileItems} theme={theme as 'light' | 'dark'} />
|
|
1879
|
+
{paging.loadingMore && (
|
|
1880
|
+
<View style={styles.loadingMoreBar}>
|
|
1881
|
+
<ActivityIndicator size="small" color={themeStyles.primaryColor} />
|
|
1882
|
+
<Text style={[styles.loadingMoreText, { color: themeStyles.textColor }]}>Loading more...</Text>
|
|
1883
|
+
</View>
|
|
1884
|
+
)}
|
|
1885
|
+
</>
|
|
1829
1886
|
)}
|
|
1830
1887
|
</ScrollView>
|
|
1831
1888
|
)}
|
|
1832
1889
|
|
|
1833
|
-
{renderFileDetailsModal()}
|
|
1890
|
+
{!selectMode && renderFileDetailsModal()}
|
|
1834
1891
|
|
|
1835
1892
|
{/* Uploading banner overlay */}
|
|
1836
|
-
{uploading && (
|
|
1893
|
+
{!selectMode && uploading && (
|
|
1837
1894
|
<View pointerEvents="none" style={styles.uploadBannerContainer}>
|
|
1838
1895
|
<View style={[styles.uploadBanner, { backgroundColor: themeStyles.isDarkTheme ? '#222831EE' : '#FFFFFFEE', borderColor: themeStyles.borderColor }]}>
|
|
1839
1896
|
<Ionicons name="cloud-upload" size={18} color={themeStyles.primaryColor} />
|
|
@@ -1847,6 +1904,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1847
1904
|
</View>
|
|
1848
1905
|
)}
|
|
1849
1906
|
|
|
1907
|
+
{/* Selection bar removed; actions are now in header */}
|
|
1908
|
+
{/* Global loadingMore bar removed; now inline in scroll areas */}
|
|
1909
|
+
|
|
1850
1910
|
{/* Drag and Drop Overlay */}
|
|
1851
1911
|
{isDragging && Platform.OS === 'web' && (
|
|
1852
1912
|
<View style={styles.dragDropOverlay}>
|
|
@@ -1869,6 +1929,48 @@ const styles = StyleSheet.create({
|
|
|
1869
1929
|
container: {
|
|
1870
1930
|
flex: 1,
|
|
1871
1931
|
},
|
|
1932
|
+
selectionBadge: {
|
|
1933
|
+
position: 'absolute',
|
|
1934
|
+
top: 4,
|
|
1935
|
+
right: 4,
|
|
1936
|
+
backgroundColor: 'rgba(0,0,0,0.4)',
|
|
1937
|
+
borderRadius: 12,
|
|
1938
|
+
padding: 2,
|
|
1939
|
+
},
|
|
1940
|
+
selectionBar: {
|
|
1941
|
+
position: 'absolute',
|
|
1942
|
+
left: 0,
|
|
1943
|
+
right: 0,
|
|
1944
|
+
bottom: 0,
|
|
1945
|
+
flexDirection: 'row',
|
|
1946
|
+
justifyContent: 'space-between',
|
|
1947
|
+
padding: 12,
|
|
1948
|
+
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
1949
|
+
gap: 12,
|
|
1950
|
+
},
|
|
1951
|
+
selectionBarButton: {
|
|
1952
|
+
flex: 1,
|
|
1953
|
+
paddingVertical: 14,
|
|
1954
|
+
borderRadius: 10,
|
|
1955
|
+
alignItems: 'center',
|
|
1956
|
+
justifyContent: 'center',
|
|
1957
|
+
},
|
|
1958
|
+
selectionBarButtonText: {
|
|
1959
|
+
color: '#FFFFFF',
|
|
1960
|
+
fontSize: 15,
|
|
1961
|
+
fontWeight: '600',
|
|
1962
|
+
},
|
|
1963
|
+
loadingMoreBar: {
|
|
1964
|
+
flexDirection: 'row',
|
|
1965
|
+
alignItems: 'center',
|
|
1966
|
+
justifyContent: 'center',
|
|
1967
|
+
paddingVertical: 12,
|
|
1968
|
+
gap: 8,
|
|
1969
|
+
},
|
|
1970
|
+
loadingMoreText: {
|
|
1971
|
+
fontSize: 13,
|
|
1972
|
+
fontWeight: '500',
|
|
1973
|
+
},
|
|
1872
1974
|
dragOverlay: {
|
|
1873
1975
|
backgroundColor: 'rgba(0, 122, 255, 0.06)',
|
|
1874
1976
|
borderWidth: 1,
|
|
@@ -2026,6 +2128,7 @@ const styles = StyleSheet.create({
|
|
|
2026
2128
|
},
|
|
2027
2129
|
scrollView: {
|
|
2028
2130
|
flex: 1,
|
|
2131
|
+
backgroundColor: '#e5f1ff',
|
|
2029
2132
|
},
|
|
2030
2133
|
scrollContainer: {
|
|
2031
2134
|
padding: 12,
|