@oxy.so/services 11.6.0 → 11.9.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/android/src/main/java/so/oxy/security/OxyCallerPolicy.kt +18 -0
- package/lib/commonjs/assets/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
- package/lib/commonjs/assets/assets/fonts/icons/manifest.json +8 -5
- package/lib/commonjs/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
- package/lib/commonjs/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
- package/lib/commonjs/assets/fonts/icons/manifest.json +8 -5
- package/lib/commonjs/ui/components/fileManagement/FileViewer.js +2 -1
- package/lib/commonjs/ui/components/fileManagement/FileViewer.js.map +1 -1
- package/lib/commonjs/ui/components/supervision/PersonPicker.js +69 -0
- package/lib/commonjs/ui/components/supervision/PersonPicker.js.map +1 -0
- package/lib/commonjs/ui/components/supervision/presentation.js +23 -0
- package/lib/commonjs/ui/components/supervision/presentation.js.map +1 -0
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +3 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useFileQueries.js +39 -17
- package/lib/commonjs/ui/hooks/queries/useFileQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useSupervision.js +37 -0
- package/lib/commonjs/ui/hooks/queries/useSupervision.js.map +1 -0
- package/lib/commonjs/ui/icons/subsetGlyphMaps.js +3 -0
- package/lib/commonjs/ui/icons/subsetGlyphMaps.js.map +1 -1
- package/lib/commonjs/ui/index.js +13 -0
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +2 -0
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/navigation/surfaceRegistry.js +2 -2
- package/lib/commonjs/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/commonjs/ui/screens/CreateAccountScreen.js +10 -2
- package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +102 -44
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileTrashScreen.js +235 -0
- package/lib/commonjs/ui/screens/FileTrashScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +7 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SupervisionScreen.js +529 -0
- package/lib/commonjs/ui/screens/SupervisionScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/fileManagement/trashActions.js +147 -0
- package/lib/commonjs/ui/screens/fileManagement/trashActions.js.map +1 -0
- package/lib/commonjs/ui/screens/index.js +7 -0
- package/lib/commonjs/ui/screens/index.js.map +1 -1
- package/lib/commonjs/ui/stores/assetStore.js +22 -7
- package/lib/commonjs/ui/stores/assetStore.js.map +1 -1
- package/lib/module/assets/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
- package/lib/module/assets/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
- package/lib/module/assets/assets/fonts/icons/manifest.json +8 -5
- package/lib/module/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
- package/lib/module/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
- package/lib/module/assets/fonts/icons/manifest.json +8 -5
- package/lib/module/ui/components/fileManagement/FileViewer.js +2 -1
- package/lib/module/ui/components/fileManagement/FileViewer.js.map +1 -1
- package/lib/module/ui/components/supervision/PersonPicker.js +65 -0
- package/lib/module/ui/components/supervision/PersonPicker.js.map +1 -0
- package/lib/module/ui/components/supervision/presentation.js +19 -0
- package/lib/module/ui/components/supervision/presentation.js.map +1 -0
- package/lib/module/ui/hooks/queries/queryKeys.js +3 -1
- package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/module/ui/hooks/queries/useFileQueries.js +38 -17
- package/lib/module/ui/hooks/queries/useFileQueries.js.map +1 -1
- package/lib/module/ui/hooks/queries/useSupervision.js +32 -0
- package/lib/module/ui/hooks/queries/useSupervision.js.map +1 -0
- package/lib/module/ui/icons/subsetGlyphMaps.js +3 -0
- package/lib/module/ui/icons/subsetGlyphMaps.js.map +1 -1
- package/lib/module/ui/index.js +1 -0
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +2 -0
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/navigation/surfaceRegistry.js +2 -2
- package/lib/module/ui/navigation/surfaceRegistry.js.map +1 -1
- package/lib/module/ui/screens/CreateAccountScreen.js +10 -2
- package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +102 -44
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/FileTrashScreen.js +232 -0
- package/lib/module/ui/screens/FileTrashScreen.js.map +1 -0
- package/lib/module/ui/screens/ManageAccountScreen.js +7 -0
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/SupervisionScreen.js +525 -0
- package/lib/module/ui/screens/SupervisionScreen.js.map +1 -0
- package/lib/module/ui/screens/fileManagement/trashActions.js +138 -0
- package/lib/module/ui/screens/fileManagement/trashActions.js.map +1 -0
- package/lib/module/ui/screens/index.js +1 -0
- package/lib/module/ui/screens/index.js.map +1 -1
- package/lib/module/ui/stores/assetStore.js +22 -7
- package/lib/module/ui/stores/assetStore.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/fileManagement/FileViewer.d.ts +3 -0
- package/lib/typescript/commonjs/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/supervision/PersonPicker.d.ts +6 -0
- package/lib/typescript/commonjs/ui/components/supervision/PersonPicker.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/supervision/presentation.d.ts +3 -0
- package/lib/typescript/commonjs/ui/components/supervision/presentation.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts +2 -0
- package/lib/typescript/commonjs/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts +22 -6
- package/lib/typescript/commonjs/ui/hooks/queries/useFileQueries.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/queries/useSupervision.d.ts +475 -0
- package/lib/typescript/commonjs/ui/hooks/queries/useSupervision.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/icons/Ionicons.d.ts +1 -1
- package/lib/typescript/commonjs/ui/icons/Ionicons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts +1 -1
- package/lib/typescript/commonjs/ui/icons/MaterialCommunityIcons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/icons/subsetGlyphMaps.d.ts +3 -0
- package/lib/typescript/commonjs/ui/icons/subsetGlyphMaps.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +1 -0
- package/lib/typescript/commonjs/ui/index.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/surfaceRegistry.d.ts +6 -0
- package/lib/typescript/commonjs/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/FileTrashScreen.d.ts +14 -0
- package/lib/typescript/commonjs/ui/screens/FileTrashScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/SupervisionScreen.d.ts +3 -0
- package/lib/typescript/commonjs/ui/screens/SupervisionScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/trashActions.d.ts +66 -0
- package/lib/typescript/commonjs/ui/screens/fileManagement/trashActions.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/index.d.ts +1 -0
- package/lib/typescript/commonjs/ui/screens/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/stores/assetStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/identityCrypto.d.ts +1 -0
- package/lib/typescript/commonjs/ui/utils/identityCrypto.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/fileManagement/FileViewer.d.ts +3 -0
- package/lib/typescript/module/ui/components/fileManagement/FileViewer.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/supervision/PersonPicker.d.ts +6 -0
- package/lib/typescript/module/ui/components/supervision/PersonPicker.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/supervision/presentation.d.ts +3 -0
- package/lib/typescript/module/ui/components/supervision/presentation.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts +2 -0
- package/lib/typescript/module/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts +22 -6
- package/lib/typescript/module/ui/hooks/queries/useFileQueries.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/queries/useSupervision.d.ts +475 -0
- package/lib/typescript/module/ui/hooks/queries/useSupervision.d.ts.map +1 -0
- package/lib/typescript/module/ui/icons/Ionicons.d.ts +1 -1
- package/lib/typescript/module/ui/icons/Ionicons.d.ts.map +1 -1
- package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts +1 -1
- package/lib/typescript/module/ui/icons/MaterialCommunityIcons.d.ts.map +1 -1
- package/lib/typescript/module/ui/icons/subsetGlyphMaps.d.ts +3 -0
- package/lib/typescript/module/ui/icons/subsetGlyphMaps.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +1 -0
- package/lib/typescript/module/ui/index.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/surfaceRegistry.d.ts +6 -0
- package/lib/typescript/module/ui/navigation/surfaceRegistry.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/FileTrashScreen.d.ts +14 -0
- package/lib/typescript/module/ui/screens/FileTrashScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/SupervisionScreen.d.ts +3 -0
- package/lib/typescript/module/ui/screens/SupervisionScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/fileManagement/trashActions.d.ts +66 -0
- package/lib/typescript/module/ui/screens/fileManagement/trashActions.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/index.d.ts +1 -0
- package/lib/typescript/module/ui/screens/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/stores/assetStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/identityCrypto.d.ts +1 -0
- package/lib/typescript/module/ui/utils/identityCrypto.d.ts.map +1 -1
- package/package.json +6 -21
- package/src/assets/fonts/icons/OxyServicesIonicons.ttf +0 -0
- package/src/assets/fonts/icons/OxyServicesMaterialCommunityIcons.ttf +0 -0
- package/src/assets/fonts/icons/manifest.json +8 -5
- package/src/ui/components/fileManagement/FileViewer.tsx +5 -1
- package/src/ui/components/supervision/PersonPicker.tsx +67 -0
- package/src/ui/components/supervision/presentation.ts +17 -0
- package/src/ui/hooks/queries/__tests__/fileQueries.test.ts +70 -0
- package/src/ui/hooks/queries/queryKeys.ts +2 -0
- package/src/ui/hooks/queries/useFileQueries.ts +45 -16
- package/src/ui/hooks/queries/useSupervision.ts +25 -0
- package/src/ui/icons/subsetGlyphMaps.ts +3 -0
- package/src/ui/index.ts +2 -0
- package/src/ui/navigation/__tests__/getSurfaceConfig.test.ts +9 -0
- package/src/ui/navigation/routes.ts +4 -0
- package/src/ui/navigation/surfaceRegistry.ts +5 -1
- package/src/ui/screens/CreateAccountScreen.tsx +15 -1
- package/src/ui/screens/FileManagementScreen.tsx +142 -49
- package/src/ui/screens/FileTrashScreen.tsx +228 -0
- package/src/ui/screens/ManageAccountScreen.tsx +10 -0
- package/src/ui/screens/SupervisionScreen.tsx +593 -0
- package/src/ui/screens/fileManagement/__tests__/trashActions.test.ts +177 -0
- package/src/ui/screens/fileManagement/trashActions.ts +161 -0
- package/src/ui/screens/index.ts +2 -0
- package/src/ui/stores/assetStore.ts +24 -13
|
@@ -9,6 +9,7 @@ export const ioniconsGlyphMap = {
|
|
|
9
9
|
"arrow-back": 61735,
|
|
10
10
|
"arrow-down": 61741,
|
|
11
11
|
"arrow-forward": 61747,
|
|
12
|
+
"arrow-undo": 61759,
|
|
12
13
|
"arrow-up": 61765,
|
|
13
14
|
"at": 61771,
|
|
14
15
|
"briefcase-outline": 61874,
|
|
@@ -199,6 +200,7 @@ export const materialCommunityIconsGlyphMap = {
|
|
|
199
200
|
"code-tags": 983412,
|
|
200
201
|
"cog": 984211,
|
|
201
202
|
"cog-outline": 985275,
|
|
203
|
+
"contacts": 984779,
|
|
202
204
|
"contain": 985662,
|
|
203
205
|
"content-copy": 983439,
|
|
204
206
|
"controller": 983732,
|
|
@@ -206,6 +208,7 @@ export const materialCommunityIconsGlyphMap = {
|
|
|
206
208
|
"credit-card": 987119,
|
|
207
209
|
"cube-outline": 983463,
|
|
208
210
|
"database-outline": 988722,
|
|
211
|
+
"decimal": 987297,
|
|
209
212
|
"delete": 983476,
|
|
210
213
|
"delete-outline": 985575,
|
|
211
214
|
"devices": 987056,
|
package/src/ui/index.ts
CHANGED
|
@@ -32,6 +32,15 @@ describe('getSurfaceConfig presentation', () => {
|
|
|
32
32
|
expect(config.frameSize).toEqual({ heightRatio: 0.9, maxWidth: 640 });
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it('hosts the file Trash as a morphing own-scroller sheet (it owns its list)', () => {
|
|
36
|
+
const config = getSurfaceConfig('FileTrash', {});
|
|
37
|
+
expect(config.presentation).toBe('sheet');
|
|
38
|
+
expect(config.scrollable).toBe(false);
|
|
39
|
+
expect(config.morph).toBe(true);
|
|
40
|
+
expect(config.stacks).toBe(false);
|
|
41
|
+
expect(config.header).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
35
44
|
it('morphs the surface for every route, the picker included', () => {
|
|
36
45
|
// NAV-WITHIN: drilling into these swaps the content inside ONE surface,
|
|
37
46
|
// which reshapes the panel instead of hard-cutting.
|
|
@@ -3,6 +3,7 @@ import type { BaseScreenProps } from '../types/navigation';
|
|
|
3
3
|
|
|
4
4
|
// Define all available route names
|
|
5
5
|
export type RouteName =
|
|
6
|
+
| 'Supervision'
|
|
6
7
|
| 'ManageAccount' // Unified "Manage your Oxy Account" surface
|
|
7
8
|
| 'AccountVerification'
|
|
8
9
|
| 'PaymentGateway'
|
|
@@ -11,6 +12,7 @@ export type RouteName =
|
|
|
11
12
|
| 'PrivacySettings'
|
|
12
13
|
| 'SearchSettings'
|
|
13
14
|
| 'FileManagement'
|
|
15
|
+
| 'FileTrash' // The person's trash: restore, or delete permanently (ADR 0043 D5)
|
|
14
16
|
| 'HelpSupport'
|
|
15
17
|
| 'LegalDocuments'
|
|
16
18
|
| 'AppInfo'
|
|
@@ -71,6 +73,7 @@ export type RouteName =
|
|
|
71
73
|
* confines the gap to one documented widening in {@link getScreenComponent}.
|
|
72
74
|
*/
|
|
73
75
|
const screenComponents: Record<RouteName, ComponentType<never>> = {
|
|
76
|
+
Supervision: lazy(() => import('../screens/SupervisionScreen')),
|
|
74
77
|
ManageAccount: lazy(() => import('../screens/ManageAccountScreen')),
|
|
75
78
|
AccountVerification: lazy(() => import('../screens/AccountVerificationScreen')),
|
|
76
79
|
PaymentGateway: lazy(() => import('../screens/PaymentGatewayScreen')),
|
|
@@ -79,6 +82,7 @@ const screenComponents: Record<RouteName, ComponentType<never>> = {
|
|
|
79
82
|
PrivacySettings: lazy(() => import('../screens/PrivacySettingsScreen')),
|
|
80
83
|
SearchSettings: lazy(() => import('../screens/SearchSettingsScreen')),
|
|
81
84
|
FileManagement: lazy(() => import('../screens/FileManagementScreen')),
|
|
85
|
+
FileTrash: lazy(() => import('../screens/FileTrashScreen')),
|
|
82
86
|
HelpSupport: lazy(() => import('../screens/HelpSupportScreen')),
|
|
83
87
|
LegalDocuments: lazy(() => import('../screens/LegalDocumentsScreen')),
|
|
84
88
|
AppInfo: lazy(() => import('../screens/AppInfoScreen')),
|
|
@@ -24,6 +24,7 @@ type SurfaceRoute<Props = Record<string, unknown>, Result = void> = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export interface SurfaceRegistry {
|
|
27
|
+
Supervision: SurfaceRoute<{ approvalId?: string; childAccountId?: string }>;
|
|
27
28
|
ManageAccount: SurfaceRoute;
|
|
28
29
|
AccountVerification: SurfaceRoute;
|
|
29
30
|
PaymentGateway: SurfaceRoute;
|
|
@@ -38,6 +39,8 @@ export interface SurfaceRegistry {
|
|
|
38
39
|
* keep their callback behaviour and this result goes unread.
|
|
39
40
|
*/
|
|
40
41
|
FileManagement: SurfaceRoute<Record<string, unknown>, FileMetadata>;
|
|
42
|
+
/** The person's trash (ADR 0043 D5), opened from the file manager. */
|
|
43
|
+
FileTrash: SurfaceRoute;
|
|
41
44
|
HelpSupport: SurfaceRoute;
|
|
42
45
|
LegalDocuments: SurfaceRoute;
|
|
43
46
|
AppInfo: SurfaceRoute;
|
|
@@ -232,7 +235,7 @@ const isFileManagementImageOnlyPicker = (props: Record<string, unknown>): boolea
|
|
|
232
235
|
* Routes whose screen owns its OWN scroll container — a FlatList /
|
|
233
236
|
* VirtualizedList (`ConnectedApps`, `TrustLeaderboard`, `FollowersList` /
|
|
234
237
|
* `FollowingList` via `UserListScreen`) or its own vertical `ScrollView`
|
|
235
|
-
* (`FileManagement`). These MUST opt out of the Dialog's internal ScrollView
|
|
238
|
+
* (`FileManagement`, `FileTrash`). These MUST opt out of the Dialog's internal ScrollView
|
|
236
239
|
* (`scrollable: false`) so there is exactly ONE scroll container — otherwise a
|
|
237
240
|
* VirtualizedList nests inside a plain ScrollView (RN warning) or a nested
|
|
238
241
|
* vertical scroll-in-scroll breaks windowing. Screens with only a HORIZONTAL
|
|
@@ -241,6 +244,7 @@ const isFileManagementImageOnlyPicker = (props: Record<string, unknown>): boolea
|
|
|
241
244
|
*/
|
|
242
245
|
const OWN_SCROLL_CONTAINER_ROUTES: ReadonlySet<RouteName> = new Set<RouteName>([
|
|
243
246
|
'FileManagement',
|
|
247
|
+
'FileTrash',
|
|
244
248
|
'ConnectedApps',
|
|
245
249
|
'TrustLeaderboard',
|
|
246
250
|
'FollowersList',
|
|
@@ -120,7 +120,12 @@ const ACCOUNT_CATEGORY_OPTIONS: readonly AccountCategoryId[] = SELECTABLE_ACCOUN
|
|
|
120
120
|
* account via the `parentAccountId` prop. NOT the cryptographic Commons/DID
|
|
121
121
|
* "identity" — that is a separate concept.
|
|
122
122
|
*/
|
|
123
|
-
const CreateAccountScreen: React.FC<BaseScreenProps> = ({
|
|
123
|
+
const CreateAccountScreen: React.FC<BaseScreenProps> = ({
|
|
124
|
+
onClose,
|
|
125
|
+
goBack,
|
|
126
|
+
parentAccountId,
|
|
127
|
+
navigate,
|
|
128
|
+
}) => {
|
|
124
129
|
const bloomTheme = useTheme();
|
|
125
130
|
const { oxyServices, createAccount, switchToAccount, user, canUsePrivateApi } = useOxy();
|
|
126
131
|
const { t, locale } = useI18n();
|
|
@@ -399,6 +404,15 @@ const CreateAccountScreen: React.FC<BaseScreenProps> = ({ onClose, goBack, paren
|
|
|
399
404
|
|
|
400
405
|
return (
|
|
401
406
|
<View className="gap-space-16 px-screen-margin pt-space-16 pb-space-32">
|
|
407
|
+
{user?.kind === 'personal' && !parentId ? (
|
|
408
|
+
<SettingsListGroup title={t('supervision.title')}>
|
|
409
|
+
<SettingsListItem
|
|
410
|
+
title={t('supervision.setupOwn')}
|
|
411
|
+
description={t('supervision.setupOwnDescription')}
|
|
412
|
+
onPress={() => navigate?.('Supervision')}
|
|
413
|
+
/>
|
|
414
|
+
</SettingsListGroup>
|
|
415
|
+
) : null}
|
|
402
416
|
{/* Account type — canonical grouped selection rows (checkmark on the chosen one) */}
|
|
403
417
|
<SettingsListGroup title={t('accounts.create.typeSection') || 'Account type'}>
|
|
404
418
|
{KIND_OPTIONS.map((option) => {
|
|
@@ -42,6 +42,12 @@ import { presentFileDetails } from '../components/fileManagement/FileDetailsModa
|
|
|
42
42
|
import { UploadPreview } from '../components/fileManagement/UploadPreview';
|
|
43
43
|
import { presentActionSheet } from '../components/surfaces/ActionSheetSurface';
|
|
44
44
|
import { getErrorMessage } from './fileManagement/shared';
|
|
45
|
+
import {
|
|
46
|
+
deletePermanently,
|
|
47
|
+
errorStatus,
|
|
48
|
+
libraryRemovalFor,
|
|
49
|
+
removeFromLibrary,
|
|
50
|
+
} from './fileManagement/trashActions';
|
|
45
51
|
import { useFileUploadState } from './fileManagement/hooks/useFileUploadState';
|
|
46
52
|
import PhotoPickerView from './fileManagement/PhotoPickerSection';
|
|
47
53
|
import FileListSection, { type FileListItem } from './fileManagement/FileListSection';
|
|
@@ -124,6 +130,10 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
124
130
|
const invalidateFiles = useCallback(() => {
|
|
125
131
|
queryClient.invalidateQueries({ queryKey: queryKeys.files.list(targetUserId) });
|
|
126
132
|
}, [queryClient, targetUserId]);
|
|
133
|
+
// A removal changes the library AND the trash (`files.all` holds both).
|
|
134
|
+
const invalidateAllFiles = useCallback(() => {
|
|
135
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.files.all });
|
|
136
|
+
}, [queryClient]);
|
|
127
137
|
|
|
128
138
|
const { width: windowWidth } = useWindowDimensions();
|
|
129
139
|
// Prefer an explicit sheet width from the router; fall back to the window
|
|
@@ -483,90 +493,142 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
483
493
|
t,
|
|
484
494
|
});
|
|
485
495
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
496
|
+
// Asks before deleting for good a file an application still uses (the API
|
|
497
|
+
// refuses it until the person says so — ADR 0043 D5).
|
|
498
|
+
const confirmStillUsed = useCallback(
|
|
499
|
+
(filename: string) => (uses: number) =>
|
|
500
|
+
surfaces.confirm({
|
|
489
501
|
title: t('fileManagement.deleteFile') || 'Delete File',
|
|
490
|
-
description: t('fileManagement.confirms.
|
|
491
|
-
confirmLabel: t('fileManagement.
|
|
502
|
+
description: t('fileManagement.confirms.deleteForeverInUse', { filename, count: uses }),
|
|
503
|
+
confirmLabel: t('fileManagement.trashView.deleteForever'),
|
|
492
504
|
cancelLabel: t('common.cancel') || 'Cancel',
|
|
493
505
|
destructive: true,
|
|
494
|
-
})
|
|
506
|
+
}),
|
|
507
|
+
[t],
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Remove one file from the library. It goes to TRASH — no longer served,
|
|
512
|
+
* restorable from Trash (ADR 0043 D5). A `blocked` file cannot be trashed
|
|
513
|
+
* (it is served to nobody already), so removing it is the permanent delete.
|
|
514
|
+
*/
|
|
515
|
+
const confirmFileDelete = useCallback(
|
|
516
|
+
async (fileId: string, filename: string) => {
|
|
517
|
+
const file = files.find((candidate) => candidate.id === fileId);
|
|
518
|
+
const removal = libraryRemovalFor({ status: file?.status });
|
|
519
|
+
const confirmed = await surfaces.confirm(
|
|
520
|
+
removal === 'trash'
|
|
521
|
+
? {
|
|
522
|
+
title: t('fileManagement.moveToTrash'),
|
|
523
|
+
description: t('fileManagement.confirms.moveToTrash', { filename }),
|
|
524
|
+
confirmLabel: t('fileManagement.moveToTrash'),
|
|
525
|
+
cancelLabel: t('common.cancel') || 'Cancel',
|
|
526
|
+
}
|
|
527
|
+
: {
|
|
528
|
+
title: t('fileManagement.deleteFile') || 'Delete File',
|
|
529
|
+
description: t('fileManagement.confirms.deleteFile', { filename }),
|
|
530
|
+
confirmLabel: t('fileManagement.confirm') || 'Delete',
|
|
531
|
+
cancelLabel: t('common.cancel') || 'Cancel',
|
|
532
|
+
destructive: true,
|
|
533
|
+
},
|
|
534
|
+
);
|
|
495
535
|
if (!confirmed) return;
|
|
496
536
|
|
|
497
537
|
try {
|
|
498
538
|
setDeletingId(fileId);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
539
|
+
if (removal === 'trash') {
|
|
540
|
+
await oxyServices.assets.trash(fileId);
|
|
541
|
+
toast.success(t('fileManagement.toasts.movedToTrash'));
|
|
542
|
+
} else {
|
|
543
|
+
const outcome = await deletePermanently(
|
|
544
|
+
oxyServices.assets,
|
|
545
|
+
fileId,
|
|
546
|
+
confirmStillUsed(filename),
|
|
547
|
+
);
|
|
548
|
+
if (outcome === 'cancelled') return;
|
|
549
|
+
if (outcome === 'deleted') toast.success(t('fileManagement.toasts.deleteSuccess'));
|
|
550
|
+
}
|
|
502
551
|
|
|
503
|
-
// Remove from the
|
|
552
|
+
// Remove from the library cache, then reconcile both lists (the file
|
|
553
|
+
// is now in the trash) against the server.
|
|
504
554
|
removeFileFromCache(queryClient, targetUserId, fileId);
|
|
505
|
-
|
|
555
|
+
invalidateAllFiles();
|
|
506
556
|
} catch (error: unknown) {
|
|
507
|
-
|
|
508
|
-
if (
|
|
509
|
-
getErrorMessage(error)?.includes('File not found') ||
|
|
510
|
-
getErrorMessage(error)?.includes('404')
|
|
511
|
-
) {
|
|
557
|
+
const status = errorStatus(error);
|
|
558
|
+
if (status === 404) {
|
|
512
559
|
toast.error(t('fileManagement.toasts.fileNotFound'));
|
|
513
560
|
// It's already gone server-side — reconcile the list.
|
|
514
|
-
|
|
515
|
-
} else if (
|
|
516
|
-
getErrorMessage(error)?.includes('permission') ||
|
|
517
|
-
getErrorMessage(error)?.includes('403')
|
|
518
|
-
) {
|
|
561
|
+
invalidateAllFiles();
|
|
562
|
+
} else if (status === 403) {
|
|
519
563
|
toast.error(t('fileManagement.toasts.noPermission'));
|
|
520
564
|
} else {
|
|
521
|
-
toast.error(
|
|
565
|
+
toast.error(
|
|
566
|
+
getErrorMessage(error) ||
|
|
567
|
+
t(
|
|
568
|
+
removal === 'trash'
|
|
569
|
+
? 'fileManagement.toasts.moveToTrashFailed'
|
|
570
|
+
: 'fileManagement.toasts.deleteFailed',
|
|
571
|
+
),
|
|
572
|
+
);
|
|
522
573
|
}
|
|
523
574
|
} finally {
|
|
524
575
|
setDeletingId(null);
|
|
525
576
|
}
|
|
526
577
|
},
|
|
527
|
-
[oxyServices, queryClient, targetUserId,
|
|
578
|
+
[files, oxyServices, queryClient, targetUserId, invalidateAllFiles, confirmStillUsed, t],
|
|
528
579
|
);
|
|
529
580
|
|
|
530
581
|
const confirmBulkDelete = useCallback(async () => {
|
|
531
582
|
if (selectedIds.size === 0) return;
|
|
532
583
|
|
|
533
584
|
const confirmed = await surfaces.confirm({
|
|
534
|
-
title: t('fileManagement.
|
|
535
|
-
description: t('fileManagement.confirms.
|
|
536
|
-
confirmLabel: t('fileManagement.
|
|
585
|
+
title: t('fileManagement.moveToTrash'),
|
|
586
|
+
description: t('fileManagement.confirms.moveFilesToTrash', { count: selectedIds.size }),
|
|
587
|
+
confirmLabel: t('fileManagement.moveToTrash'),
|
|
537
588
|
cancelLabel: t('common.cancel') || 'Cancel',
|
|
538
|
-
destructive: true,
|
|
539
589
|
});
|
|
540
590
|
if (!confirmed) return;
|
|
541
591
|
|
|
542
592
|
try {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const results = await Promise.allSettled(deletePromises);
|
|
554
|
-
const successful = results.filter((r) => r.status === 'fulfilled' && r.value.success).length;
|
|
555
|
-
const failed = results.length - successful;
|
|
593
|
+
const selected = Array.from(selectedIds).map(
|
|
594
|
+
(id) => files.find((file) => file.id === id) ?? { id, filename: '', status: undefined },
|
|
595
|
+
);
|
|
596
|
+
const result = await removeFromLibrary(oxyServices.assets, selected, (uses, filename) =>
|
|
597
|
+
confirmStillUsed(filename)(uses),
|
|
598
|
+
);
|
|
599
|
+
for (const fileId of [...result.trashed, ...result.deleted]) {
|
|
600
|
+
removeFileFromCache(queryClient, targetUserId, fileId);
|
|
601
|
+
}
|
|
556
602
|
|
|
557
|
-
if (
|
|
558
|
-
toast.success(
|
|
603
|
+
if (result.trashed.length > 0) {
|
|
604
|
+
toast.success(
|
|
605
|
+
t('fileManagement.toasts.bulkMovedToTrash', { count: result.trashed.length }),
|
|
606
|
+
);
|
|
559
607
|
}
|
|
560
|
-
if (
|
|
561
|
-
toast.
|
|
608
|
+
if (result.deleted.length > 0) {
|
|
609
|
+
toast.success(
|
|
610
|
+
t('fileManagement.toasts.bulkDeleteSuccess', { count: result.deleted.length }),
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (result.failed.length > 0) {
|
|
614
|
+
toast.error(t('fileManagement.toasts.bulkDeleteFailed', { count: result.failed.length }));
|
|
562
615
|
}
|
|
563
616
|
|
|
564
617
|
setSelectedIds(new Set());
|
|
565
|
-
|
|
618
|
+
invalidateAllFiles();
|
|
566
619
|
} catch (error: unknown) {
|
|
567
620
|
toast.error(getErrorMessage(error) || t('fileManagement.toasts.bulkDeleteError'));
|
|
568
621
|
}
|
|
569
|
-
}, [
|
|
622
|
+
}, [
|
|
623
|
+
selectedIds,
|
|
624
|
+
files,
|
|
625
|
+
oxyServices,
|
|
626
|
+
queryClient,
|
|
627
|
+
targetUserId,
|
|
628
|
+
invalidateAllFiles,
|
|
629
|
+
confirmStillUsed,
|
|
630
|
+
t,
|
|
631
|
+
]);
|
|
570
632
|
|
|
571
633
|
const handleBulkVisibilityChange = useCallback(
|
|
572
634
|
async (visibility: 'private' | 'public' | 'unlisted') => {
|
|
@@ -912,7 +974,16 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
912
974
|
<Ionicons name={getFileIcon(file.contentType)} size={20} color={colors.primary} />
|
|
913
975
|
) : undefined),
|
|
914
976
|
title: file.filename,
|
|
915
|
-
|
|
977
|
+
// "Not in use": no application references it. Informational only —
|
|
978
|
+
// the file is never removed for that (ADR 0043 D5). A file taken down
|
|
979
|
+
// after a content claim says so: it is served to nobody (D10).
|
|
980
|
+
description: `${formatFileSize(file.length)} • ${new Date(file.uploadDate).toLocaleDateString()}${
|
|
981
|
+
file.status === 'blocked'
|
|
982
|
+
? ` • ${t('fileManagement.removedAfterClaim')}`
|
|
983
|
+
: file.inUse === false
|
|
984
|
+
? ` • ${t('fileManagement.notInUse')}`
|
|
985
|
+
: ''
|
|
986
|
+
}`,
|
|
916
987
|
onPress: () => {
|
|
917
988
|
// Support selection in regular mode with long press or if already selecting
|
|
918
989
|
if (!selectMode && selectedIds.size > 0) {
|
|
@@ -955,7 +1026,12 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
955
1026
|
iconOnly
|
|
956
1027
|
stopPropagation
|
|
957
1028
|
loading={deletingId === file.id}
|
|
958
|
-
accessibilityLabel={
|
|
1029
|
+
accessibilityLabel={t(
|
|
1030
|
+
file.status === 'blocked'
|
|
1031
|
+
? 'fileManagement.a11y.deleteFile'
|
|
1032
|
+
: 'fileManagement.a11y.moveToTrash',
|
|
1033
|
+
{ name: file.filename },
|
|
1034
|
+
)}
|
|
959
1035
|
onPress={() => confirmFileDelete(file.id, file.filename)}
|
|
960
1036
|
icon={<Ionicons name="trash" size={18} color={colors.error} />}
|
|
961
1037
|
/>
|
|
@@ -974,6 +1050,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
974
1050
|
thumbSourceFor,
|
|
975
1051
|
selectMode,
|
|
976
1052
|
selectedIds,
|
|
1053
|
+
t,
|
|
977
1054
|
]);
|
|
978
1055
|
|
|
979
1056
|
// Browse-themed terminal load-failure state — DISTINCT from the empty state.
|
|
@@ -1122,7 +1199,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1122
1199
|
},
|
|
1123
1200
|
{
|
|
1124
1201
|
key: 'delete',
|
|
1125
|
-
label: t('fileManagement.
|
|
1202
|
+
label: t('fileManagement.moveToTrashCount', { count: selectedIds.size }),
|
|
1126
1203
|
onPress: confirmBulkDelete,
|
|
1127
1204
|
disabled: false,
|
|
1128
1205
|
},
|
|
@@ -1387,6 +1464,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1387
1464
|
onClose={handleCloseFile}
|
|
1388
1465
|
onDownload={handleFileDownload}
|
|
1389
1466
|
onDelete={confirmFileDelete}
|
|
1467
|
+
deleteLabel={
|
|
1468
|
+
openedFile.status === 'blocked'
|
|
1469
|
+
? t('fileManagement.deleteFile')
|
|
1470
|
+
: t('fileManagement.moveToTrash')
|
|
1471
|
+
}
|
|
1390
1472
|
isOwner={user?.id === targetUserId}
|
|
1391
1473
|
/>
|
|
1392
1474
|
);
|
|
@@ -1494,6 +1576,17 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
1494
1576
|
</View>
|
|
1495
1577
|
}
|
|
1496
1578
|
/>
|
|
1579
|
+
{user?.id === targetUserId && !selectMode && navigate && (
|
|
1580
|
+
<Button
|
|
1581
|
+
appearance="subtle"
|
|
1582
|
+
tone="neutral"
|
|
1583
|
+
size="sm"
|
|
1584
|
+
iconOnly
|
|
1585
|
+
onPress={() => navigate('FileTrash', userId ? { userId } : undefined)}
|
|
1586
|
+
accessibilityLabel={t('fileManagement.a11y.openTrash')}
|
|
1587
|
+
icon={<Ionicons name="trash-outline" size={20} color={colors.text} />}
|
|
1588
|
+
/>
|
|
1589
|
+
)}
|
|
1497
1590
|
{user?.id === targetUserId && (!selectMode || allowUploadInSelectMode) && (
|
|
1498
1591
|
<Button
|
|
1499
1592
|
iconOnly
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { View, type ScrollView } from 'react-native';
|
|
4
|
+
import { Button } from '@oxy.so/bloom/button';
|
|
5
|
+
import { Text } from '@oxy.so/bloom/typography';
|
|
6
|
+
import { toast } from '@oxy.so/bloom/toast';
|
|
7
|
+
import { surfaces } from '@oxy.so/bloom/surfaces';
|
|
8
|
+
import { useTheme } from '@oxy.so/bloom/theme';
|
|
9
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
10
|
+
import type { BaseScreenProps } from '../types/navigation';
|
|
11
|
+
import Ionicons from '../icons/Ionicons';
|
|
12
|
+
import { useOxy } from '../context/OxyContext';
|
|
13
|
+
import { useI18n } from '../hooks/useI18n';
|
|
14
|
+
import { useSurfaceHeader } from '../hooks/useSurfaceHeader';
|
|
15
|
+
import { queryKeys } from '../hooks/queries/queryKeys';
|
|
16
|
+
import { removeFileFromCache, useUserFilesInfinite } from '../hooks/queries/useFileQueries';
|
|
17
|
+
import { formatFileSize, getFileIcon } from '../utils/fileManagement';
|
|
18
|
+
import FileListSection, { type FileListItem } from './fileManagement/FileListSection';
|
|
19
|
+
import FileLibraryError from './fileManagement/FileLibraryError';
|
|
20
|
+
import { getErrorMessage } from './fileManagement/shared';
|
|
21
|
+
import { deletePermanently, restoreFromTrash } from './fileManagement/trashActions';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The person's Trash (ADR 0043 D5): the files they moved to trash, most
|
|
25
|
+
* recently trashed first. Each can be restored — served again, back in the
|
|
26
|
+
* library — or deleted permanently, after a confirmation (and a second one when
|
|
27
|
+
* an app still uses it). An account's trash never expires, so nothing here
|
|
28
|
+
* disappears on its own; it still counts toward the account's storage.
|
|
29
|
+
*/
|
|
30
|
+
const FileTrashScreen: React.FC<BaseScreenProps & { userId?: string }> = ({ userId }) => {
|
|
31
|
+
const { user, oxyServices } = useOxy();
|
|
32
|
+
const { t } = useI18n();
|
|
33
|
+
const { colors } = useTheme();
|
|
34
|
+
const queryClient = useQueryClient();
|
|
35
|
+
const ownerId = userId || user?.id;
|
|
36
|
+
|
|
37
|
+
const trashQuery = useUserFilesInfinite(ownerId, 'trash');
|
|
38
|
+
const files = useMemo(
|
|
39
|
+
() => trashQuery.data?.pages.flatMap((page) => page.files) ?? [],
|
|
40
|
+
[trashQuery.data],
|
|
41
|
+
);
|
|
42
|
+
const scrollViewRef = useRef<ScrollView | null>(null);
|
|
43
|
+
// The file an action is in flight for: its buttons show progress.
|
|
44
|
+
const [busy, setBusy] = useState<{ id: string; restoring: boolean } | null>(null);
|
|
45
|
+
|
|
46
|
+
// Both lists change: a restored file returns to the library, a deleted one
|
|
47
|
+
// leaves the trash. `files.all` covers both keys.
|
|
48
|
+
const refreshFiles = useCallback(() => {
|
|
49
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.files.all });
|
|
50
|
+
}, [queryClient]);
|
|
51
|
+
|
|
52
|
+
const handleRestore = useCallback(
|
|
53
|
+
async (fileId: string) => {
|
|
54
|
+
setBusy({ id: fileId, restoring: true });
|
|
55
|
+
try {
|
|
56
|
+
const outcome = await restoreFromTrash(oxyServices.assets, fileId);
|
|
57
|
+
if (outcome === 'restored') {
|
|
58
|
+
toast.success(t('fileManagement.toasts.restored'));
|
|
59
|
+
} else {
|
|
60
|
+
toast.error(t('fileManagement.toasts.notInTrash'));
|
|
61
|
+
}
|
|
62
|
+
removeFileFromCache(queryClient, ownerId, fileId, 'trash');
|
|
63
|
+
refreshFiles();
|
|
64
|
+
} catch (error: unknown) {
|
|
65
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.restoreFailed'));
|
|
66
|
+
} finally {
|
|
67
|
+
setBusy(null);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
[oxyServices, queryClient, ownerId, refreshFiles, t],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const handleDeleteForever = useCallback(
|
|
74
|
+
async (fileId: string, filename: string) => {
|
|
75
|
+
const confirmed = await surfaces.confirm({
|
|
76
|
+
title: t('fileManagement.trashView.deleteForever'),
|
|
77
|
+
description: t('fileManagement.confirms.deleteForever', { filename }),
|
|
78
|
+
confirmLabel: t('fileManagement.trashView.deleteForever'),
|
|
79
|
+
cancelLabel: t('common.cancel') || 'Cancel',
|
|
80
|
+
destructive: true,
|
|
81
|
+
});
|
|
82
|
+
if (!confirmed) return;
|
|
83
|
+
|
|
84
|
+
setBusy({ id: fileId, restoring: false });
|
|
85
|
+
try {
|
|
86
|
+
const outcome = await deletePermanently(oxyServices.assets, fileId, (uses) =>
|
|
87
|
+
surfaces.confirm({
|
|
88
|
+
title: t('fileManagement.trashView.deleteForever'),
|
|
89
|
+
description: t('fileManagement.confirms.deleteForeverInUse', {
|
|
90
|
+
filename,
|
|
91
|
+
count: uses,
|
|
92
|
+
}),
|
|
93
|
+
confirmLabel: t('fileManagement.trashView.deleteForever'),
|
|
94
|
+
cancelLabel: t('common.cancel') || 'Cancel',
|
|
95
|
+
destructive: true,
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
if (outcome === 'cancelled') return;
|
|
99
|
+
if (outcome === 'deleted') toast.success(t('fileManagement.toasts.deletedForever'));
|
|
100
|
+
removeFileFromCache(queryClient, ownerId, fileId, 'trash');
|
|
101
|
+
refreshFiles();
|
|
102
|
+
} catch (error: unknown) {
|
|
103
|
+
toast.error(getErrorMessage(error) || t('fileManagement.toasts.deleteFailed'));
|
|
104
|
+
} finally {
|
|
105
|
+
setBusy(null);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
[oxyServices, queryClient, ownerId, refreshFiles, t],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const items = useMemo<FileListItem[]>(
|
|
112
|
+
() =>
|
|
113
|
+
files.map((file) => {
|
|
114
|
+
const trashedOn = file.trashedAt ? new Date(file.trashedAt).toLocaleDateString() : null;
|
|
115
|
+
return {
|
|
116
|
+
id: file.id,
|
|
117
|
+
icon: <Ionicons name={getFileIcon(file.contentType)} size={20} color={colors.primary} />,
|
|
118
|
+
title: file.filename,
|
|
119
|
+
description: trashedOn
|
|
120
|
+
? `${formatFileSize(file.length)} • ${t('fileManagement.trashView.trashedOn', { date: trashedOn })}`
|
|
121
|
+
: formatFileSize(file.length),
|
|
122
|
+
// A trashed file is not served, so there is nothing to open.
|
|
123
|
+
onPress: () => {},
|
|
124
|
+
rightElement: (
|
|
125
|
+
<View className="flex-row items-center gap-1.5 ml-3">
|
|
126
|
+
<Button
|
|
127
|
+
appearance="subtle"
|
|
128
|
+
tone="neutral"
|
|
129
|
+
size="sm"
|
|
130
|
+
iconOnly
|
|
131
|
+
stopPropagation
|
|
132
|
+
disabled={busy !== null && busy.id !== file.id}
|
|
133
|
+
loading={busy?.id === file.id && busy.restoring}
|
|
134
|
+
accessibilityLabel={t('fileManagement.trashView.a11y.restore', {
|
|
135
|
+
name: file.filename,
|
|
136
|
+
})}
|
|
137
|
+
onPress={() => handleRestore(file.id)}
|
|
138
|
+
icon={<Ionicons name="arrow-undo" size={18} color={colors.text} />}
|
|
139
|
+
/>
|
|
140
|
+
<Button
|
|
141
|
+
appearance="subtle"
|
|
142
|
+
tone="danger"
|
|
143
|
+
size="sm"
|
|
144
|
+
iconOnly
|
|
145
|
+
stopPropagation
|
|
146
|
+
disabled={busy !== null && busy.id !== file.id}
|
|
147
|
+
loading={busy?.id === file.id && !busy.restoring}
|
|
148
|
+
accessibilityLabel={t('fileManagement.trashView.a11y.deleteForever', {
|
|
149
|
+
name: file.filename,
|
|
150
|
+
})}
|
|
151
|
+
onPress={() => handleDeleteForever(file.id, file.filename)}
|
|
152
|
+
icon={<Ionicons name="trash" size={18} color={colors.error} />}
|
|
153
|
+
/>
|
|
154
|
+
</View>
|
|
155
|
+
),
|
|
156
|
+
};
|
|
157
|
+
}),
|
|
158
|
+
[files, colors, busy, handleRestore, handleDeleteForever, t],
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
useSurfaceHeader({
|
|
162
|
+
title: t('fileManagement.trashView.title'),
|
|
163
|
+
subtitle: t('fileManagement.trashView.subtitle'),
|
|
164
|
+
largeTitle: false,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const hasLoadError = trashQuery.isError && files.length === 0;
|
|
168
|
+
const renderEmptyState = () =>
|
|
169
|
+
hasLoadError ? (
|
|
170
|
+
<FileLibraryError
|
|
171
|
+
title={t('fileManagement.trashView.loadError')}
|
|
172
|
+
description={t('fileManagement.loadError.description')}
|
|
173
|
+
retryLabel={t('fileManagement.retry')}
|
|
174
|
+
onRetry={() => trashQuery.refetch()}
|
|
175
|
+
iconColor={colors.error}
|
|
176
|
+
titleColor={colors.text}
|
|
177
|
+
descriptionColor={colors.textSecondary}
|
|
178
|
+
/>
|
|
179
|
+
) : trashQuery.isLoading || !ownerId ? null : (
|
|
180
|
+
<View className="items-center py-[40px] px-[24px]">
|
|
181
|
+
<Ionicons name="trash-outline" size={64} color={colors.textTertiary} />
|
|
182
|
+
<Text
|
|
183
|
+
className="text-[24px] font-bold mt-[16px] mb-[8px] text-center"
|
|
184
|
+
style={{ color: colors.text }}
|
|
185
|
+
>
|
|
186
|
+
{t('fileManagement.trashView.emptyTitle')}
|
|
187
|
+
</Text>
|
|
188
|
+
<Text
|
|
189
|
+
className="text-[16px] text-center leading-[24px]"
|
|
190
|
+
style={{ color: colors.textSecondary }}
|
|
191
|
+
>
|
|
192
|
+
{t('fileManagement.trashView.emptyDescription')}
|
|
193
|
+
</Text>
|
|
194
|
+
</View>
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<View className="flex-1">
|
|
199
|
+
{files.length > 0 && (
|
|
200
|
+
<Text
|
|
201
|
+
className="text-[13px] leading-[18px] mx-[16px] mt-[4px] mb-[12px]"
|
|
202
|
+
style={{ color: colors.textSecondary }}
|
|
203
|
+
>
|
|
204
|
+
{t('fileManagement.trashView.notice')}
|
|
205
|
+
</Text>
|
|
206
|
+
)}
|
|
207
|
+
<FileListSection
|
|
208
|
+
scrollViewRef={scrollViewRef}
|
|
209
|
+
filteredFiles={files}
|
|
210
|
+
searchQuery=""
|
|
211
|
+
items={items}
|
|
212
|
+
paging={{
|
|
213
|
+
loadingMore: trashQuery.isFetchingNextPage,
|
|
214
|
+
hasMore: trashQuery.hasNextPage ?? false,
|
|
215
|
+
}}
|
|
216
|
+
refreshing={trashQuery.isRefetching}
|
|
217
|
+
colors={colors}
|
|
218
|
+
t={t}
|
|
219
|
+
onRefresh={() => trashQuery.refetch()}
|
|
220
|
+
onLoadMore={() => trashQuery.fetchNextPage()}
|
|
221
|
+
onClearSearch={() => {}}
|
|
222
|
+
renderEmptyState={renderEmptyState}
|
|
223
|
+
/>
|
|
224
|
+
</View>
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export default FileTrashScreen;
|