Skip to content

Commit 5d4220a

Browse files
committed
feat: create success upload ui in share extension, implement deep linking for drive folders and fix folder loading logic
1 parent d4df71b commit 5d4220a

19 files changed

Lines changed: 498 additions & 171 deletions

assets/lang/strings.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ const translations = {
333333
fileNameFallback: 'File',
334334
rename: 'Rename',
335335
itemsSelected: '{0} items selected',
336+
itemsUploaded: '{0} items uploaded',
336337
searchPlaceholder: 'Search in Drive',
337338
rootFolderName: 'Drive',
338339
noResults: 'No results found',
@@ -343,8 +344,11 @@ const translations = {
343344
folderNameEmpty: 'Folder name cannot be empty',
344345
folderCreateError: 'Failed to create folder. Please try again.',
345346
uploadSuccess: 'Files uploaded successfully',
347+
uploadedTitle: 'Uploaded!',
348+
uploadedSubtitle: 'Encrypted and safely stored in your selected folder',
349+
viewInFolder: 'View in folder',
346350
errorGeneral: 'Upload failed. Please try again.',
347-
errorNoInternet: 'No internet connection.',
351+
errorNoInternet: 'No internet connection. Please try again.',
348352
errorSessionExpired: 'Session expired. Please sign in again.',
349353
errorPrep: 'Could not prepare the file for upload.',
350354
errorFileTooLarge: 'The maximum upload size is 300 MB at a time.',
@@ -1166,6 +1170,7 @@ const translations = {
11661170
fileNameFallback: 'Archivo',
11671171
rename: 'Renombrar',
11681172
itemsSelected: '{0} elementos seleccionados',
1173+
itemsUploaded: '{0} elementos subidos',
11691174
searchPlaceholder: 'Buscar en Drive',
11701175
rootFolderName: 'Drive',
11711176
noResults: 'Sin resultados',
@@ -1176,8 +1181,11 @@ const translations = {
11761181
folderNameEmpty: 'El nombre no puede estar vacío',
11771182
folderCreateError: 'Error al crear la carpeta. Inténtalo de nuevo.',
11781183
uploadSuccess: 'Archivos subidos correctamente',
1184+
uploadedTitle: '¡Subido!',
1185+
uploadedSubtitle: 'Encriptado y guardado de forma segura en la carpeta seleccionada',
1186+
viewInFolder: 'Ver en carpeta',
11791187
errorGeneral: 'Error al subir. Inténtalo de nuevo.',
1180-
errorNoInternet: 'Sin conexión a internet.',
1188+
errorNoInternet: 'Sin conexión a internet. Inténtalo de nuevo.',
11811189
errorSessionExpired: 'Sesión expirada. Inicia sesión de nuevo.',
11821190
errorPrep: 'No se pudo preparar el archivo para la subida.',
11831191
errorFileTooLarge: 'El tamaño máximo de subida es de 300 MB a la vez.',

src/contexts/Drive/Drive.context.tsx

Lines changed: 104 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import React, { useEffect, useRef, useState } from 'react';
99
import appService from '@internxt-mobile/services/AppService';
1010
import errorService from '@internxt-mobile/services/ErrorService';
1111
import notificationsService from '@internxt-mobile/services/NotificationsService';
12-
import { AppStateStatus, NativeEventSubscription } from 'react-native';
1312
import { NotificationType } from '@internxt-mobile/types/index';
13+
import { AppStateStatus, NativeEventSubscription } from 'react-native';
1414

1515
import { driveFolderService } from '@internxt-mobile/services/drive/folder';
1616
import { mapFileWithIsFolder, mapFolderWithIsFolder } from 'src/helpers/driveItemMappers';
@@ -85,6 +85,8 @@ export const DriveContextProvider: React.FC<DriveContextProviderProps> = ({ chil
8585
const [currentFolder, setCurrentFolder] = useState<DriveFoldersTreeNode | null>(null);
8686
const currentFolderId = useRef<string | null>(null);
8787
const onAppStateChangeListener = useRef<NativeEventSubscription | null>(null);
88+
const folderAbortControllers = useRef<Map<string, AbortController>>(new Map());
89+
const driveFoldersTreeRef = useRef<DriveFoldersTree>({ [rootFolderId]: ROOT_FOLDER_NODE });
8890

8991
const handleAppStateChange = (state: AppStateStatus) => {
9092
if (state === 'active' && currentFolderId.current) {
@@ -110,6 +112,10 @@ export const DriveContextProvider: React.FC<DriveContextProviderProps> = ({ chil
110112
};
111113
}, []);
112114

115+
useEffect(() => {
116+
driveFoldersTreeRef.current = driveFoldersTree;
117+
}, [driveFoldersTree]);
118+
113119
useEffect(() => {
114120
asyncStorageService.getItem(AsyncStorageKey.PreferredDriveViewMode).then((preferredDriveViewMode) => {
115121
if (preferredDriveViewMode && preferredDriveViewMode !== viewMode) {
@@ -245,43 +251,71 @@ export const DriveContextProvider: React.FC<DriveContextProviderProps> = ({ chil
245251
};
246252
};
247253

248-
const loadFolderContent = async (folderId: string, options?: LoadFolderContentOptions) => {
249-
const shouldResetPagination = options?.resetPagination;
250-
const driveFolderTreeNode: DriveFoldersTreeNode = driveFoldersTree[folderId] ?? ROOT_FOLDER_NODE;
251-
if (!driveFolderTreeNode) throw new Error('Cannot load this folder');
252-
253-
if (options?.focusFolder && driveFolderTreeNode) {
254-
setCurrentFolder(driveFolderTreeNode);
255-
}
254+
const registerFolderLoad = (folderId: string): AbortController => {
255+
folderAbortControllers.current.get(folderId)?.abort();
256+
const controller = new AbortController();
257+
folderAbortControllers.current.set(folderId, controller);
258+
return controller;
259+
};
256260

257-
let files: DriveFileForTree[] = [];
258-
let folders: DriveFolderForTree[] = [];
261+
const updateFolderNode = (folderId: string, updates: Partial<DriveFoldersTreeNode>) => {
262+
setDriveFoldersTree((prevTree) => {
263+
const node = prevTree[folderId];
264+
if (!node) return prevTree;
265+
return { ...prevTree, [folderId]: { ...node, ...updates } };
266+
});
267+
};
259268

269+
const fetchFolderData = async (
270+
folderId: string,
271+
options: LoadFolderContentOptions | undefined,
272+
controller: AbortController,
273+
): Promise<{ files: DriveFileForTree[]; folders: DriveFolderForTree[] } | null> => {
260274
if (options?.loadAllContent) {
261275
const allContent = await fetchAllFolderContent(folderId);
262-
files = allContent.files;
263-
folders = allContent.folders;
264-
} else {
265-
const nextFilesPage = options?.resetPagination
266-
? 1
267-
: Math.ceil(driveFolderTreeNode.files.length / FILES_LIMIT_PER_PAGE) + 1;
268-
const nextFoldersPage = options?.resetPagination
269-
? 1
270-
: Math.ceil(driveFolderTreeNode.folders.length / FOLDERS_LIMIT_PER_PAGE) + 1;
271-
272-
const paginatedContent = await fetchFolderContent(folderId, nextFilesPage, nextFoldersPage);
273-
files = paginatedContent.files;
274-
folders = paginatedContent.folders;
276+
return controller.signal.aborted ? null : allContent;
275277
}
276278

277-
updateDriveFoldersTree({
278-
folderId,
279-
parentId: driveFolderTreeNode.parentId,
280-
newFiles: files,
281-
newFolders: folders,
282-
error: undefined,
283-
resetPagination: shouldResetPagination ?? false,
284-
});
279+
const currentDriveFoldersTree = driveFoldersTreeRef.current[folderId] ?? ROOT_FOLDER_NODE;
280+
const nextFilesPage = options?.resetPagination
281+
? 1
282+
: Math.ceil(currentDriveFoldersTree.files.length / FILES_LIMIT_PER_PAGE) + 1;
283+
const nextFoldersPage = options?.resetPagination
284+
? 1
285+
: Math.ceil(currentDriveFoldersTree.folders.length / FOLDERS_LIMIT_PER_PAGE) + 1;
286+
287+
const folderContentResult = await fetchFolderContent(folderId, nextFilesPage, nextFoldersPage);
288+
return controller.signal.aborted ? null : folderContentResult;
289+
};
290+
291+
const loadFolderContent = async (folderId: string, options?: LoadFolderContentOptions) => {
292+
const driveFolderNode = driveFoldersTreeRef.current[folderId] ?? ROOT_FOLDER_NODE;
293+
if (options?.focusFolder) setCurrentFolder(driveFolderNode);
294+
295+
const controller = registerFolderLoad(folderId);
296+
updateFolderNode(folderId, { loading: true, error: undefined });
297+
298+
try {
299+
const driveFolderNodeContent = await fetchFolderData(folderId, options, controller);
300+
if (!driveFolderNodeContent) return;
301+
302+
updateDriveFoldersTree({
303+
folderId,
304+
parentId: driveFolderNode.parentId,
305+
newFiles: driveFolderNodeContent.files,
306+
newFolders: driveFolderNodeContent.folders,
307+
error: undefined,
308+
resetPagination: options?.resetPagination ?? false,
309+
});
310+
} catch (error) {
311+
if (controller.signal.aborted) return;
312+
updateFolderNode(folderId, { loading: false, error: error as Error });
313+
throw error;
314+
} finally {
315+
if (folderAbortControllers.current.get(folderId) === controller) {
316+
folderAbortControllers.current.delete(folderId);
317+
}
318+
}
285319
};
286320

287321
const updateDriveFoldersTree = ({
@@ -299,47 +333,46 @@ export const DriveContextProvider: React.FC<DriveContextProviderProps> = ({ chil
299333
newFolders: DriveFolderForTree[];
300334
error?: Error;
301335
}) => {
302-
const driveFolderTreeNode = driveFoldersTree[folderId];
303-
304-
const allFiles = resetPagination ? newFiles : [...(driveFolderTreeNode?.files ?? []), ...newFiles];
305-
306-
const allFolders = resetPagination ? newFolders : [...(driveFolderTreeNode?.folders ?? []), ...newFolders];
307-
308-
const newTreeNodes = {
309-
[folderId]: {
310-
name: driveFolderTreeNode?.name || 'Drive',
311-
parentId: parentId,
312-
uuid: folderId,
313-
files: allFiles,
314-
folders: allFolders,
315-
loading: false,
316-
error,
317-
} as DriveFoldersTreeNode,
318-
};
336+
setDriveFoldersTree((prevDriveFoldersTree) => {
337+
const driveFolderTreeNode = prevDriveFoldersTree[folderId];
319338

320-
allFolders.forEach((folder) => {
321-
const existingNode = driveFoldersTree[folder.uuid];
322-
if (!existingNode) {
323-
newTreeNodes[folder.uuid] = {
324-
uuid: folder.uuid,
325-
name: folder.plainName ?? '',
326-
parentId: folder.parentUuid,
327-
id: folder.id,
328-
updatedAt: folder.updatedAt,
329-
createdAt: folder.createdAt,
330-
loading: true,
331-
files: [],
332-
folders: [],
333-
// @ts-expect-error - leave old implementation in order to not break anything
334-
currentFoldersPage: 2,
335-
error: undefined,
336-
};
337-
}
338-
});
339+
const allFiles = resetPagination ? newFiles : [...(driveFolderTreeNode?.files ?? []), ...newFiles];
340+
341+
const allFolders = resetPagination ? newFolders : [...(driveFolderTreeNode?.folders ?? []), ...newFolders];
342+
343+
const newTreeNodes = {
344+
[folderId]: {
345+
name: driveFolderTreeNode?.name || 'Drive',
346+
parentId: parentId,
347+
uuid: folderId,
348+
files: allFiles,
349+
folders: allFolders,
350+
loading: false,
351+
error,
352+
} as DriveFoldersTreeNode,
353+
};
354+
355+
allFolders.forEach((folder) => {
356+
const existingNode = prevDriveFoldersTree[folder.uuid];
357+
if (!existingNode) {
358+
newTreeNodes[folder.uuid] = {
359+
uuid: folder.uuid,
360+
name: folder.plainName ?? '',
361+
parentId: folder.parentUuid,
362+
id: folder.id,
363+
updatedAt: folder.updatedAt,
364+
createdAt: folder.createdAt,
365+
loading: true,
366+
files: [],
367+
folders: [],
368+
// @ts-expect-error - leave old implementation in order to not break anything
369+
currentFoldersPage: 2,
370+
error: undefined,
371+
};
372+
}
373+
});
339374

340-
setDriveFoldersTree({
341-
...driveFoldersTree,
342-
...newTreeNodes,
375+
return { ...prevDriveFoldersTree, ...newTreeNodes };
343376
});
344377
};
345378

src/navigation/AppLinks.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const SCHEME = 'internxt://';
2+
3+
export const AppLinks = {
4+
driveFolder: (folderUuid: string): string => `${SCHEME}tab-explorer/drive/folder/${folderUuid}`,
5+
signIn: (): string => `${SCHEME}sign-in`,
6+
} as const;
7+
8+
export const AppPaths = {
9+
driveFolder: (folderUuid: string): string => `tab-explorer/drive/folder/${folderUuid}`,
10+
signIn: (): string => 'sign-in',
11+
} as const;

src/navigation/DriveNavigator.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const DriveNavigator = () => {
1313
name="DriveFolder"
1414
component={DriveFolderScreen}
1515
initialParams={{
16-
isRootFolder: true,
1716
folderUuid: user?.rootFolderId,
1817
}}
1918
options={{ animation: 'default' }}

src/navigation/LinkingConfiguration.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ const linking: LinkingOptions<RootStackParamList> = {
1313
screens: {
1414
Debug: 'debug',
1515
SignIn: 'sign-in',
16-
TabExplorer: 'tab-explorer',
16+
TabExplorer: {
17+
path: 'tab-explorer',
18+
screens: {
19+
Drive: {
20+
path: 'drive',
21+
screens: {
22+
DriveFolder: 'folder/:folderUuid',
23+
},
24+
},
25+
},
26+
},
1727
WebLogin: 'login-success',
1828
},
1929
},

src/screens/drive/DriveFolderScreen/DriveFolderScreen.helpers.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
1+
import { FolderAncestor } from '../../../services/drive/folder/driveFolder.service';
12
import { DriveItemData, DriveItemStatus, DriveListItem } from '../../../types/drive';
23
import { FolderUploadState } from '../../../types/drive/folderUpload';
4+
import { DriveStackParamList } from '../../../types/navigation';
5+
6+
export type DriveFolderRoute = {
7+
name: 'DriveFolder';
8+
params: DriveStackParamList['DriveFolder'];
9+
};
10+
11+
export const buildDeepLinkRoutes = (folderUuid: string, ancestors: FolderAncestor[]): DriveFolderRoute[] | null => {
12+
if (ancestors.length === 0) return null;
13+
const [targetFolder, ...ancestorFolders] = ancestors;
14+
15+
if (targetFolder.uuid !== folderUuid) return null;
16+
17+
const nameByUuid = new Map(ancestors.map((ancestor) => [ancestor.uuid, ancestor.plainName ?? undefined]));
18+
const ancestorFoldersFromRoot = [...ancestorFolders].reverse();
19+
20+
const routes: DriveFolderRoute[] = ancestorFoldersFromRoot.map((ancestor) => ({
21+
name: 'DriveFolder',
22+
params: {
23+
folderUuid: ancestor.uuid,
24+
folderName: ancestor.plainName,
25+
parentUuid: ancestor.parentUuid ?? undefined,
26+
parentFolderName: ancestor.parentUuid ? nameByUuid.get(ancestor.parentUuid) : undefined,
27+
},
28+
}));
29+
30+
routes.push({
31+
name: 'DriveFolder',
32+
params: {
33+
folderUuid,
34+
folderName: targetFolder.plainName,
35+
parentUuid: targetFolder.parentUuid ?? undefined,
36+
parentFolderName: targetFolder.parentUuid ? nameByUuid.get(targetFolder.parentUuid) : undefined,
37+
},
38+
});
39+
40+
return routes;
41+
};
342

443
export const buildFolderUploadListItem = (state: FolderUploadState): DriveListItem => ({
544
id: `folder-upload-${state.uploadId}`,

0 commit comments

Comments
 (0)