diff --git a/package.json b/package.json index 899859b6b..3258fd8cb 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@internxt/lib": "^1.2.0", "@internxt/mobile-sdk": "^0.2.41", "@internxt/rn-crypto": "0.1.15", - "@internxt/sdk": "1.11.0", + "@internxt/sdk": "=1.11.6", "@react-native-async-storage/async-storage": "1.21.0", "@react-navigation/bottom-tabs": "^6.2.0", "@react-navigation/native": "^6.1.18", diff --git a/src/components/modals/AddModal/index.tsx b/src/components/modals/AddModal/index.tsx index 1cb55f6d1..b152703b2 100644 --- a/src/components/modals/AddModal/index.tsx +++ b/src/components/modals/AddModal/index.tsx @@ -16,6 +16,11 @@ import { useDrive } from '@internxt-mobile/hooks/drive'; import { imageService, logger } from '@internxt-mobile/services/common'; import { uploadService } from '@internxt-mobile/services/common/network/upload/upload.service'; import drive from '@internxt-mobile/services/drive'; +import { + generateFileName, + isTemporaryFileName, + parseExifDate, +} from '@internxt-mobile/services/drive/file/utils/exifHelpers'; import errorService from '@internxt-mobile/services/ErrorService'; import { DriveFileData, EncryptionVersion, FileEntryByUuid, Thumbnail } from '@internxt/sdk/dist/drive/storage/types'; import { SaveFormat } from 'expo-image-manipulator'; @@ -46,7 +51,7 @@ import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { driveActions, driveThunks } from '../../../store/slices/drive'; import { uiActions } from '../../../store/slices/ui'; import { NotificationType, ProgressCallback } from '../../../types'; -import { DriveEventKey, UPLOAD_FILE_SIZE_LIMIT, UploadingFile } from '../../../types/drive'; +import { DocumentPickerFile, DriveEventKey, UPLOAD_FILE_SIZE_LIMIT, UploadingFile } from '../../../types/drive'; import AppText from '../../AppText'; import BottomModal from '../BottomModal'; import CreateFolderModal from '../CreateFolderModal'; @@ -127,6 +132,8 @@ function AddModal(): JSX.Element { fileExtension, fileToUpload.parentUuid, progressCallback, + fileToUpload.modificationTime, + fileToUpload.creationTime, ); dispatch(driveActions.uploadingFileEnd(fileToUpload.id)); @@ -157,6 +164,8 @@ function AddModal(): JSX.Element { fileExtension: string, currentFolderId: string, progressCallback: ProgressCallback, + modificationTime?: string, + creationTime?: string, ) { const { bucket, bridgeUser, mnemonic, userId } = await asyncStorage.getUser(); logger.info('Stating file...'); @@ -184,22 +193,29 @@ function AddModal(): JSX.Element { notifyProgress: progressCallback, }, ); - logger.info('File uploaded with fileId: ', fileId); logger.info('File uploaded with name: ', fileName); + logger.info('File uploaded with modificationTime: ', modificationTime); + logger.info('File uploaded with creationTime: ', creationTime); const folderId = currentFolderId; const plainName = fileName; + const modTimestamp = modificationTime ?? fileStat.mtime; + const modificationTimeISO = modTimestamp ? new Date(modTimestamp).toISOString() : undefined; + + const createTimestamp = creationTime ?? fileStat.ctime; + const creationTimeISO = createTimestamp ? new Date(createTimestamp).toISOString() : undefined; const fileEntryByUuid: FileEntryByUuid = { - id: fileId, + fileId: fileId, type: fileExtension, size: fileSize, - name: plainName, - plain_name: plainName, + plainName: plainName, bucket, - folder_id: folderId, - encrypt_version: EncryptionVersion.Aes03, + folderUuid: folderId, + encryptVersion: EncryptionVersion.Aes03, + modificationTime: modificationTimeISO, + creationTime: creationTimeISO, }; let uploadedThumbnail: Thumbnail | null = null; @@ -319,14 +335,14 @@ function AddModal(): JSX.Element { dispatch(driveActions.setUri(undefined)); } - function processFilesFromPicker(documents: DocumentPickerResponse[]): Promise { + function processFilesFromPicker(documents: DocumentPickerFile[]): Promise { documents.forEach((doc) => (doc.uri = doc.fileCopyUri)); dispatch(uiActions.setShowUploadFileModal(false)); return uploadDocuments(documents); } - async function uploadDocuments(documents: DocumentPickerResponse[]) { + async function uploadDocuments(documents: DocumentPickerFile[]) { if (!focusedFolder) { throw new Error('No current folder found'); } @@ -334,7 +350,6 @@ function AddModal(): JSX.Element { const { filesToUpload, filesExcluded } = validateAndFilterFiles(documents); showFileSizeAlert(filesExcluded); const filesToProcess = await handleDuplicateFiles(filesToUpload, focusedFolder.uuid); - if (filesToProcess.length === 0) { dispatch(uiActions.setShowUploadFileModal(false)); return; @@ -431,7 +446,6 @@ function AddModal(): JSX.Element { try { const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.assetId || asset.uri); const cleanUri = assetInfo.mediaType === 'video' ? asset.uri : assetInfo.localUri || asset.uri; - // asset info has the correct format (heic issue) const originalFileName = assetInfo.filename || asset.fileName; let fileSize = asset.fileSize; @@ -455,10 +469,11 @@ function AddModal(): JSX.Element { } catch (error) { logger.error('Error obtaining original asset info:', error); const cleanUri = asset.uri; - const formatInfo = detectImageFormat(asset); + const fallbackName = generateFileName(cleanUri); + documents.push({ fileCopyUri: cleanUri, - name: asset.fileName ?? `media_${Date.now()}.${formatInfo.extension ?? 'jpg'}`, + name: fallbackName, size: asset.fileSize ?? 0, type: asset.type ?? '', uri: cleanUri, @@ -496,35 +511,99 @@ function AddModal(): JSX.Element { } } } else { - DocumentPicker.pickMultiple({ - type: [DocumentPicker.types.images], - copyTo: 'cachesDirectory', - }) - .then(processFilesFromPicker) - .then(async () => { - dispatch(driveThunks.loadUsageThunk()); + const { status } = await MediaLibrary.requestPermissionsAsync(); - if (focusedFolder) { - await SLEEP_BECAUSE_MAYBE_BACKEND_IS_NOT_RETURNING_FRESHLY_MODIFIED_OR_CREATED_ITEMS_YET(1000); - driveCtx.loadFolderContent(focusedFolder.uuid, { - pullFrom: ['network'], - resetPagination: true, - }); - } - }) - .catch((err) => { - if (err.message === 'User canceled document picker') { - return; - } - logger.error('Error on handleUploadFromCameraRoll function:', JSON.stringify(err)); - notificationsService.show({ - type: NotificationType.Error, - text1: strings.formatString(strings.errors.uploadFile, err.message) as string, + if (status === 'granted') { + try { + const result = await launchImageLibraryAsync({ + mediaTypes: MediaTypeOptions.All, + allowsMultipleSelection: true, + selectionLimit: MAX_FILES_BULK_UPLOAD, + allowsEditing: false, + preferredAssetRepresentationMode: UIImagePickerPreferredAssetRepresentationMode.Current, + exif: true, + base64: false, }); - }) - .finally(() => { + + if (result.canceled || !result.assets?.length) return; + + const documents: DocumentPickerFile[] = []; + + for (const asset of result.assets) { + try { + const cleanUri = asset.uri; + let originalFileName = asset.fileName; + const exif = asset.exif || null; + + const creationTime = parseExifDate(exif?.DateTimeOriginal); + const modificationTime = parseExifDate(exif?.DateTime); + if (isTemporaryFileName(originalFileName)) { + originalFileName = generateFileName(cleanUri, creationTime, modificationTime); + } + + let fileSize = asset.fileSize ?? 0; + if (!fileSize) { + try { + const fileInfo = await FileSystem.getInfoAsync(cleanUri); + fileSize = fileInfo.exists ? fileInfo.size || 0 : 0; + } catch (error) { + logger.warn('The file size could not be obtained:', error); + fileSize = 0; + } + } + + documents.push({ + fileCopyUri: cleanUri, + name: decodeURIComponent(originalFileName ?? ''), + size: fileSize, + type: drive.file.getExtensionFromUri(cleanUri)?.toLowerCase() ?? '', + uri: cleanUri, + creationTime, + modificationTime, + }); + } catch (error) { + logger.error('Error obtaining original asset info:', error); + const cleanUri = asset.uri; + const fallbackName = generateFileName(cleanUri); + + documents.push({ + fileCopyUri: cleanUri, + name: fallbackName, + size: asset.fileSize ?? 0, + type: asset.type ?? '', + uri: cleanUri, + }); + } + } + dispatch(uiActions.setShowUploadFileModal(false)); - }); + + uploadDocuments(documents) + .then(async () => { + dispatch(driveThunks.loadUsageThunk()); + + if (focusedFolder) { + await SLEEP_BECAUSE_MAYBE_BACKEND_IS_NOT_RETURNING_FRESHLY_MODIFIED_OR_CREATED_ITEMS_YET(500); + driveCtx.loadFolderContent(focusedFolder.uuid, { + pullFrom: ['network'], + resetPagination: true, + }); + } + }) + .catch((err) => { + logger.error('Error on handleUploadFromCameraRoll (Android):', JSON.stringify(err)); + notificationsService.show({ + type: NotificationType.Error, + text1: strings.formatString(strings.errors.uploadFile, err.message) as string, + }); + }) + .finally(() => { + dispatch(uiActions.setShowUploadFileModal(false)); + }); + } catch (error) { + logger.error('Error accessing media library (Android):', error); + } + } } } diff --git a/src/services/drive/file/driveFile.service.ts b/src/services/drive/file/driveFile.service.ts index 44de3dfe7..97649ad9f 100644 --- a/src/services/drive/file/driveFile.service.ts +++ b/src/services/drive/file/driveFile.service.ts @@ -6,7 +6,7 @@ import asyncStorageService from '@internxt-mobile/services/AsyncStorageService'; import { imageService, SdkManager } from '@internxt-mobile/services/common'; import fileSystemService, { fs } from '@internxt-mobile/services/FileSystemService'; import { Abortable, AsyncStorageKey } from '@internxt-mobile/types/index'; -import { EncryptionVersion, MoveFileUuidPayload, Thumbnail } from '@internxt/sdk/dist/drive/storage/types'; +import { EncryptionVersion, FileMeta, Thumbnail } from '@internxt/sdk/dist/drive/storage/types'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import { SaveFormat } from 'expo-image-manipulator'; import { Image } from 'react-native'; @@ -97,8 +97,14 @@ class DriveFileService { }); } - public async moveFile(moveFilePayload: MoveFileUuidPayload) { - return this.sdk.storageV2.moveFileByUuid(moveFilePayload); + public async moveFile({ + fileUuid, + destinationFolderUuid, + }: { + fileUuid: string; + destinationFolderUuid: string; + }): Promise { + return this.sdk.storageV2.moveFileByUuid(fileUuid, { destinationFolder: destinationFolderUuid }); } public getSortFunction({ diff --git a/src/services/drive/file/utils/checkDuplicatedFiles.ts b/src/services/drive/file/utils/checkDuplicatedFiles.ts index 2b98030c8..95ec798c8 100644 --- a/src/services/drive/file/utils/checkDuplicatedFiles.ts +++ b/src/services/drive/file/utils/checkDuplicatedFiles.ts @@ -18,6 +18,8 @@ export interface File { uri: string; size: number; type?: string; + modificationTime?: string; + creationTime?: string; } export const checkDuplicatedFiles = async (files: File[], parentFolderUuid: string): Promise => { diff --git a/src/services/drive/file/utils/exifHelpers.spec.ts b/src/services/drive/file/utils/exifHelpers.spec.ts new file mode 100644 index 000000000..aca1825eb --- /dev/null +++ b/src/services/drive/file/utils/exifHelpers.spec.ts @@ -0,0 +1,75 @@ +jest.mock('../..', () => ({ + default: { + file: { + getExtensionFromUri: jest.fn(), + }, + }, +})); + +import { isTemporaryFileName, parseExifDate } from './exifHelpers'; + +describe('parseExifDate', () => { + test('should parse valid EXIF date format', () => { + const exifDate = '2024:12:25 14:30:45'; + const result = parseExifDate(exifDate); + expect(result).toMatch(/2024-12-25T\d{2}:30:45\.\d{3}Z/); + }); + + test('should return undefined for undefined input', () => { + const result = parseExifDate(undefined); + expect(result).toBeUndefined(); + }); + + test('should return undefined for invalid date', () => { + const result = parseExifDate('invalid:date:format'); + expect(result).toBeUndefined(); + }); + + test('should return undefined for malformed EXIF date', () => { + const result = parseExifDate('2024:13:45 25:99:99'); + expect(result).toBeUndefined(); + }); + + test('should handle partial EXIF dates', () => { + const result = parseExifDate('2024:01:15'); + expect(result).toBeDefined(); + }); +}); + +describe('isTemporaryFileName', () => { + test('should return true for numeric filenames', () => { + expect(isTemporaryFileName('12345.jpg')).toBe(true); + expect(isTemporaryFileName('987654321.png')).toBe(true); + expect(isTemporaryFileName('1234.mp4')).toBe(true); + }); + + test('should return true for undefined or null', () => { + expect(isTemporaryFileName(undefined)).toBe(true); + expect(isTemporaryFileName(null)).toBe(true); + }); + + test('should return true for empty string', () => { + expect(isTemporaryFileName('')).toBe(true); + }); + + test('should return false for descriptive filenames', () => { + expect(isTemporaryFileName('IMG_20240512_143045.jpg')).toBe(false); + expect(isTemporaryFileName('photo_2024.png')).toBe(false); + expect(isTemporaryFileName('vacation.mp4')).toBe(false); + }); + + test('should return false for filenames with letters', () => { + expect(isTemporaryFileName('abc123.jpg')).toBe(false); + expect(isTemporaryFileName('12abc.png')).toBe(false); + }); + + test('should handle different file extensions', () => { + expect(isTemporaryFileName('12345.heic')).toBe(true); + expect(isTemporaryFileName('67890.mov')).toBe(true); + expect(isTemporaryFileName('11111.webp')).toBe(true); + }); + + test('should return false for filenames without extension', () => { + expect(isTemporaryFileName('12345')).toBe(false); + }); +}); diff --git a/src/services/drive/file/utils/exifHelpers.ts b/src/services/drive/file/utils/exifHelpers.ts new file mode 100644 index 000000000..1e4857d45 --- /dev/null +++ b/src/services/drive/file/utils/exifHelpers.ts @@ -0,0 +1,67 @@ +import drive from '../..'; + +/** + * Parses EXIF date string (format: "YYYY:MM:DD HH:MM:SS") to ISO string + * @param exifDate - EXIF date string + * @returns ISO date string or undefined if parsing fails + * @note Invalid dates are handled by returning undefined (no error thrown) + */ +export function parseExifDate(exifDate: string | undefined): string | undefined { + if (!exifDate) return undefined; + + try { + const fixedDate = exifDate.replace(/^(\d{4}):(\d{2}):(\d{2})/, '$1-$2-$3'); + const parsedDate = new Date(fixedDate); + return Number.isNaN(parsedDate.getTime()) ? undefined : parsedDate.toISOString(); + } catch { + return undefined; + } +} + +/** + * Checks if filename is a temporary numeric name + * @param fileName - Original filename + * @returns true if it's a temporary name + */ +export function isTemporaryFileName(fileName: string | undefined | null): boolean { + if (!fileName) return true; + return /^\d+\.\w+$/i.test(fileName); +} + +/** + * Generates a descriptive filename for media files + * @param uri - File URI to extract extension + * @param creationTime - ISO string of creation time + * @param modificationTime - ISO string of modification time + * @returns Generated filename in format IMG_YYYY-MM-DD_HH-MM-SS.extension + * @note Invalid dates fallback to current timestamp. Errors during formatting + * fallback to timestamp-based names (IMG_.) + */ +export function generateFileName(uri: string, creationTime?: string, modificationTime?: string): string { + let timestamp: Date; + + try { + const dateString = modificationTime || creationTime; + timestamp = dateString ? new Date(dateString) : new Date(); + + if (Number.isNaN(timestamp.getTime())) { + timestamp = new Date(); + } + } catch { + timestamp = new Date(); + } + + try { + const isoString = timestamp.toISOString(); + const [datePart, timePart] = isoString.split('T'); + const timeStr = timePart.split('.')[0].replaceAll(':', ''); + const extension = drive.file.getExtensionFromUri(uri)?.toLowerCase(); + + const generatedName = `IMG_${datePart}_${timeStr}.${extension}`; + + return generatedName; + } catch { + const fallbackExtension = drive.file.getExtensionFromUri(uri)?.toLowerCase(); + return `IMG_${Date.now()}.${fallbackExtension}`; + } +} diff --git a/src/services/drive/file/utils/prepareFilesToUpload.ts b/src/services/drive/file/utils/prepareFilesToUpload.ts index d3782c03f..494a2948d 100644 --- a/src/services/drive/file/utils/prepareFilesToUpload.ts +++ b/src/services/drive/file/utils/prepareFilesToUpload.ts @@ -1,5 +1,5 @@ import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types'; -import { DocumentPickerResponse } from 'react-native-document-picker'; +import { DocumentPickerFile } from '../../../../types/drive'; import { checkDuplicatedFiles } from './checkDuplicatedFiles'; import { processDuplicateFiles } from './processDuplicateFiles'; @@ -9,6 +9,8 @@ export interface FileToUpload { size: number; type: string; parentUuid: string; + modificationTime?: string; + creationTime?: string; } const BATCH_SIZE = 200; @@ -19,7 +21,7 @@ export const prepareFilesToUpload = async ({ disableDuplicatedNamesCheck = false, disableExistenceCheck = false, }: { - files: DocumentPickerResponse[]; + files: DocumentPickerFile[]; parentFolderUuid: string; disableDuplicatedNamesCheck?: boolean; disableExistenceCheck?: boolean; @@ -28,7 +30,7 @@ export const prepareFilesToUpload = async ({ let zeroLengthFilesNumber = 0; const processFiles = async ( - filesBatch: DocumentPickerResponse[], + filesBatch: DocumentPickerFile[], disableDuplicatedNamesCheckOverride: boolean, duplicatedFiles?: DriveFileData[], ) => { @@ -44,7 +46,7 @@ export const prepareFilesToUpload = async ({ zeroLengthFilesNumber += zeroLengthFiles; }; - const processFilesBatch = async (filesBatch: DocumentPickerResponse[]) => { + const processFilesBatch = async (filesBatch: DocumentPickerFile[]) => { if (disableExistenceCheck) { await processFiles(filesBatch, true); } else { @@ -53,6 +55,8 @@ export const prepareFilesToUpload = async ({ uri: f.uri, size: f.size, type: f.type ?? '', + modificationTime: f.modificationTime, + creationTime: f.creationTime, })); const { duplicatedFilesResponse, filesWithoutDuplicates, filesWithDuplicates } = await checkDuplicatedFiles( @@ -60,9 +64,9 @@ export const prepareFilesToUpload = async ({ parentFolderUuid, ); - await processFiles(filesWithoutDuplicates as DocumentPickerResponse[], true); + await processFiles(filesWithoutDuplicates as DocumentPickerFile[], true); await processFiles( - filesWithDuplicates as DocumentPickerResponse[], + filesWithDuplicates as DocumentPickerFile[], disableDuplicatedNamesCheck, duplicatedFilesResponse, ); diff --git a/src/services/drive/file/utils/processDuplicateFiles.ts b/src/services/drive/file/utils/processDuplicateFiles.ts index ca44000fe..ef6837657 100644 --- a/src/services/drive/file/utils/processDuplicateFiles.ts +++ b/src/services/drive/file/utils/processDuplicateFiles.ts @@ -1,5 +1,6 @@ import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types'; import { DocumentPickerResponse } from 'react-native-document-picker'; +import { DocumentPickerFile } from '../../../../types/drive'; import { getUniqueFilename } from './getUniqueFilename'; import { FileToUpload } from './prepareFilesToUpload'; @@ -24,7 +25,7 @@ export const processDuplicateFiles = async ({ const zeroLengthFiles = files.filter((file) => file.size === 0).length; const newFilesToUpload: FileToUpload[] = [...existingFilesToUpload]; - const processFile = async (file: DocumentPickerResponse): Promise => { + const processFile = async (file: DocumentPickerFile): Promise => { if (file.size === 0) return; const { plainName, extension } = getFilenameAndExt(file.name); @@ -40,6 +41,8 @@ export const processDuplicateFiles = async ({ type: extension ?? file.type ?? '', uri: file.uri, parentUuid: parentFolderUuid, + modificationTime: file.modificationTime ?? undefined, + creationTime: file.creationTime ?? undefined, }); }; diff --git a/src/services/drive/file/utils/uploadFileUtils.ts b/src/services/drive/file/utils/uploadFileUtils.ts index c9c2eb73d..a6476c9be 100644 --- a/src/services/drive/file/utils/uploadFileUtils.ts +++ b/src/services/drive/file/utils/uploadFileUtils.ts @@ -4,7 +4,7 @@ import uuid from 'react-native-uuid'; import strings from '../../../../../assets/lang/strings'; import { isValidFilename } from '../../../../helpers'; import { driveActions } from '../../../../store/slices/drive'; -import { UPLOAD_FILE_SIZE_LIMIT, UploadingFile } from '../../../../types/drive'; +import { DocumentPickerFile, UPLOAD_FILE_SIZE_LIMIT, UploadingFile } from '../../../../types/drive'; import { checkDuplicatedFiles, File } from './checkDuplicatedFiles'; import { FileToUpload, prepareFilesToUpload } from './prepareFilesToUpload'; @@ -20,12 +20,12 @@ import notificationsService from '../../../NotificationsService'; /** * Validate file names and filter out files exceeding the upload size limit. * - * @param {DocumentPickerResponse[]} documents - Array of selected documents. - * @returns {{ filesToUpload: DocumentPickerResponse[], filesExcluded: DocumentPickerResponse[] }} + * @param {DocumentPickerFile[]} documents - Array of selected documents. + * @returns {{ filesToUpload: DocumentPickerFile[], filesExcluded: DocumentPickerFile[] }} */ -export function validateAndFilterFiles(documents: DocumentPickerResponse[]) { - const filesToUpload: DocumentPickerResponse[] = []; - const filesExcluded: DocumentPickerResponse[] = []; +export function validateAndFilterFiles(documents: DocumentPickerFile[]) { + const filesToUpload: DocumentPickerFile[] = []; + const filesExcluded: DocumentPickerFile[] = []; if (!documents.every((file) => isValidFilename(file.name))) { throw new Error('Some file names are not valid'); @@ -45,9 +45,9 @@ export function validateAndFilterFiles(documents: DocumentPickerResponse[]) { /** * Show an alert when some files exceed the upload size limit. * - * @param {DocumentPickerResponse[]} filesExcluded - Files that were excluded due to size. + * @param {DocumentPickerFile[]} filesExcluded - Files that were excluded due to size. */ -export function showFileSizeAlert(filesExcluded: DocumentPickerResponse[]) { +export function showFileSizeAlert(filesExcluded: DocumentPickerFile[]) { if (filesExcluded.length === 0) return; const messageKey = filesExcluded.length === 1 ? strings.messages.uploadFileLimit : strings.messages.uploadFilesLimit; @@ -58,30 +58,28 @@ export function showFileSizeAlert(filesExcluded: DocumentPickerResponse[]) { /** * Handle duplicate files by checking for existing files in the target folder and optionally prompting the user. * - * @param {DocumentPickerResponse[]} files - Files to check for duplication. + * @param {DocumentPickerFile[]} files - Files to check for duplication. * @param {string} folderUuid - UUID of the destination folder. - * @returns {Promise} - Files to proceed with after handling duplicates. + * @returns {Promise} - Files to proceed with after handling duplicates. */ export async function handleDuplicateFiles( - files: DocumentPickerResponse[], + files: DocumentPickerFile[], folderUuid: string, -): Promise { +): Promise { const mappedFiles = files.map((file) => ({ - name: file.name, - uri: file.uri, - size: file.size, + ...file, type: file.type ?? '', })); const { filesWithoutDuplicates, filesWithDuplicates } = await checkDuplicatedFiles(mappedFiles, folderUuid); - let filesToProcess = [...filesWithoutDuplicates] as DocumentPickerResponse[]; + let filesToProcess = [...filesWithoutDuplicates] as DocumentPickerFile[]; if (filesWithDuplicates.length > 0) { const shouldUploadDuplicates = await askUserAboutDuplicates(filesWithDuplicates); if (shouldUploadDuplicates) { - filesToProcess = [...filesToProcess, ...(filesWithDuplicates as DocumentPickerResponse[])]; + filesToProcess = [...filesToProcess, ...(filesWithDuplicates as DocumentPickerFile[])]; } } @@ -163,6 +161,8 @@ export function createUploadingFiles( size: preparedFile.size, progress: 0, uploaded: false, + modificationTime: preparedFile.modificationTime, + creationTime: preparedFile.creationTime, }; formattedFiles.push(fileToUpload); diff --git a/src/services/drive/folder/driveFolder.service.ts b/src/services/drive/folder/driveFolder.service.ts index 6b83b0f58..be2db33a1 100644 --- a/src/services/drive/folder/driveFolder.service.ts +++ b/src/services/drive/folder/driveFolder.service.ts @@ -1,5 +1,3 @@ -import { MoveFolderUuidPayload } from '@internxt/sdk/dist/drive/storage/types'; - import asyncStorageService from '@internxt-mobile/services/AsyncStorageService'; import { SdkManager } from '@internxt-mobile/services/common'; import { AsyncStorageKey } from '@internxt-mobile/types/index'; @@ -34,8 +32,14 @@ class DriveFolderService { return sdkResult ? sdkResult[0] : Promise.reject('createFolder Sdk method did not return a valid result'); } - public async moveFolder(payload: MoveFolderUuidPayload) { - return this.sdk.storageV2.moveFolderByUuid(payload); + public async moveFolder({ + destinationFolderUuid, + folderUuid, + }: { + folderUuid: string; + destinationFolderUuid: string; + }) { + return this.sdk.storageV2.moveFolderByUuid(folderUuid, { destinationFolder: destinationFolderUuid }); } public async updateMetaData(folderUuid: string, newName: string): Promise { diff --git a/src/types/drive.ts b/src/types/drive.ts index 3cecf43cb..b2a957fb9 100644 --- a/src/types/drive.ts +++ b/src/types/drive.ts @@ -1,3 +1,4 @@ +import { DocumentPickerResponse } from 'react-native-document-picker'; import { SharedFiles, SharedFolders } from '@internxt/sdk/dist/drive/share/types'; import { DriveFileData, @@ -10,6 +11,15 @@ import { const GB = 1024 * 1024 * 1024; export const UPLOAD_FILE_SIZE_LIMIT = 5 * GB; +/** + * Extended DocumentPickerResponse with file metadata timestamps. + * Used during file upload process to preserve original creation and modification times. + */ +export type DocumentPickerFile = DocumentPickerResponse & { + modificationTime?: string; + creationTime?: string; +}; + export interface DriveNavigationStackItem { id: number; parentId: number; @@ -120,6 +130,8 @@ export interface UploadingFile { size: number; progress: number; uploaded: boolean; + modificationTime?: string; + creationTime?: string; } export interface DownloadingFile { diff --git a/tsconfig.json b/tsconfig.json index c54b8f53e..b657dd5ed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "@internxt-mobile/hooks/*": ["src/hooks/*"], "@internxt-mobile/services/*": ["src/services/*"], "@internxt-mobile/types/*": ["src/types/*"], - "@internxt-mobile/useCases/*": ["src/useCases/*"] + "@internxt-mobile/useCases/*": ["src/useCases/*"], + "@internxt-mobile/contexts/*": ["src/contexts/*"] } }, "include": ["app.config.ts", "src", "__tests__", "../modile-sdk"] diff --git a/yarn.lock b/yarn.lock index f43569c3e..44ce933d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1766,10 +1766,10 @@ dependencies: buffer "^6.0.3" -"@internxt/sdk@1.11.0": - version "1.11.0" - resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.11.0/eb547ca5a15d4e56a4e23db065482b3533e11b33#eb547ca5a15d4e56a4e23db065482b3533e11b33" - integrity sha512-65v2zzpACKlBXBoQURVrfevyELNBB1NypNiOqhlR4havwnCLrLgX9ZMSoUKvBtq2O0Orfdfqy8AA2AJrq+xW3Q== +"@internxt/sdk@=1.11.6": + version "1.11.6" + resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.11.6/af41e231f6aa28df84977f96ef0049c295446907#af41e231f6aa28df84977f96ef0049c295446907" + integrity sha512-H/cd1e574Z1Adcb0jrQp+LIIUPXEkJZEExrxV2m3JgFMc6+VIlJTDzzTi5Bbz42ycSEF+vareY24Qfcn6ijlEA== dependencies: axios "1.11.0" uuid "11.1.0"