@oxyhq/services 22.10.0 → 22.11.0
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/AvatarCameraBadge.js +42 -0
- package/lib/commonjs/ui/components/AvatarCameraBadge.js.map +1 -0
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +41 -19
- package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAuthChooser.js +640 -168
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileButton.js +14 -0
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileSummaryCard.js +3 -23
- package/lib/commonjs/ui/components/ProfileSummaryCard.js.map +1 -1
- package/lib/commonjs/ui/components/SurfaceScreen.js +41 -5
- package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +78 -26
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +83 -23
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +73 -37
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +18 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useFileQueries.js +129 -0
- package/lib/commonjs/ui/hooks/queries/useFileQueries.js.map +1 -0
- package/lib/commonjs/ui/hooks/useAvatarPicker.js +36 -48
- package/lib/commonjs/ui/hooks/useAvatarPicker.js.map +1 -1
- package/lib/commonjs/ui/hooks/useReduceMotion.js +57 -0
- package/lib/commonjs/ui/hooks/useReduceMotion.js.map +1 -0
- package/lib/commonjs/ui/hooks/useSurfaceHeader.js +1 -1
- package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/commonjs/ui/navigation/accountDialogManager.js +22 -0
- package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -1
- package/lib/commonjs/ui/navigation/bottomSheetManager.js +5 -6
- package/lib/commonjs/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +1 -0
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceNavStack.js +62 -15
- package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceRegistry.js +38 -17
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaces.js +98 -21
- package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
- package/lib/commonjs/ui/screens/AvatarCropScreen.js +281 -279
- package/lib/commonjs/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +301 -0
- package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/FileManagementScreen.js +194 -431
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +16 -48
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js +63 -0
- package/lib/commonjs/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +54 -41
- package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +44 -81
- package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/commonjs/ui/stores/resetSessionScopedStores.js +8 -3
- package/lib/commonjs/ui/stores/resetSessionScopedStores.js.map +1 -1
- package/lib/commonjs/ui/utils/avatarUtils.js +11 -2
- package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
- package/lib/commonjs/ui/utils/fileManagement.js +0 -79
- package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
- package/lib/module/ui/components/AvatarCameraBadge.js +40 -0
- package/lib/module/ui/components/AvatarCameraBadge.js.map +1 -0
- package/lib/module/ui/components/OxyAccountDialogScreen.js +41 -19
- package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +642 -171
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +15 -1
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/ProfileSummaryCard.js +2 -23
- package/lib/module/ui/components/ProfileSummaryCard.js.map +1 -1
- package/lib/module/ui/components/SurfaceScreen.js +43 -7
- package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
- package/lib/module/ui/components/oxyAuthChooserStyles.js +78 -26
- package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +83 -25
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +74 -38
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/queries/queryKeys.js +16 -0
- package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/module/ui/hooks/queries/useFileQueries.js +120 -0
- package/lib/module/ui/hooks/queries/useFileQueries.js.map +1 -0
- package/lib/module/ui/hooks/useAvatarPicker.js +37 -49
- package/lib/module/ui/hooks/useAvatarPicker.js.map +1 -1
- package/lib/module/ui/hooks/useReduceMotion.js +53 -0
- package/lib/module/ui/hooks/useReduceMotion.js.map +1 -0
- package/lib/module/ui/hooks/useSurfaceHeader.js +1 -1
- package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
- package/lib/module/ui/navigation/accountDialogManager.js +20 -0
- package/lib/module/ui/navigation/accountDialogManager.js.map +1 -1
- package/lib/module/ui/navigation/bottomSheetManager.js +6 -7
- package/lib/module/ui/navigation/bottomSheetManager.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +1 -0
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/navigation/surfaceNavStack.js +62 -15
- package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
- package/lib/module/ui/navigation/surfaceRegistry.js +38 -17
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/module/ui/navigation/surfaces.js +97 -22
- package/lib/module/ui/navigation/surfaces.js.map +1 -1
- package/lib/module/ui/screens/AvatarCropScreen.js +283 -281
- package/lib/module/ui/screens/AvatarCropScreen.js.map +1 -1
- package/lib/module/ui/screens/ChangeAvatarScreen.js +298 -0
- package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -0
- package/lib/module/ui/screens/FileManagementScreen.js +196 -433
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +17 -49
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/FileLibraryError.js +59 -0
- package/lib/module/ui/screens/fileManagement/FileLibraryError.js.map +1 -0
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +54 -42
- package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +45 -81
- package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
- package/lib/module/ui/stores/resetSessionScopedStores.js +8 -3
- package/lib/module/ui/stores/resetSessionScopedStores.js.map +1 -1
- package/lib/module/ui/utils/avatarUtils.js +11 -2
- package/lib/module/ui/utils/avatarUtils.js.map +1 -1
- package/lib/module/ui/utils/fileManagement.js +0 -78
- package/lib/module/ui/utils/fileManagement.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts +4 -0
- package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts +2 -1
- package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileSummaryCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts +1 -4
- package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +71 -19
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
- package/lib/typescript/commonjs/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts +9 -0
- package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts +50 -0
- package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts +15 -10
- package/lib/typescript/commonjs/ui/hooks/useAvatarPicker.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts +14 -0
- package/lib/typescript/commonjs/ui/hooks/useReduceMotion.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +13 -0
- package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts +4 -5
- package/lib/typescript/commonjs/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +21 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts +73 -13
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts +42 -11
- package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts +18 -5
- package/lib/typescript/commonjs/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +34 -0
- package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
- package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts +8 -1
- package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/types/navigation.d.ts +2 -0
- package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts +0 -48
- package/lib/typescript/commonjs/ui/utils/fileManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts +4 -0
- package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts +2 -1
- package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileSummaryCard.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SurfaceScreen.d.ts +1 -4
- package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +71 -19
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts +10 -8
- package/lib/typescript/module/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts +9 -0
- package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts +50 -0
- package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts +15 -10
- package/lib/typescript/module/ui/hooks/useAvatarPicker.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts +14 -0
- package/lib/typescript/module/ui/hooks/useReduceMotion.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +13 -0
- package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts +4 -5
- package/lib/typescript/module/ui/navigation/bottomSheetManager.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +21 -0
- package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts +73 -13
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/surfaces.d.ts +42 -11
- package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts +18 -5
- package/lib/typescript/module/ui/screens/AvatarCropScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +34 -0
- package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts +23 -0
- package/lib/typescript/module/ui/screens/fileManagement/FileLibraryError.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts +4 -0
- package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +10 -2
- package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
- package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts +8 -1
- package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts.map +1 -1
- package/lib/typescript/module/ui/types/navigation.d.ts +2 -0
- package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/fileManagement.d.ts +0 -48
- package/lib/typescript/module/ui/utils/fileManagement.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/ui/components/AvatarCameraBadge.tsx +35 -0
- package/src/ui/components/OxyAccountDialogScreen.tsx +38 -15
- package/src/ui/components/OxyAuthChooser.tsx +722 -185
- package/src/ui/components/ProfileButton.tsx +16 -1
- package/src/ui/components/ProfileSummaryCard.tsx +2 -25
- package/src/ui/components/SurfaceScreen.tsx +48 -14
- package/src/ui/components/oxyAuthChooserStyles.ts +75 -23
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -84
- package/src/ui/components/photogrid/__tests__/chunkPhotos.test.ts +51 -0
- package/src/ui/context/OxyContext.tsx +74 -34
- package/src/ui/hooks/queries/queryKeys.ts +15 -0
- package/src/ui/hooks/queries/useFileQueries.ts +162 -0
- package/src/ui/hooks/useAvatarPicker.ts +38 -53
- package/src/ui/hooks/useReduceMotion.ts +58 -0
- package/src/ui/hooks/useSurfaceHeader.ts +2 -1
- package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +55 -7
- package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +81 -0
- package/src/ui/navigation/accountDialogManager.ts +28 -0
- package/src/ui/navigation/bottomSheetManager.ts +6 -7
- package/src/ui/navigation/routes.ts +3 -1
- package/src/ui/navigation/surfaceNavStack.ts +72 -9
- package/src/ui/navigation/surfaceRegistry.ts +91 -17
- package/src/ui/navigation/surfaces.ts +108 -29
- package/src/ui/screens/AvatarCropScreen.tsx +336 -298
- package/src/ui/screens/ChangeAvatarScreen.tsx +359 -0
- package/src/ui/screens/FileManagementScreen.tsx +189 -432
- package/src/ui/screens/WelcomeNewUserScreen.tsx +18 -48
- package/src/ui/screens/fileManagement/FileLibraryError.tsx +56 -0
- package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +56 -46
- package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +55 -71
- package/src/ui/stores/resetSessionScopedStores.ts +8 -3
- package/src/ui/types/navigation.ts +2 -0
- package/src/ui/utils/avatarUtils.ts +11 -2
- package/src/ui/utils/fileManagement.ts +1 -101
- package/lib/commonjs/ui/stores/fileStore.js +0 -153
- package/lib/commonjs/ui/stores/fileStore.js.map +0 -1
- package/lib/module/ui/stores/fileStore.js +0 -145
- package/lib/module/ui/stores/fileStore.js.map +0 -1
- package/lib/typescript/commonjs/ui/stores/fileStore.d.ts +0 -31
- package/lib/typescript/commonjs/ui/stores/fileStore.d.ts.map +0 -1
- package/lib/typescript/module/ui/stores/fileStore.d.ts +0 -31
- package/lib/typescript/module/ui/stores/fileStore.d.ts.map +0 -1
- package/src/ui/stores/fileStore.ts +0 -118
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState,
|
|
1
|
+
import React, { useState, useCallback, useMemo, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -8,10 +8,9 @@ import {
|
|
|
8
8
|
ActivityIndicator,
|
|
9
9
|
RefreshControl,
|
|
10
10
|
TextInput,
|
|
11
|
-
Image,
|
|
12
|
-
AccessibilityInfo,
|
|
13
11
|
useWindowDimensions,
|
|
14
12
|
} from 'react-native';
|
|
13
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
15
14
|
import { Image as ExpoImage } from 'expo-image';
|
|
16
15
|
import type { FileManagementScreenProps } from '../types/fileManagement';
|
|
17
16
|
import { toast } from '@oxyhq/bloom';
|
|
@@ -20,13 +19,15 @@ import * as Skeleton from '@oxyhq/bloom/skeleton';
|
|
|
20
19
|
import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons';
|
|
21
20
|
import { Pressable } from 'react-native';
|
|
22
21
|
import type { FileMetadata } from '@oxyhq/core';
|
|
23
|
-
import {
|
|
22
|
+
import { queryKeys } from '../hooks/queries/queryKeys';
|
|
23
|
+
import { useUserFilesInfinite, removeFileFromCache, patchFileMetadataInCache } from '../hooks/queries/useFileQueries';
|
|
24
24
|
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
25
25
|
import { SurfaceHeaderAction } from '../components/SurfaceHeaderAction';
|
|
26
26
|
import JustifiedPhotoGrid from '../components/photogrid/JustifiedPhotoGrid';
|
|
27
27
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
28
28
|
import { useOxy } from '../context/OxyContext';
|
|
29
29
|
import { useI18n } from '../hooks/useI18n';
|
|
30
|
+
import { useReduceMotion } from '../hooks/useReduceMotion';
|
|
30
31
|
import { useUploadFile } from '../hooks/mutations/useAccountMutations';
|
|
31
32
|
import {
|
|
32
33
|
formatFileSize,
|
|
@@ -41,6 +42,7 @@ import { getErrorMessage } from './fileManagement/shared';
|
|
|
41
42
|
import { useFileUploadState } from './fileManagement/hooks/useFileUploadState';
|
|
42
43
|
import PhotoPickerView from './fileManagement/PhotoPickerSection';
|
|
43
44
|
import FileListSection, { type FileListItem } from './fileManagement/FileListSection';
|
|
45
|
+
import FileLibraryError from './fileManagement/FileLibraryError';
|
|
44
46
|
import UploadBar from './fileManagement/UploadBar';
|
|
45
47
|
import { AnimatedButton } from '../components/fileManagement/AnimatedButton';
|
|
46
48
|
|
|
@@ -72,13 +74,11 @@ const screenStyles = StyleSheet.create({
|
|
|
72
74
|
justifyContent: 'center',
|
|
73
75
|
marginHorizontal: 1,
|
|
74
76
|
},
|
|
75
|
-
photoImage: { width: '100%', height: '100%', borderRadius: 8 },
|
|
76
77
|
justifiedPhotoImage: { width: '100%', height: '100%', borderRadius: 6 },
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
80
81
|
onClose,
|
|
81
|
-
theme,
|
|
82
82
|
goBack,
|
|
83
83
|
navigate,
|
|
84
84
|
dismiss,
|
|
@@ -102,8 +102,35 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
102
102
|
// against `user`, so switching shows/manages that account's files.
|
|
103
103
|
const { user, oxyServices } = useOxy();
|
|
104
104
|
const { t } = useI18n();
|
|
105
|
+
const queryClient = useQueryClient();
|
|
105
106
|
const uploadFileMutation = useUploadFile();
|
|
106
|
-
|
|
107
|
+
|
|
108
|
+
// Files are owned by the ACTIVE account. `targetUserId` scopes the query key,
|
|
109
|
+
// so switching accounts (or an explicit `userId` prop) shows that account's
|
|
110
|
+
// library with no manual reset.
|
|
111
|
+
const targetUserId = userId || user?.id;
|
|
112
|
+
|
|
113
|
+
// The file list is React Query's, NOT a store: infinite-paginated, fetched on
|
|
114
|
+
// mount + on owner change, edited optimistically via cache helpers. The
|
|
115
|
+
// rendered list is the flattened pages.
|
|
116
|
+
const filesQuery = useUserFilesInfinite(targetUserId);
|
|
117
|
+
const files = useMemo(
|
|
118
|
+
() => filesQuery.data?.pages.flatMap((page) => page.files) ?? [],
|
|
119
|
+
[filesQuery.data],
|
|
120
|
+
);
|
|
121
|
+
// No owner resolved yet reads as "loading" (matches the old skeleton-until-
|
|
122
|
+
// first-load behaviour); `isLoading` is the first-page fetch only.
|
|
123
|
+
const isLoadingFiles = !targetUserId || filesQuery.isLoading;
|
|
124
|
+
const isRefreshingFiles = filesQuery.isRefetching;
|
|
125
|
+
const isFetchingMore = filesQuery.isFetchingNextPage;
|
|
126
|
+
const hasMoreFiles = filesQuery.hasNextPage ?? false;
|
|
127
|
+
// Terminal load failure with nothing cached — render a DISTINCT error state
|
|
128
|
+
// (not the empty state) so a failed load never looks like an empty library.
|
|
129
|
+
const hasLoadError = filesQuery.isError && files.length === 0;
|
|
130
|
+
const invalidateFiles = useCallback(() => {
|
|
131
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.files.list(targetUserId) });
|
|
132
|
+
}, [queryClient, targetUserId]);
|
|
133
|
+
|
|
107
134
|
const { width: windowWidth } = useWindowDimensions();
|
|
108
135
|
// Prefer an explicit sheet width from the router; fall back to the window
|
|
109
136
|
// so photo grids never size against a hardcoded 400px default.
|
|
@@ -111,38 +138,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
111
138
|
typeof containerWidth === 'number' && containerWidth > 0
|
|
112
139
|
? containerWidth
|
|
113
140
|
: windowWidth;
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const deleting = useDeletingStore();
|
|
117
|
-
const [loading, setLoading] = useState(true);
|
|
118
|
-
const [refreshing, setRefreshing] = useState(false);
|
|
119
|
-
const [paging, setPaging] = useState({ offset: 0, limit: 40, total: 0, hasMore: true, loadingMore: false });
|
|
141
|
+
// In-flight delete target (local UI state — was in the file store).
|
|
142
|
+
const [deletingId, setDeletingId] = useState<string | null>(null);
|
|
120
143
|
// In selectMode we never open the detailed viewer
|
|
121
144
|
const [openedFile, setOpenedFile] = useState<FileMetadata | null>(null);
|
|
122
145
|
const [fileContent, setFileContent] = useState<string | null>(null);
|
|
123
146
|
const [loadingFileContent, setLoadingFileContent] = useState(false);
|
|
124
147
|
const [showFileDetailsInViewer, setShowFileDetailsInViewer] = useState(false);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Used by `PhotoPickerView` to skip cell stagger animations.
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
let cancelled = false;
|
|
131
|
-
AccessibilityInfo.isReduceMotionEnabled()
|
|
132
|
-
.then((enabled) => {
|
|
133
|
-
if (!cancelled) setReduceMotion(enabled);
|
|
134
|
-
})
|
|
135
|
-
.catch(() => {
|
|
136
|
-
// Defaults to false; no action needed.
|
|
137
|
-
});
|
|
138
|
-
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', (enabled) => {
|
|
139
|
-
setReduceMotion(enabled);
|
|
140
|
-
});
|
|
141
|
-
return () => {
|
|
142
|
-
cancelled = true;
|
|
143
|
-
sub.remove();
|
|
144
|
-
};
|
|
145
|
-
}, []);
|
|
148
|
+
// Reduce-motion preference, read as an external store (no effect). Passed to
|
|
149
|
+
// `PhotoPickerView` to skip cell stagger animations.
|
|
150
|
+
const reduceMotion = useReduceMotion();
|
|
146
151
|
|
|
147
152
|
// Image-only picker mode: when the consumer restricts to image MIME types
|
|
148
153
|
// (e.g. avatar picker), photos grid is the more useful default view.
|
|
@@ -216,25 +221,54 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
216
221
|
|
|
217
222
|
return sorted;
|
|
218
223
|
}, [files, searchQuery, viewMode, sortBy, sortOrder]);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const [
|
|
223
|
-
// Selection state
|
|
224
|
-
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set(initialSelectedIds));
|
|
225
|
-
const [lastSelectedFileId, setLastSelectedFileId] = useState<string | null>(null);
|
|
224
|
+
// Selection state. `initialSelectedIds` is the INITIAL selection only (lazy
|
|
225
|
+
// init) — there is no prop→state sync effect, since no caller mutates it
|
|
226
|
+
// after mount.
|
|
227
|
+
const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set(initialSelectedIds));
|
|
226
228
|
const scrollViewRef = useRef<ScrollView>(null);
|
|
227
229
|
const photoScrollViewRef = useRef<ScrollView>(null);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
|
|
231
|
+
// Scroll the browse list/grid to a just-selected file. Driven directly from
|
|
232
|
+
// the selection event handler (double-rAF so the selection re-render has
|
|
233
|
+
// committed first) — not an effect. No-ops in the image-only picker, whose
|
|
234
|
+
// own FlatList is not either ScrollView ref.
|
|
235
|
+
const scrollToSelectedFile = useCallback((fileId: string) => {
|
|
236
|
+
if (viewMode === 'all' && scrollViewRef.current) {
|
|
237
|
+
const itemIndex = filteredFiles.findIndex(file => file.id === fileId);
|
|
238
|
+
if (itemIndex < 0) return;
|
|
239
|
+
// GroupedItem dense rows are ~65px, +30px when a description wraps.
|
|
240
|
+
const baseItemHeight = 65;
|
|
241
|
+
const descriptionHeight = 30;
|
|
242
|
+
let scrollPosition = 0;
|
|
243
|
+
for (let i = 0; i <= itemIndex && i < filteredFiles.length; i++) {
|
|
244
|
+
scrollPosition += baseItemHeight;
|
|
245
|
+
if (filteredFiles[i].metadata?.description) scrollPosition += descriptionHeight;
|
|
246
|
+
}
|
|
247
|
+
// Offset for the header/controls/search/stats chrome (~250px) so the
|
|
248
|
+
// item lands near the top, not under the chrome.
|
|
249
|
+
const finalScrollPosition = 250 + scrollPosition - 150;
|
|
250
|
+
requestAnimationFrame(() => {
|
|
251
|
+
requestAnimationFrame(() => {
|
|
252
|
+
scrollViewRef.current?.scrollTo({ y: Math.max(0, finalScrollPosition), animated: true });
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
} else if (viewMode === 'photos' && photoScrollViewRef.current) {
|
|
256
|
+
const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
|
|
257
|
+
const photoIndex = photos.findIndex(p => p.id === fileId);
|
|
258
|
+
if (photoIndex < 0) return;
|
|
259
|
+
// Rough estimate for the justified grid (3 photos/row, variable heights).
|
|
260
|
+
const row = Math.floor(photoIndex / 3);
|
|
261
|
+
const finalScrollPosition = row * (150 + 4) - 100;
|
|
262
|
+
requestAnimationFrame(() => {
|
|
263
|
+
requestAnimationFrame(() => {
|
|
264
|
+
photoScrollViewRef.current?.scrollTo({ y: Math.max(0, finalScrollPosition), animated: true });
|
|
265
|
+
});
|
|
266
|
+
});
|
|
232
267
|
}
|
|
233
|
-
}, [
|
|
268
|
+
}, [viewMode, filteredFiles]);
|
|
234
269
|
|
|
235
270
|
const toggleSelect = useCallback(async (file: FileMetadata) => {
|
|
236
|
-
//
|
|
237
|
-
// if (!selectMode) return;
|
|
271
|
+
// Selection is allowed in regular mode too (for bulk operations).
|
|
238
272
|
if (disabledMimeTypes.length) {
|
|
239
273
|
const blocked = disabledMimeTypes.some(mt => file.contentType === mt || file.contentType.startsWith(mt.endsWith('/') ? mt : `${mt}/`));
|
|
240
274
|
if (blocked) {
|
|
@@ -253,9 +287,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
253
287
|
}
|
|
254
288
|
}
|
|
255
289
|
|
|
256
|
-
// Track the selected file for scrolling
|
|
257
|
-
setLastSelectedFileId(file.id);
|
|
258
|
-
|
|
259
290
|
// Link file to entity if linkContext is provided
|
|
260
291
|
if (linkContext) {
|
|
261
292
|
try {
|
|
@@ -303,12 +334,13 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
303
334
|
}
|
|
304
335
|
return next;
|
|
305
336
|
});
|
|
306
|
-
|
|
337
|
+
scrollToSelectedFile(file.id);
|
|
338
|
+
}, [multiSelect, onSelect, onClose, goBack, dismiss, disabledMimeTypes, maxSelection, afterSelect, defaultVisibility, oxyServices, linkContext, t, scrollToSelectedFile]);
|
|
307
339
|
|
|
308
340
|
const confirmMultiSelection = useCallback(async () => {
|
|
309
341
|
if (!selectMode || !multiSelect) return;
|
|
310
342
|
const map: Record<string, FileMetadata> = {};
|
|
311
|
-
|
|
343
|
+
for (const f of files) { map[f.id] = f; }
|
|
312
344
|
const chosen = Array.from(selectedIds).map(id => map[id]).filter(Boolean);
|
|
313
345
|
|
|
314
346
|
// Update visibility and link files if needed
|
|
@@ -373,70 +405,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
373
405
|
const backgroundColor = colors.backgroundSecondary;
|
|
374
406
|
const borderColor = colors.border;
|
|
375
407
|
|
|
376
|
-
const targetUserId = userId || user?.id;
|
|
377
|
-
|
|
378
|
-
const storeSetDeleting = useFileStore(s => s.setDeleting);
|
|
379
|
-
|
|
380
|
-
const loadFiles = useCallback(async (mode: 'initial' | 'refresh' | 'silent' | 'more' = 'initial') => {
|
|
381
|
-
if (!targetUserId) return;
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
if (mode === 'refresh') {
|
|
385
|
-
setRefreshing(true);
|
|
386
|
-
} else if (mode === 'initial') {
|
|
387
|
-
setLoading(true);
|
|
388
|
-
setPaging(p => ({ ...p, offset: 0, hasMore: true }));
|
|
389
|
-
} else if (mode === 'more') {
|
|
390
|
-
// Prevent duplicate fetches
|
|
391
|
-
setPaging(p => ({ ...p, loadingMore: true }));
|
|
392
|
-
}
|
|
393
|
-
const currentPaging = mode === 'more' ? (prevPagingRef.current ?? paging) : paging;
|
|
394
|
-
const effectiveOffset = mode === 'more' ? currentPaging.offset + currentPaging.limit : 0;
|
|
395
|
-
const response = await oxyServices.listUserFiles(currentPaging.limit, effectiveOffset);
|
|
396
|
-
const assets: FileMetadata[] = (response.files || []).map((f: { id: string; originalName?: string; sha256?: string; mime?: string; size?: number; createdAt?: string; metadata?: Record<string, unknown>; variants?: unknown[] }) => ({
|
|
397
|
-
id: f.id,
|
|
398
|
-
filename: f.originalName ?? f.sha256 ?? '',
|
|
399
|
-
contentType: f.mime ?? '',
|
|
400
|
-
length: f.size ?? 0,
|
|
401
|
-
chunkSize: 0,
|
|
402
|
-
uploadDate: f.createdAt ?? '',
|
|
403
|
-
metadata: f.metadata ?? {},
|
|
404
|
-
variants: (f.variants ?? []) as FileMetadata['variants'],
|
|
405
|
-
}));
|
|
406
|
-
if (mode === 'more') {
|
|
407
|
-
// append
|
|
408
|
-
useFileStore.getState().setFiles(assets, { merge: true });
|
|
409
|
-
setPaging(p => ({
|
|
410
|
-
...p,
|
|
411
|
-
offset: effectiveOffset,
|
|
412
|
-
total: response.total || (effectiveOffset + assets.length),
|
|
413
|
-
hasMore: response.hasMore,
|
|
414
|
-
loadingMore: false,
|
|
415
|
-
}));
|
|
416
|
-
} else {
|
|
417
|
-
useFileStore.getState().setFiles(assets, { merge: false });
|
|
418
|
-
setPaging(p => ({
|
|
419
|
-
...p,
|
|
420
|
-
offset: 0,
|
|
421
|
-
total: response.total || assets.length,
|
|
422
|
-
hasMore: response.hasMore,
|
|
423
|
-
loadingMore: false,
|
|
424
|
-
}));
|
|
425
|
-
}
|
|
426
|
-
} catch (error: unknown) {
|
|
427
|
-
toast.error(getErrorMessage(error) || t('fileManagement.toasts.loadFailed'));
|
|
428
|
-
} finally {
|
|
429
|
-
setLoading(false);
|
|
430
|
-
setRefreshing(false);
|
|
431
|
-
setPaging(p => ({ ...p, loadingMore: false }));
|
|
432
|
-
}
|
|
433
|
-
}, [targetUserId, oxyServices, paging]);
|
|
434
|
-
|
|
435
408
|
// Self-contained document-picking + upload-preview state and handlers.
|
|
409
|
+
// Optimistic uploads edit the file query cache directly (see
|
|
410
|
+
// useFileQueries), so no loader is threaded in.
|
|
436
411
|
const {
|
|
437
412
|
isPickingDocument,
|
|
438
413
|
pendingFiles,
|
|
439
414
|
showUploadPreview,
|
|
415
|
+
uploading,
|
|
416
|
+
uploadProgress,
|
|
440
417
|
handleFileUpload,
|
|
441
418
|
handleConfirmUpload,
|
|
442
419
|
handleCancelUpload,
|
|
@@ -453,105 +430,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
453
430
|
onClose,
|
|
454
431
|
selectedIds,
|
|
455
432
|
setSelectedIds,
|
|
456
|
-
loadFiles,
|
|
457
433
|
t,
|
|
458
434
|
});
|
|
459
435
|
|
|
460
|
-
// Keep a ref to avoid stale closure when calculating next offset
|
|
461
|
-
const prevPagingRef = useRef(paging);
|
|
462
|
-
useEffect(() => { prevPagingRef.current = paging; }, [paging]);
|
|
463
|
-
|
|
464
|
-
// (removed effect; filteredFiles is memoized)
|
|
465
|
-
|
|
466
|
-
// Load photo dimensions for justified grid - unified approach using Image.getSize
|
|
467
|
-
const loadPhotoDimensions = useCallback(async (photos: FileMetadata[]) => {
|
|
468
|
-
if (photos.length === 0) return;
|
|
469
|
-
|
|
470
|
-
setLoadingDimensions(true);
|
|
471
|
-
|
|
472
|
-
// Snapshot ids missing from the cache at kick-off; concurrent runs may
|
|
473
|
-
// overlap but each merge is applied via a functional updater.
|
|
474
|
-
const photosToLoad = photos.filter((photo) => !photoDimensionsRef.current[photo.id]);
|
|
475
|
-
|
|
476
|
-
if (photosToLoad.length === 0) {
|
|
477
|
-
setLoadingDimensions(false);
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
try {
|
|
482
|
-
const measured: { [key: string]: { width: number; height: number } } = {};
|
|
483
|
-
|
|
484
|
-
await Promise.all(
|
|
485
|
-
photosToLoad.map(async (photo) => {
|
|
486
|
-
try {
|
|
487
|
-
const downloadUrl = thumbSourceFor(photo);
|
|
488
|
-
// URL not resolved yet — skip (do NOT record fallback
|
|
489
|
-
// dims) so this photo is re-measured once its private-safe
|
|
490
|
-
// URL resolves. Measuring the broken public URL is exactly
|
|
491
|
-
// the bug we are fixing.
|
|
492
|
-
if (!downloadUrl) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Unified approach using Image.getSize (works on all platforms)
|
|
497
|
-
await new Promise<void>((resolve) => {
|
|
498
|
-
Image.getSize(
|
|
499
|
-
downloadUrl,
|
|
500
|
-
(width: number, height: number) => {
|
|
501
|
-
measured[photo.id] = { width, height };
|
|
502
|
-
resolve();
|
|
503
|
-
},
|
|
504
|
-
() => {
|
|
505
|
-
// Fallback dimensions
|
|
506
|
-
measured[photo.id] = { width: 1, height: 1 };
|
|
507
|
-
resolve();
|
|
508
|
-
}
|
|
509
|
-
);
|
|
510
|
-
});
|
|
511
|
-
} catch (error) {
|
|
512
|
-
// Fallback dimensions for any errors
|
|
513
|
-
measured[photo.id] = { width: 1, height: 1 };
|
|
514
|
-
}
|
|
515
|
-
})
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
if (Object.keys(measured).length > 0) {
|
|
519
|
-
setPhotoDimensions((prev) => ({ ...prev, ...measured }));
|
|
520
|
-
}
|
|
521
|
-
} catch (error) {
|
|
522
|
-
// Photo dimensions loading failed, continue without dimensions
|
|
523
|
-
} finally {
|
|
524
|
-
setLoadingDimensions(false);
|
|
525
|
-
}
|
|
526
|
-
}, [thumbSourceFor]);
|
|
527
|
-
|
|
528
|
-
// Re-measure photo dimensions when their private-safe URLs resolve. The
|
|
529
|
-
// justified grid's own trigger fires on the photo SET, not on URL
|
|
530
|
-
// availability, so newly-resolved private tiles would otherwise keep their
|
|
531
|
-
// skipped (unmeasured) state and render with fallback geometry.
|
|
532
|
-
useEffect(() => {
|
|
533
|
-
if (viewMode !== 'photos') return;
|
|
534
|
-
const photos = filteredFiles.filter((file) => file.contentType.startsWith('image/'));
|
|
535
|
-
if (photos.length > 0) {
|
|
536
|
-
loadPhotoDimensions(photos);
|
|
537
|
-
}
|
|
538
|
-
}, [resolvedThumbUrls, viewMode, filteredFiles, loadPhotoDimensions]);
|
|
539
|
-
|
|
540
|
-
// Create justified rows from photos with responsive algorithm
|
|
541
|
-
const createJustifiedRows = useCallback((photos: FileMetadata[], containerWidth: number) => {
|
|
542
|
-
if (photos.length === 0) return [];
|
|
543
|
-
|
|
544
|
-
const rows: FileMetadata[][] = [];
|
|
545
|
-
const photosPerRow = 3; // Fixed 3 photos per row for consistency
|
|
546
|
-
|
|
547
|
-
for (let i = 0; i < photos.length; i += photosPerRow) {
|
|
548
|
-
const rowPhotos = photos.slice(i, i + photosPerRow);
|
|
549
|
-
rows.push(rowPhotos);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
return rows;
|
|
553
|
-
}, []);
|
|
554
|
-
|
|
555
436
|
const confirmFileDelete = useCallback(async (fileId: string, filename: string) => {
|
|
556
437
|
const confirmed = await surfaces.confirm({
|
|
557
438
|
title: t('fileManagement.deleteFile') || 'Delete File',
|
|
@@ -563,32 +444,29 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
563
444
|
if (!confirmed) return;
|
|
564
445
|
|
|
565
446
|
try {
|
|
566
|
-
|
|
447
|
+
setDeletingId(fileId);
|
|
567
448
|
await oxyServices.deleteFile(fileId);
|
|
568
449
|
|
|
569
450
|
toast.success(t('fileManagement.toasts.deleteSuccess'));
|
|
570
451
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
// Silent background reconcile
|
|
575
|
-
setTimeout(() => loadFiles('silent'), 800);
|
|
452
|
+
// Remove from the query cache, then reconcile against the server.
|
|
453
|
+
removeFileFromCache(queryClient, targetUserId, fileId);
|
|
454
|
+
invalidateFiles();
|
|
576
455
|
} catch (error: unknown) {
|
|
577
|
-
|
|
578
456
|
// Provide specific error messages
|
|
579
457
|
if (getErrorMessage(error)?.includes('File not found') || getErrorMessage(error)?.includes('404')) {
|
|
580
458
|
toast.error(t('fileManagement.toasts.fileNotFound'));
|
|
581
|
-
//
|
|
582
|
-
|
|
459
|
+
// It's already gone server-side — reconcile the list.
|
|
460
|
+
invalidateFiles();
|
|
583
461
|
} else if (getErrorMessage(error)?.includes('permission') || getErrorMessage(error)?.includes('403')) {
|
|
584
462
|
toast.error(t('fileManagement.toasts.noPermission'));
|
|
585
463
|
} else {
|
|
586
464
|
toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
|
|
587
465
|
}
|
|
588
466
|
} finally {
|
|
589
|
-
|
|
467
|
+
setDeletingId(null);
|
|
590
468
|
}
|
|
591
|
-
}, [
|
|
469
|
+
}, [oxyServices, queryClient, targetUserId, invalidateFiles, t]);
|
|
592
470
|
|
|
593
471
|
const confirmBulkDelete = useCallback(async () => {
|
|
594
472
|
if (selectedIds.size === 0) return;
|
|
@@ -606,7 +484,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
606
484
|
const deletePromises = Array.from(selectedIds).map(async (fileId) => {
|
|
607
485
|
try {
|
|
608
486
|
await oxyServices.deleteFile(fileId);
|
|
609
|
-
|
|
487
|
+
removeFileFromCache(queryClient, targetUserId, fileId);
|
|
610
488
|
return { success: true, fileId };
|
|
611
489
|
} catch (error: unknown) {
|
|
612
490
|
return { success: false, fileId, error };
|
|
@@ -625,11 +503,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
625
503
|
}
|
|
626
504
|
|
|
627
505
|
setSelectedIds(new Set());
|
|
628
|
-
|
|
506
|
+
invalidateFiles();
|
|
629
507
|
} catch (error: unknown) {
|
|
630
508
|
toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
|
|
631
509
|
}
|
|
632
|
-
}, [selectedIds, oxyServices,
|
|
510
|
+
}, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
|
|
633
511
|
|
|
634
512
|
const handleBulkVisibilityChange = useCallback(async (visibility: 'private' | 'public' | 'unlisted') => {
|
|
635
513
|
if (selectedIds.size === 0) return;
|
|
@@ -650,22 +528,20 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
650
528
|
|
|
651
529
|
if (successful > 0) {
|
|
652
530
|
toast.success(t('fileManagement.toasts.visibilitySuccess', { count: successful, visibility }));
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
});
|
|
658
|
-
});
|
|
531
|
+
// Reflect the new visibility in the cached files.
|
|
532
|
+
for (const fileId of selectedIds) {
|
|
533
|
+
patchFileMetadataInCache(queryClient, targetUserId, fileId, { visibility });
|
|
534
|
+
}
|
|
659
535
|
}
|
|
660
536
|
if (failed > 0) {
|
|
661
537
|
toast.error(t('fileManagement.toasts.visibilityFailed', { count: failed }));
|
|
662
538
|
}
|
|
663
539
|
|
|
664
|
-
|
|
540
|
+
invalidateFiles();
|
|
665
541
|
} catch (error: unknown) {
|
|
666
542
|
toast.error(getErrorMessage(error) || t('fileManagement.toasts.visibilityError'));
|
|
667
543
|
}
|
|
668
|
-
}, [selectedIds, oxyServices,
|
|
544
|
+
}, [selectedIds, oxyServices, queryClient, targetUserId, invalidateFiles, t]);
|
|
669
545
|
|
|
670
546
|
const handleVisibilityChange = useCallback(async () => {
|
|
671
547
|
if (selectedIds.size === 0) return;
|
|
@@ -683,7 +559,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
683
559
|
}, [selectedIds.size, handleBulkVisibilityChange, t]);
|
|
684
560
|
|
|
685
561
|
// Unified download function - works on all platforms
|
|
686
|
-
const handleFileDownload = async (fileId: string, filename: string) => {
|
|
562
|
+
const handleFileDownload = useCallback(async (fileId: string, filename: string) => {
|
|
687
563
|
try {
|
|
688
564
|
// Resolve an authenticated, private-safe URL. The synchronous
|
|
689
565
|
// `getFileDownloadUrl` yields the public CDN origin, which 404s for
|
|
@@ -727,10 +603,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
727
603
|
} catch (error: unknown) {
|
|
728
604
|
toast.error(getErrorMessage(error) || t('fileManagement.toasts.downloadFailed'));
|
|
729
605
|
}
|
|
730
|
-
};
|
|
606
|
+
}, [oxyServices, t]);
|
|
731
607
|
|
|
732
|
-
|
|
733
|
-
const handleFileOpen = async (file: FileMetadata) => {
|
|
608
|
+
const handleFileOpen = useCallback(async (file: FileMetadata) => {
|
|
734
609
|
if (selectMode) {
|
|
735
610
|
toggleSelect(file);
|
|
736
611
|
return;
|
|
@@ -782,23 +657,23 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
782
657
|
} finally {
|
|
783
658
|
setLoadingFileContent(false);
|
|
784
659
|
}
|
|
785
|
-
};
|
|
660
|
+
}, [selectMode, toggleSelect, oxyServices, t]);
|
|
786
661
|
|
|
787
|
-
const handleCloseFile = () => {
|
|
662
|
+
const handleCloseFile = useCallback(() => {
|
|
788
663
|
setOpenedFile(null);
|
|
789
664
|
setFileContent(null);
|
|
790
665
|
setShowFileDetailsInViewer(false);
|
|
791
666
|
// Don't reset view mode when closing a file
|
|
792
|
-
};
|
|
667
|
+
}, []);
|
|
793
668
|
|
|
794
|
-
const showFileDetailsModal = (file: FileMetadata) => {
|
|
669
|
+
const showFileDetailsModal = useCallback((file: FileMetadata) => {
|
|
795
670
|
presentFileDetails({
|
|
796
671
|
file,
|
|
797
672
|
onDownload: handleFileDownload,
|
|
798
673
|
onDelete: confirmFileDelete,
|
|
799
674
|
isOwner: user?.id === targetUserId,
|
|
800
675
|
});
|
|
801
|
-
};
|
|
676
|
+
}, [handleFileDownload, confirmFileDelete, user?.id, targetUserId]);
|
|
802
677
|
|
|
803
678
|
const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
|
|
804
679
|
const downloadUrl = thumbSourceFor(photo);
|
|
@@ -836,18 +711,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
836
711
|
</View>
|
|
837
712
|
</TouchableOpacity>
|
|
838
713
|
);
|
|
839
|
-
}, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text]);
|
|
840
|
-
|
|
841
|
-
// Run initial load once per targetUserId change to avoid accidental loops
|
|
842
|
-
const lastLoadedFor = useRef<string | undefined>(undefined);
|
|
843
|
-
useEffect(() => {
|
|
844
|
-
const key = targetUserId || 'anonymous';
|
|
845
|
-
if (lastLoadedFor.current !== key) {
|
|
846
|
-
lastLoadedFor.current = key;
|
|
847
|
-
loadFiles('initial');
|
|
848
|
-
}
|
|
849
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
850
|
-
}, [targetUserId]);
|
|
714
|
+
}, [thumbSourceFor, selectMode, selectedIds, multiSelect, colors.primary, colors.text, handleFileOpen]);
|
|
851
715
|
|
|
852
716
|
// SettingsListItem-based file items (for 'all' view)
|
|
853
717
|
const groupedFileItems: FileListItem[] = useMemo(() => {
|
|
@@ -945,9 +809,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
945
809
|
className="w-[34px] h-[34px] rounded-[8px] items-center justify-center"
|
|
946
810
|
style={{ backgroundColor: colors.negativeSubtle }}
|
|
947
811
|
onPress={() => confirmFileDelete(file.id, file.filename)}
|
|
948
|
-
disabled={
|
|
812
|
+
disabled={deletingId === file.id}
|
|
949
813
|
>
|
|
950
|
-
{
|
|
814
|
+
{deletingId === file.id ? (
|
|
951
815
|
<ActivityIndicator size="small" color={colors.error} />
|
|
952
816
|
) : (
|
|
953
817
|
<Ionicons name="trash" size={18} color={colors.error} />
|
|
@@ -957,89 +821,30 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
957
821
|
) : undefined,
|
|
958
822
|
};
|
|
959
823
|
});
|
|
960
|
-
}, [filteredFiles,
|
|
961
|
-
|
|
962
|
-
//
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const sortedFiles = filteredFiles;
|
|
976
|
-
|
|
977
|
-
// Calculate total height up to this item
|
|
978
|
-
let scrollPosition = 0;
|
|
979
|
-
for (let i = 0; i <= itemIndex && i < sortedFiles.length; i++) {
|
|
980
|
-
const file = sortedFiles[i];
|
|
981
|
-
scrollPosition += baseItemHeight;
|
|
982
|
-
if (file.metadata?.description) {
|
|
983
|
-
scrollPosition += descriptionHeight;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// Add header, controls, search, and stats height (approximately 250px)
|
|
988
|
-
const headerHeight = 250;
|
|
989
|
-
const finalScrollPosition = headerHeight + scrollPosition - 150; // Offset to show item near top
|
|
990
|
-
|
|
991
|
-
// Use requestAnimationFrame to ensure DOM is updated before scrolling
|
|
992
|
-
requestAnimationFrame(() => {
|
|
993
|
-
requestAnimationFrame(() => {
|
|
994
|
-
scrollViewRef.current?.scrollTo({
|
|
995
|
-
y: Math.max(0, finalScrollPosition),
|
|
996
|
-
animated: true,
|
|
997
|
-
});
|
|
998
|
-
});
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
} else if (viewMode === 'photos' && photoScrollViewRef.current) {
|
|
1002
|
-
// For photo grid, find the photo index
|
|
1003
|
-
const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
|
|
1004
|
-
const photoIndex = photos.findIndex(p => p.id === lastSelectedFileId);
|
|
1005
|
-
|
|
1006
|
-
if (photoIndex >= 0) {
|
|
1007
|
-
// Rough scroll estimate for the justified grid (3 photos/row; variable row heights).
|
|
1008
|
-
const itemsPerRow = 3;
|
|
1009
|
-
const estimatedRowHeight = 150;
|
|
1010
|
-
const rowGap = 4;
|
|
1011
|
-
const row = Math.floor(photoIndex / itemsPerRow);
|
|
1012
|
-
const finalScrollPosition = row * (estimatedRowHeight + rowGap) - 100;
|
|
1013
|
-
|
|
1014
|
-
// Use requestAnimationFrame to ensure DOM is updated before scrolling
|
|
1015
|
-
requestAnimationFrame(() => {
|
|
1016
|
-
requestAnimationFrame(() => {
|
|
1017
|
-
photoScrollViewRef.current?.scrollTo({
|
|
1018
|
-
y: Math.max(0, finalScrollPosition),
|
|
1019
|
-
animated: true,
|
|
1020
|
-
});
|
|
1021
|
-
});
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}, [lastSelectedFileId, selectMode, viewMode, filteredFiles, safeContainerWidth]);
|
|
1027
|
-
|
|
1028
|
-
// Clear selected file ID after scroll animation completes
|
|
1029
|
-
useEffect(() => {
|
|
1030
|
-
if (lastSelectedFileId && scrollViewRef.current) {
|
|
1031
|
-
const timeoutId = setTimeout(() => {
|
|
1032
|
-
setLastSelectedFileId(null);
|
|
1033
|
-
}, 600); // Allow time for scroll animation to complete
|
|
1034
|
-
|
|
1035
|
-
return () => clearTimeout(timeoutId);
|
|
1036
|
-
}
|
|
1037
|
-
}, [lastSelectedFileId]);
|
|
824
|
+
}, [filteredFiles, colors, deletingId, toggleSelect, handleFileDownload, confirmFileDelete, handleFileOpen, thumbSourceFor, selectMode, selectedIds]);
|
|
825
|
+
|
|
826
|
+
// Browse-themed terminal load-failure state — DISTINCT from the empty state.
|
|
827
|
+
const renderLoadError = () => (
|
|
828
|
+
<FileLibraryError
|
|
829
|
+
title={t('fileManagement.loadError.title')}
|
|
830
|
+
description={t('fileManagement.loadError.description')}
|
|
831
|
+
retryLabel={t('fileManagement.retry')}
|
|
832
|
+
onRetry={() => filesQuery.refetch()}
|
|
833
|
+
iconColor={colors.error}
|
|
834
|
+
titleColor={colors.text}
|
|
835
|
+
descriptionColor={colors.textSecondary}
|
|
836
|
+
buttonColor={colors.primary}
|
|
837
|
+
/>
|
|
838
|
+
);
|
|
1038
839
|
|
|
1039
|
-
|
|
840
|
+
// Browse "photos" view. The justified grid owns its own dimension
|
|
841
|
+
// measurement + reflow (see JustifiedPhotoGrid); this only supplies the
|
|
842
|
+
// photo set, the private-safe URL resolver, and the tile renderer.
|
|
843
|
+
const renderPhotoGrid = () => {
|
|
1040
844
|
const photos = filteredFiles.filter(file => file.contentType.startsWith('image/'));
|
|
1041
845
|
|
|
1042
846
|
if (photos.length === 0) {
|
|
847
|
+
if (hasLoadError) return renderLoadError();
|
|
1043
848
|
return (
|
|
1044
849
|
<View className="items-center py-[40px] px-[24px]">
|
|
1045
850
|
<Ionicons name="images-outline" size={64} color={colors.textTertiary} />
|
|
@@ -1056,9 +861,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1056
861
|
onPress={handleFileUpload}
|
|
1057
862
|
disabled={uploading || isPickingDocument}
|
|
1058
863
|
>
|
|
1059
|
-
{uploading ? (
|
|
1060
|
-
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1061
|
-
) : isPickingDocument ? (
|
|
864
|
+
{(uploading || isPickingDocument) ? (
|
|
1062
865
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1063
866
|
) : (
|
|
1064
867
|
<>
|
|
@@ -1079,8 +882,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1079
882
|
contentContainerClassName="p-[10px]"
|
|
1080
883
|
refreshControl={
|
|
1081
884
|
<RefreshControl
|
|
1082
|
-
refreshing={
|
|
1083
|
-
onRefresh={() =>
|
|
885
|
+
refreshing={isRefreshingFiles}
|
|
886
|
+
onRefresh={() => filesQuery.refetch()}
|
|
1084
887
|
tintColor={colors.primary}
|
|
1085
888
|
/>
|
|
1086
889
|
}
|
|
@@ -1088,24 +891,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1088
891
|
onScroll={({ nativeEvent }) => {
|
|
1089
892
|
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
|
|
1090
893
|
const distanceFromBottom = contentSize.height - (contentOffset.y + layoutMeasurement.height);
|
|
1091
|
-
if (distanceFromBottom < 200 && !
|
|
1092
|
-
|
|
894
|
+
if (distanceFromBottom < 200 && !isFetchingMore && hasMoreFiles) {
|
|
895
|
+
filesQuery.fetchNextPage();
|
|
1093
896
|
}
|
|
1094
897
|
}}
|
|
1095
898
|
scrollEventThrottle={250}
|
|
1096
899
|
>
|
|
1097
|
-
{loadingDimensions && (
|
|
1098
|
-
<View className="flex-row items-center justify-center py-[16px] gap-[8px]">
|
|
1099
|
-
<ActivityIndicator size="small" color={colors.primary} />
|
|
1100
|
-
<Text className="text-[14px] italic" style={{ color: colors.textSecondary }}>{t('fileManagement.loadingPhotoLayout')}</Text>
|
|
1101
|
-
</View>
|
|
1102
|
-
)}
|
|
1103
|
-
|
|
1104
900
|
<JustifiedPhotoGrid
|
|
1105
901
|
photos={photos}
|
|
1106
|
-
|
|
1107
|
-
loadPhotoDimensions={loadPhotoDimensions}
|
|
1108
|
-
createJustifiedRows={createJustifiedRows}
|
|
902
|
+
getThumbUrl={thumbSourceFor}
|
|
1109
903
|
renderJustifiedPhotoItem={renderJustifiedPhotoItem}
|
|
1110
904
|
textColor={colors.text}
|
|
1111
905
|
containerWidth={
|
|
@@ -1116,28 +910,9 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1116
910
|
/>
|
|
1117
911
|
</ScrollView>
|
|
1118
912
|
);
|
|
1119
|
-
}
|
|
1120
|
-
filteredFiles,
|
|
1121
|
-
colors,
|
|
1122
|
-
user?.id,
|
|
1123
|
-
targetUserId,
|
|
1124
|
-
uploading,
|
|
1125
|
-
handleFileUpload,
|
|
1126
|
-
refreshing,
|
|
1127
|
-
loadFiles,
|
|
1128
|
-
loadingDimensions,
|
|
1129
|
-
photoDimensions,
|
|
1130
|
-
loadPhotoDimensions,
|
|
1131
|
-
createJustifiedRows,
|
|
1132
|
-
renderJustifiedPhotoItem,
|
|
1133
|
-
containerWidth,
|
|
1134
|
-
]);
|
|
1135
|
-
|
|
1136
|
-
// Inline justified grid removed (moved to components/photogrid/JustifiedPhotoGrid.tsx)
|
|
1137
|
-
|
|
1138
|
-
|
|
913
|
+
};
|
|
1139
914
|
|
|
1140
|
-
const renderEmptyState = () => (
|
|
915
|
+
const renderEmptyState = () => hasLoadError ? renderLoadError() : (
|
|
1141
916
|
<View className="items-center py-[40px] px-[24px]">
|
|
1142
917
|
<Ionicons name="folder-open-outline" size={64} color={colors.textTertiary} />
|
|
1143
918
|
<Text className="text-[24px] font-bold mt-[16px] mb-[8px]" style={{ color: colors.text }}>{t('fileManagement.emptyFiles.title')}</Text>
|
|
@@ -1154,9 +929,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1154
929
|
onPress={handleFileUpload}
|
|
1155
930
|
disabled={uploading || isPickingDocument}
|
|
1156
931
|
>
|
|
1157
|
-
{uploading ? (
|
|
1158
|
-
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1159
|
-
) : isPickingDocument ? (
|
|
932
|
+
{(uploading || isPickingDocument) ? (
|
|
1160
933
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1161
934
|
) : (
|
|
1162
935
|
<>
|
|
@@ -1237,7 +1010,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1237
1010
|
},
|
|
1238
1011
|
);
|
|
1239
1012
|
|
|
1240
|
-
if (
|
|
1013
|
+
if (isLoadingFiles && !isImageOnlyPicker) {
|
|
1241
1014
|
const GRID_PADDING = 10;
|
|
1242
1015
|
const TILE_GAP = 4;
|
|
1243
1016
|
const GRID_COLUMNS = 3;
|
|
@@ -1304,13 +1077,15 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1304
1077
|
multiSelect={multiSelect}
|
|
1305
1078
|
maxSelection={maxSelection}
|
|
1306
1079
|
allowUpload={allowUpload}
|
|
1307
|
-
refreshing={
|
|
1080
|
+
refreshing={isRefreshingFiles}
|
|
1308
1081
|
uploading={uploading}
|
|
1309
1082
|
isPickingDocument={isPickingDocument}
|
|
1310
1083
|
uploadProgress={uploadProgress}
|
|
1311
|
-
hasMore={
|
|
1312
|
-
loadingMore={
|
|
1313
|
-
loading={
|
|
1084
|
+
hasMore={hasMoreFiles}
|
|
1085
|
+
loadingMore={isFetchingMore}
|
|
1086
|
+
loading={isLoadingFiles}
|
|
1087
|
+
loadError={hasLoadError}
|
|
1088
|
+
onRetry={() => filesQuery.refetch()}
|
|
1314
1089
|
reduceMotion={reduceMotion}
|
|
1315
1090
|
getThumbUrl={thumbSourceFor}
|
|
1316
1091
|
primaryColor={colors.primary}
|
|
@@ -1319,8 +1094,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1319
1094
|
// Long-press preview surfaces the file-detail panel on the stack.
|
|
1320
1095
|
onPreviewPhoto={(file) => showFileDetailsModal(file)}
|
|
1321
1096
|
onUpload={handleFileUpload}
|
|
1322
|
-
onRefresh={() =>
|
|
1323
|
-
onLoadMore={() =>
|
|
1097
|
+
onRefresh={() => filesQuery.refetch()}
|
|
1098
|
+
onLoadMore={() => filesQuery.fetchNextPage()}
|
|
1324
1099
|
onCancel={() => {
|
|
1325
1100
|
if (onSelect || onConfirmSelection) {
|
|
1326
1101
|
// Legacy callback caller: preserve close/back behaviour.
|
|
@@ -1400,37 +1175,33 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1400
1175
|
style={screenStyles.viewModeButton}
|
|
1401
1176
|
accessibilityLabel={t('fileManagement.a11y.viewPhotos') || 'Show photos only'}
|
|
1402
1177
|
/>
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
accessibilityLabel={t('fileManagement.a11y.viewAudio') || 'Show audio only'}
|
|
1431
|
-
/>
|
|
1432
|
-
</>
|
|
1433
|
-
)}
|
|
1178
|
+
<AnimatedButton
|
|
1179
|
+
isSelected={viewMode === 'videos'}
|
|
1180
|
+
onPress={() => setViewMode('videos')}
|
|
1181
|
+
icon={viewMode === 'videos' ? 'video' : 'video-outline'}
|
|
1182
|
+
primaryColor={colors.primary}
|
|
1183
|
+
textColor={colors.text}
|
|
1184
|
+
style={screenStyles.viewModeButton}
|
|
1185
|
+
accessibilityLabel={t('fileManagement.a11y.viewVideos') || 'Show videos only'}
|
|
1186
|
+
/>
|
|
1187
|
+
<AnimatedButton
|
|
1188
|
+
isSelected={viewMode === 'documents'}
|
|
1189
|
+
onPress={() => setViewMode('documents')}
|
|
1190
|
+
icon={viewMode === 'documents' ? 'file-document' : 'file-document-outline'}
|
|
1191
|
+
primaryColor={colors.primary}
|
|
1192
|
+
textColor={colors.text}
|
|
1193
|
+
style={screenStyles.viewModeButton}
|
|
1194
|
+
accessibilityLabel={t('fileManagement.a11y.viewDocuments') || 'Show documents only'}
|
|
1195
|
+
/>
|
|
1196
|
+
<AnimatedButton
|
|
1197
|
+
isSelected={viewMode === 'audio'}
|
|
1198
|
+
onPress={() => setViewMode('audio')}
|
|
1199
|
+
icon={viewMode === 'audio' ? 'music-note' : 'music-note-outline'}
|
|
1200
|
+
primaryColor={colors.primary}
|
|
1201
|
+
textColor={colors.text}
|
|
1202
|
+
style={screenStyles.viewModeButton}
|
|
1203
|
+
accessibilityLabel={t('fileManagement.a11y.viewAudio') || 'Show audio only'}
|
|
1204
|
+
/>
|
|
1434
1205
|
</View>
|
|
1435
1206
|
</ScrollView>
|
|
1436
1207
|
<TouchableOpacity
|
|
@@ -1468,18 +1239,14 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1468
1239
|
color={colors.textSecondary}
|
|
1469
1240
|
/>
|
|
1470
1241
|
</TouchableOpacity>
|
|
1471
|
-
{user?.id === targetUserId && (!selectMode ||
|
|
1242
|
+
{user?.id === targetUserId && (!selectMode || allowUploadInSelectMode) && (
|
|
1472
1243
|
<TouchableOpacity
|
|
1473
|
-
className=
|
|
1244
|
+
className="h-[44px] w-[44px] rounded-[22px] items-center justify-center"
|
|
1474
1245
|
style={{ backgroundColor: colors.primary }}
|
|
1475
1246
|
onPress={handleFileUpload}
|
|
1476
1247
|
disabled={uploading || isPickingDocument}
|
|
1477
1248
|
accessibilityRole="button"
|
|
1478
|
-
accessibilityLabel={
|
|
1479
|
-
isImageOnlyPicker
|
|
1480
|
-
? (t('fileManagement.a11y.uploadFromDevice') || 'Upload photo from device')
|
|
1481
|
-
: (t('fileManagement.a11y.uploadFile') || 'Upload file')
|
|
1482
|
-
}
|
|
1249
|
+
accessibilityLabel={t('fileManagement.a11y.uploadFile') || 'Upload file'}
|
|
1483
1250
|
accessibilityState={{ busy: uploading || isPickingDocument }}
|
|
1484
1251
|
>
|
|
1485
1252
|
{uploading ? (
|
|
@@ -1493,13 +1260,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1493
1260
|
</View>
|
|
1494
1261
|
) : isPickingDocument ? (
|
|
1495
1262
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
1496
|
-
) : isImageOnlyPicker ? (
|
|
1497
|
-
<View className="flex-row items-center gap-[6px]">
|
|
1498
|
-
<Ionicons name="cloud-upload" size={18} color="#FFFFFF" />
|
|
1499
|
-
<Text className="text-white text-[14px] font-semibold" numberOfLines={1}>
|
|
1500
|
-
{t('fileManagement.upload') || 'Upload'}
|
|
1501
|
-
</Text>
|
|
1502
|
-
</View>
|
|
1503
1263
|
) : (
|
|
1504
1264
|
<Ionicons name="add" size={22} color="#FFFFFF" />
|
|
1505
1265
|
)}
|
|
@@ -1573,12 +1333,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1573
1333
|
filteredFiles={filteredFiles}
|
|
1574
1334
|
searchQuery={searchQuery}
|
|
1575
1335
|
items={groupedFileItems}
|
|
1576
|
-
paging={
|
|
1577
|
-
refreshing={
|
|
1336
|
+
paging={{ loadingMore: isFetchingMore, hasMore: hasMoreFiles }}
|
|
1337
|
+
refreshing={isRefreshingFiles}
|
|
1578
1338
|
colors={colors}
|
|
1579
1339
|
t={t}
|
|
1580
|
-
onRefresh={() =>
|
|
1581
|
-
onLoadMore={() =>
|
|
1340
|
+
onRefresh={() => filesQuery.refetch()}
|
|
1341
|
+
onLoadMore={() => filesQuery.fetchNextPage()}
|
|
1582
1342
|
onClearSearch={() => setSearchQuery('')}
|
|
1583
1343
|
renderEmptyState={renderEmptyState}
|
|
1584
1344
|
/>
|
|
@@ -1593,9 +1353,6 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1593
1353
|
t={t}
|
|
1594
1354
|
/>
|
|
1595
1355
|
)}
|
|
1596
|
-
|
|
1597
|
-
{/* Selection bar removed; actions are now in header */}
|
|
1598
|
-
{/* Global loadingMore bar removed; now inline in scroll areas */}
|
|
1599
1356
|
</View>
|
|
1600
1357
|
);
|
|
1601
1358
|
};
|