From cdc8b46847e2385eb8b89dc393781079b890baeb Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 25 Oct 2024 11:37:45 +0200 Subject: [PATCH 01/29] Fixed Android cursor position bug in TextInput --- src/components/AppTextInput/index.tsx | 81 +++++++++++++++++-- .../modals/DriveRenameModal/index.tsx | 24 +++--- 2 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/components/AppTextInput/index.tsx b/src/components/AppTextInput/index.tsx index 90c218266..a199fc356 100644 --- a/src/components/AppTextInput/index.tsx +++ b/src/components/AppTextInput/index.tsx @@ -1,7 +1,16 @@ import { isString } from 'lodash'; -import { useState } from 'react'; -import { StyleProp, TextInput, TextInputProps, View, ViewStyle } from 'react-native'; -import { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; +import { useRef, useState } from 'react'; +import { + NativeSyntheticEvent, + Platform, + StyleProp, + TextInput, + TextInputFocusEventData, + TextInputProps, + TextInputSelectionChangeEventData, + View, + ViewStyle, +} from 'react-native'; import { useTailwind } from 'tailwind-rn'; import useGetColor from '../../hooks/useColor'; import AppText from '../AppText'; @@ -19,8 +28,56 @@ const AppTextInput = (props: AppTextInputProps): JSX.Element => { const tailwind = useTailwind(); const getColor = useGetColor(); const [isFocused, setIsFocused] = useState(false); + const [selection, setSelection] = useState<{ start: number; end: number } | undefined>(); + const localInputRef = useRef(null); const editable = props.editable !== false; const [status, statusMessage] = props.status || ['idle', '']; + const isAndroid = Platform.OS === 'android'; + + const handleSelectionChange = (event: NativeSyntheticEvent) => { + if (isAndroid) { + const newSelection = event.nativeEvent.selection; + setSelection(newSelection); + } + }; + + const handleOnFocusInput = (e: NativeSyntheticEvent) => { + setIsFocused(true); + if (isAndroid && props.value && (!selection || (selection.start === 0 && selection.end === 0))) { + const position = props.value.length; + setSelection({ start: position, end: position }); + } + props.onFocus?.(e); + }; + + // Added selection prop handler to resolve Android input cursor position bug + const handleChangeText = (newText: string) => { + if (isAndroid && selection) { + const oldText = props.value || ''; + let newPosition: number; + + const hasNothingSelected = selection.start === selection.end; + if (hasNothingSelected) { + newPosition = selection.start + (newText.length - oldText.length); + setSelection({ start: newPosition, end: newPosition }); + } else { + const selectionStart = Math.min(selection.start, selection.end); + newPosition = selectionStart + newText.length - oldText.slice(selection.start, selection.end).length; + setSelection({ start: newPosition, end: newPosition }); + } + + setTimeout(() => { + if (localInputRef.current) { + localInputRef.current.setNativeProps({ + selection: { start: newPosition, end: newPosition }, + }); + } + }, 0); + } + + props.onChangeText?.(newText); + }; + const renderStatusMessage = () => { let template: JSX.Element | undefined = undefined; @@ -47,6 +104,14 @@ const AppTextInput = (props: AppTextInputProps): JSX.Element => { return template; }; + const inputProps = { + ...props, + ref: props.inputRef || localInputRef, + onChangeText: isAndroid ? handleChangeText : props.onChangeText, + onSelectionChange: isAndroid ? handleSelectionChange : props.onSelectionChange, + selection: isAndroid ? selection : props.selection, + }; + return ( {props.label && {props.label}} @@ -63,11 +128,13 @@ const AppTextInput = (props: AppTextInputProps): JSX.Element => { > setIsFocused(true)} - onBlur={() => setIsFocused(false)} - ref={props.inputRef} + onFocus={handleOnFocusInput} + onBlur={(e) => { + setIsFocused(false); + props.onBlur?.(e); + }} /> {props.renderAppend && {props.renderAppend({ isFocused })}} diff --git a/src/components/modals/DriveRenameModal/index.tsx b/src/components/modals/DriveRenameModal/index.tsx index fd3d90f16..084b33a16 100644 --- a/src/components/modals/DriveRenameModal/index.tsx +++ b/src/components/modals/DriveRenameModal/index.tsx @@ -1,24 +1,24 @@ import React, { useState } from 'react'; -import { View, TextInput, Platform } from 'react-native'; +import { View } from 'react-native'; +import Portal from '@burstware/react-native-portal'; +import { useDrive } from '@internxt-mobile/hooks/drive'; +import drive from '@internxt-mobile/services/drive'; +import uuid from 'react-native-uuid'; +import AppText from 'src/components/AppText'; +import AppTextInput from 'src/components/AppTextInput'; +import { useTailwind } from 'tailwind-rn'; import strings from '../../../../assets/lang/strings'; import { FolderIcon, getFileTypeIcon } from '../../../helpers'; +import useGetColor from '../../../hooks/useColor'; +import errorService from '../../../services/ErrorService'; +import notificationsService from '../../../services/NotificationsService'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { driveActions } from '../../../store/slices/drive'; import { uiActions } from '../../../store/slices/ui'; -import errorService from '../../../services/ErrorService'; -import AppButton from '../../AppButton'; -import notificationsService from '../../../services/NotificationsService'; import { NotificationType } from '../../../types'; +import AppButton from '../../AppButton'; import CenterModal from '../CenterModal'; -import { useTailwind } from 'tailwind-rn'; -import useGetColor from '../../../hooks/useColor'; -import { useDrive } from '@internxt-mobile/hooks/drive'; -import drive from '@internxt-mobile/services/drive'; -import uuid from 'react-native-uuid'; -import Portal from '@burstware/react-native-portal'; -import AppTextInput from 'src/components/AppTextInput'; -import AppText from 'src/components/AppText'; function RenameModal(): JSX.Element { const tailwind = useTailwind(); const getColor = useGetColor(); From 18b10f11a481b8f93c2ebb0cd85c3d28c7d158d5 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Mon, 11 Nov 2024 15:43:16 +0100 Subject: [PATCH 02/29] Added patch to not convert heic files to jpg when upload them from ios camera roll --- ios/Internxt.xcodeproj/project.pbxproj | 4 +- ios/Podfile.lock | 6 +- package.json | 2 +- patches/react-native-image-picker+7.1.2.patch | 24 +++++ src/components/modals/AddModal/index.tsx | 89 ++++++++++--------- yarn.lock | 8 +- 6 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 patches/react-native-image-picker+7.1.2.patch diff --git a/ios/Internxt.xcodeproj/project.pbxproj b/ios/Internxt.xcodeproj/project.pbxproj index 40b9f8fcc..b74e9dc32 100644 --- a/ios/Internxt.xcodeproj/project.pbxproj +++ b/ios/Internxt.xcodeproj/project.pbxproj @@ -343,7 +343,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.internxt.snacks; - PRODUCT_NAME = "Internxt"; + PRODUCT_NAME = Internxt; SWIFT_OBJC_BRIDGING_HEADER = "Internxt/Internxt-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -374,7 +374,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.internxt.snacks; - PRODUCT_NAME = "Internxt"; + PRODUCT_NAME = Internxt; SWIFT_OBJC_BRIDGING_HEADER = "Internxt/Internxt-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c72ea57fe..e6842f8e3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -940,7 +940,9 @@ PODS: - React-debug - react-native-document-picker (4.3.0): - React-Core - - react-native-image-picker (4.10.3): + - react-native-image-picker (7.1.2): + - glog + - RCT-Folly (= 2022.05.16.00) - React-Core - react-native-pdf (6.7.5): - React-Core @@ -1565,7 +1567,7 @@ SPEC CHECKSUMS: React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab react-native-document-picker: 20f652c2402d3ddc81f396d8167c3bd978add4a2 - react-native-image-picker: 60f4246eb5bb7187fc15638a8c1f13abd3820695 + react-native-image-picker: d3db110a3ded6e48c93aef7e8e51afdde8b16ed0 react-native-pdf: 103940c90d62adfd259f63cca99c7c0c306b514c react-native-pdf-thumbnail: 390b1bc4b115b613ca61d6b14cbc712b3621b1df react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 diff --git a/package.json b/package.json index 2eab6152e..6d675ec1f 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "react-native-file-viewer": "^2.1.4", "react-native-fs": "^2.16.6", "react-native-gesture-handler": "~2.14.0", - "react-native-image-picker": "^4.0.6", + "react-native-image-picker": "^7.1.2", "react-native-image-zoom-viewer": "^3.0.1", "react-native-localization": "^2.3.1", "react-native-logs": "^5.0.1", diff --git a/patches/react-native-image-picker+7.1.2.patch b/patches/react-native-image-picker+7.1.2.patch new file mode 100644 index 000000000..650b8f677 --- /dev/null +++ b/patches/react-native-image-picker+7.1.2.patch @@ -0,0 +1,24 @@ +diff --git a/node_modules/react-native-image-picker/ios/ImagePickerUtils.mm b/node_modules/react-native-image-picker/ios/ImagePickerUtils.mm +index 443500d..6f8d0ba 100644 +--- a/node_modules/react-native-image-picker/ios/ImagePickerUtils.mm ++++ b/node_modules/react-native-image-picker/ios/ImagePickerUtils.mm +@@ -97,6 +97,8 @@ + const uint8_t firstByteJpg = 0xFF; + const uint8_t firstBytePng = 0x89; + const uint8_t firstByteGif = 0x47; ++ const uint8_t firstByteWebp = 0x52; ++ const uint8_t firstByteHeic = 0x00; + + uint8_t firstByte; + [imageData getBytes:&firstByte length:1]; +@@ -107,6 +109,10 @@ + return @"png"; + case firstByteGif: + return @"gif"; ++ case firstByteWebp: ++ return @"webp"; ++ case firstByteHeic: ++ return @"heic"; + default: + return @"jpg"; + } diff --git a/src/components/modals/AddModal/index.tsx b/src/components/modals/AddModal/index.tsx index 7b9e7a191..4e913ab15 100644 --- a/src/components/modals/AddModal/index.tsx +++ b/src/components/modals/AddModal/index.tsx @@ -464,51 +464,54 @@ function AddModal(): JSX.Element { const { status } = await requestMediaLibraryPermissionsAsync(false); if (status === 'granted') { - launchImageLibrary({ mediaType: 'mixed', selectionLimit: MAX_FILES_BULK_UPLOAD }, async (response) => { - if (response.errorMessage) { - return Alert.alert(response.errorMessage); - } - if (response.assets) { - const documents: DocumentPickerResponse[] = []; - - for (const asset of response.assets) { - const decodedURI = decodeURIComponent(asset.uri as string); - const stat = await RNFS.stat(decodedURI); - - documents.push({ - fileCopyUri: asset.uri || '', - name: decodeURIComponent( - asset.fileName || asset.uri?.substring((asset.uri || '').lastIndexOf('/') + 1) || '', - ), - size: asset.fileSize || stat.size, - type: asset.type || '', - uri: asset.uri || '', - }); + launchImageLibrary( + { mediaType: 'mixed', selectionLimit: MAX_FILES_BULK_UPLOAD, assetRepresentationMode: 'current' }, + async (response) => { + if (response.errorMessage) { + return Alert.alert(response.errorMessage); } - - dispatch(uiActions.setShowUploadFileModal(false)); - uploadDocuments(documents) - .then(() => { - dispatch(driveThunks.loadUsageThunk()); - - if (focusedFolder) { - driveCtx.loadFolderContent(focusedFolder.id); - } - }) - .catch((err) => { - if (err.message === 'User canceled document picker') { - return; - } - notificationsService.show({ - type: NotificationType.Error, - text1: strings.formatString(strings.errors.uploadFile, err.message) as string, + if (response.assets) { + const documents: DocumentPickerResponse[] = []; + + for (const asset of response.assets) { + const decodedURI = decodeURIComponent(asset.uri as string); + const stat = await RNFS.stat(decodedURI); + + documents.push({ + fileCopyUri: asset.uri || '', + name: decodeURIComponent( + asset.fileName || asset.uri?.substring((asset.uri || '').lastIndexOf('/') + 1) || '', + ), + size: asset.fileSize || stat.size, + type: asset.type || '', + uri: asset.uri || '', }); - }) - .finally(() => { - dispatch(uiActions.setShowUploadFileModal(false)); - }); - } - }); + } + + dispatch(uiActions.setShowUploadFileModal(false)); + uploadDocuments(documents) + .then(() => { + dispatch(driveThunks.loadUsageThunk()); + + if (focusedFolder) { + driveCtx.loadFolderContent(focusedFolder.id); + } + }) + .catch((err) => { + if (err.message === 'User canceled document picker') { + return; + } + notificationsService.show({ + type: NotificationType.Error, + text1: strings.formatString(strings.errors.uploadFile, err.message) as string, + }); + }) + .finally(() => { + dispatch(uiActions.setShowUploadFileModal(false)); + }); + } + }, + ); } } else { DocumentPicker.pickMultiple({ diff --git a/yarn.lock b/yarn.lock index 2c8364e90..3c927cf83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10765,10 +10765,10 @@ react-native-image-pan-zoom@^2.1.12: resolved "https://registry.yarnpkg.com/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz#eb98bf56fb5610379bdbfdb63219cc1baca98fd2" integrity sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q== -react-native-image-picker@^4.0.6: - version "4.10.3" - resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-4.10.3.tgz#cdc11d9836b4cfa57e658c0700201babf8fdca10" - integrity sha512-gLX8J6jCBkUt6jogpSdA7YyaGVLGYywRzMEwBciXshihpFZjc/cRlKymAVlu6Q7HMw0j3vrho6pI8ZGC5O/FGg== +react-native-image-picker@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-7.1.2.tgz#383849d1953caf4578874a1f5e5dd11c737bd5cd" + integrity sha512-b5y5nP60RIPxlAXlptn2QwlIuZWCUDWa/YPUVjgHc0Ih60mRiOg1PSzf0IjHSLeOZShCpirpvSPGnDExIpTRUg== react-native-image-zoom-viewer@^3.0.1: version "3.0.1" From 6c68b7065350a3f087fe7e9a6b2fec43b5e98ca6 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 12 Nov 2024 13:26:52 +0100 Subject: [PATCH 03/29] Fixed how decimals are diplayed in plans modal --- src/components/modals/PlansModal/index.tsx | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/modals/PlansModal/index.tsx b/src/components/modals/PlansModal/index.tsx index a97561df4..51c34c60d 100644 --- a/src/components/modals/PlansModal/index.tsx +++ b/src/components/modals/PlansModal/index.tsx @@ -1,27 +1,30 @@ import React, { useEffect, useState } from 'react'; import { ScrollView, TouchableWithoutFeedback, View } from 'react-native'; -import strings from '../../../../assets/lang/strings'; -import FileIcon from '../../../../assets/icons/file-icon.svg'; -import BottomModal from '../BottomModal'; -import { BaseModalProps } from '../../../types/ui'; -import { useTailwind } from 'tailwind-rn'; +import Animated, { FadeInDown } from 'react-native-reanimated'; import AppButton from 'src/components/AppButton'; import AppText from 'src/components/AppText'; +import StorageUsageBar from 'src/components/StorageUsageBar'; +import storageService from 'src/services/StorageService'; import { useAppDispatch, useAppSelector } from 'src/store/hooks'; import { paymentsSelectors, paymentsThunks } from 'src/store/slices/payments'; -import StorageUsageBar from 'src/components/StorageUsageBar'; import { storageSelectors } from 'src/store/slices/storage'; -import storageService from 'src/services/StorageService'; +import { useTailwind } from 'tailwind-rn'; +import FileIcon from '../../../../assets/icons/file-icon.svg'; +import strings from '../../../../assets/lang/strings'; +import { BaseModalProps } from '../../../types/ui'; +import BottomModal from '../BottomModal'; import { ConfirmationStep } from './ConfirmationStep'; -import Animated, { FadeInDown } from 'react-native-reanimated'; -import { getLineHeight } from 'src/styles/global'; -import prettysize from 'prettysize'; -import { notifications } from '@internxt-mobile/services/NotificationsService'; import errorService from '@internxt-mobile/services/ErrorService'; +import { notifications } from '@internxt-mobile/services/NotificationsService'; +import prettysize from 'prettysize'; +import { getLineHeight } from 'src/styles/global'; export type SubscriptionInterval = 'month' | 'year'; + +const formatAmount = (amount: number | undefined) => ((amount || 0) * 0.01).toFixed(2); + const PlansModal = (props: BaseModalProps) => { const [selectedStorageBytes, setSelectedStorageBytes] = useState(); const [selectedInterval, setSelectedInterval] = useState(); @@ -184,12 +187,13 @@ const PlansModal = (props: BaseModalProps) => { ); }); + const renderButtons = (selectedBytes: number) => { const displayPrices = getDisplayPriceWithIntervals(selectedBytes); const monthlyPrice = displayPrices.find((display) => display.interval === 'month'); const yearlyPrice = displayPrices.find((display) => display.interval === 'year'); - const monthlyAmount = (monthlyPrice?.amount || 0) * 0.01; - const yearlyAmount = (yearlyPrice?.amount || 0) * 0.01; + const monthlyAmount = formatAmount(monthlyPrice?.amount); + const yearlyAmount = formatAmount(yearlyPrice?.amount); return ( <> From 9eea2b73b8e7ea4f34c15bff7c59a64e790b4366 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 12 Nov 2024 15:51:15 +0100 Subject: [PATCH 04/29] Added close preview abillity when swipe back in preview view --- src/components/SwipeBackHandler/index.tsx | 23 +++++++++++++++++++ .../DrivePreviewScreenHeader.tsx | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 src/components/SwipeBackHandler/index.tsx diff --git a/src/components/SwipeBackHandler/index.tsx b/src/components/SwipeBackHandler/index.tsx new file mode 100644 index 000000000..f8fc18d5a --- /dev/null +++ b/src/components/SwipeBackHandler/index.tsx @@ -0,0 +1,23 @@ +import { useEffect } from 'react'; +import { BackHandler } from 'react-native'; + +interface SwipeBackProps { + swipeBack: () => void; +} + +const SwipeBackHandler = ({ swipeBack }: SwipeBackProps) => { + useEffect(() => { + const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackPress); + + return () => backHandler.remove(); + }, []); + + const handleBackPress = () => { + swipeBack(); + return true; + }; + + return null; +}; + +export default SwipeBackHandler; diff --git a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreenHeader.tsx b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreenHeader.tsx index f3005f243..fb074038f 100644 --- a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreenHeader.tsx +++ b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreenHeader.tsx @@ -4,6 +4,7 @@ import { TouchableOpacity, View } from 'react-native'; import AppText from 'src/components/AppText'; import { INCREASED_TOUCH_AREA } from 'src/styles/global'; import { useTailwind } from 'tailwind-rn'; +import SwipeBackHandler from '../../../components/SwipeBackHandler'; export interface DrivePreviewScreenHeaderProps { onCloseButtonPress: () => void; @@ -21,6 +22,7 @@ export const DrivePreviewScreenHeader: React.FC = { height: DRIVE_PREVIEW_HEADER_HEIGHT }, ]} > + From 47bc88ecc7a0b10460a14a8fc978002a23d3d712 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 12 Nov 2024 17:17:57 +0100 Subject: [PATCH 05/29] Sonar cloud minor change --- src/components/modals/PlansModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modals/PlansModal/index.tsx b/src/components/modals/PlansModal/index.tsx index 51c34c60d..8e44a303e 100644 --- a/src/components/modals/PlansModal/index.tsx +++ b/src/components/modals/PlansModal/index.tsx @@ -23,7 +23,7 @@ import { getLineHeight } from 'src/styles/global'; export type SubscriptionInterval = 'month' | 'year'; -const formatAmount = (amount: number | undefined) => ((amount || 0) * 0.01).toFixed(2); +const formatAmount = (amount: number | undefined) => ((amount ?? 0) * 0.01).toFixed(2); const PlansModal = (props: BaseModalProps) => { const [selectedStorageBytes, setSelectedStorageBytes] = useState(); From c636cf92b1de5782190fc1e7d6ba9a684f323f37 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Wed, 13 Nov 2024 15:33:34 +0100 Subject: [PATCH 06/29] Display server message when login fails instead of generic message --- src/screens/SignInScreen/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/SignInScreen/index.tsx b/src/screens/SignInScreen/index.tsx index 6dc357766..4232abf3d 100644 --- a/src/screens/SignInScreen/index.tsx +++ b/src/screens/SignInScreen/index.tsx @@ -118,7 +118,7 @@ function SignInScreen({ navigation }: RootStackScreenProps<'SignIn'>): JSX.Eleme setIsLoading(false); - setErrors({ loginFailed: strings.errors.missingAuthCredentials }); + setErrors({ loginFailed: castedError.message }); } }; const onGoToSignUpButtonPressed = () => { From eace2bcf41f6c85fc729ffc79dc3d21df1ca814d Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Mon, 18 Nov 2024 13:18:02 +0100 Subject: [PATCH 07/29] Fixed bug that modal stay stucked at bottom of the screen --- src/components/modals/AddModal/index.tsx | 6 +++++- src/components/modals/BottomModal/index.tsx | 5 ++--- src/screens/drive/DriveFolderScreen/DriveFolderScreen.tsx | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/modals/AddModal/index.tsx b/src/components/modals/AddModal/index.tsx index 4e913ab15..9a77cfca1 100644 --- a/src/components/modals/AddModal/index.tsx +++ b/src/components/modals/AddModal/index.tsx @@ -41,6 +41,7 @@ import BottomModal from '../BottomModal'; import CreateFolderModal from '../CreateFolderModal'; const MAX_FILES_BULK_UPLOAD = 25; + function AddModal(): JSX.Element { const tailwind = useTailwind(); const getColor = useGetColor(); @@ -53,6 +54,7 @@ function AddModal(): JSX.Element { const { limit } = useAppSelector((state) => state.storage); const usage = useAppSelector(storageSelectors.usage); + async function uploadIOS(file: UploadingFile, fileType: 'document' | 'image', progressCallback: ProgressCallback) { const name = decodeURI(file.uri).split('/').pop() || ''; const regex = /^(.*:\/{0,2})\/?(.*)$/gm; @@ -81,6 +83,7 @@ function AddModal(): JSX.Element { await SLEEP_BECAUSE_MAYBE_BACKEND_IS_NOT_RETURNING_FRESHLY_MODIFIED_OR_CREATED_ITEMS_YET(500); await driveCtx.loadFolderContent(focusedFolder.id, { pullFrom: ['network'], resetPagination: true }); }; + async function uploadAndroid( fileToUpload: UploadingFile, fileType: 'document' | 'image', @@ -217,6 +220,7 @@ function AddModal(): JSX.Element { thumbnails: uploadedThumbnail ? [uploadedThumbnail] : null, } as DriveFileData; } + const uploadFile = async (uploadingFile: UploadingFile, fileType: 'document' | 'image') => { logger.info('Starting file upload process: ', JSON.stringify(uploadingFile)); function progressCallback(progress: number) { @@ -674,8 +678,8 @@ function AddModal(): JSX.Element { style={tailwind('flex-grow')} underlayColor={getColor('text-gray-40')} onPress={() => { - setShowCreateFolderModal(true); dispatch(uiActions.setShowUploadFileModal(false)); + setShowCreateFolderModal(true); }} > diff --git a/src/components/modals/BottomModal/index.tsx b/src/components/modals/BottomModal/index.tsx index 46a66a32a..0f0388bab 100644 --- a/src/components/modals/BottomModal/index.tsx +++ b/src/components/modals/BottomModal/index.tsx @@ -4,10 +4,9 @@ import Modal from 'react-native-modalbox'; import { StatusBar } from 'expo-status-bar'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { X } from 'phosphor-react-native'; +import { INCREASED_TOUCH_AREA } from 'src/styles/global'; import { useTailwind } from 'tailwind-rn'; import useGetColor from '../../../hooks/useColor'; -import { INCREASED_TOUCH_AREA } from 'src/styles/global'; export interface BottomModalProps { isOpen: boolean; @@ -38,7 +37,7 @@ const BottomModal = (props: BottomModalProps): JSX.Element => { { const isFolder = driveItem.data.type ? false : true; if (!isFolder) { From 6e63967a2322c13ec576a93b0014eb3fa582ea3f Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Wed, 20 Nov 2024 16:09:05 +0100 Subject: [PATCH 08/29] Migrated react-native-fs to forked library to version that fix error generating hash sha1 checksum on 1GB size or bigger files --- .../{ => @dr.pogodin}/react-native-fs.ts | 2 +- ios/Podfile.lock | 14 +++++++------ package.json | 2 +- src/components/modals/AddModal/index.tsx | 5 +++-- src/network/NetworkFacade.ts | 5 ++--- src/network/crypto.ts | 4 ++-- src/network/download.ts | 12 +++++------ src/services/FileSystemService.ts | 15 +++++++------- src/services/NetworkService/download.ts | 11 +++++----- .../filesystem/fileCacheManager.spec.ts | 4 ++-- src/services/common/logger/logger.service.ts | 3 ++- src/services/common/media/image.service.ts | 3 ++- src/services/drive/constants.ts | 3 ++- src/services/drive/events/driveEvents.ts | 2 +- tsconfig.json | 1 + yarn.lock | 20 +++++++------------ 16 files changed, 54 insertions(+), 52 deletions(-) rename __mocks__/{ => @dr.pogodin}/react-native-fs.ts (96%) diff --git a/__mocks__/react-native-fs.ts b/__mocks__/@dr.pogodin/react-native-fs.ts similarity index 96% rename from __mocks__/react-native-fs.ts rename to __mocks__/@dr.pogodin/react-native-fs.ts index 5a3963564..259b79660 100644 --- a/__mocks__/react-native-fs.ts +++ b/__mocks__/@dr.pogodin/react-native-fs.ts @@ -1,4 +1,4 @@ -jest.mock('react-native-fs', () => { +jest.mock('@dr.pogodin/react-native-fs', () => { return { mkdir: jest.fn(), moveFile: jest.fn(), diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e6842f8e3..f2561bed1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,10 @@ PODS: - boost (1.83.0) - DoubleConversion (1.1.6) + - dr-pogodin-react-native-fs (2.22.0): + - glog + - RCT-Folly (= 2022.05.16.00) + - React-Core - EASClient (0.11.2): - ExpoModulesCore - EXConstants (15.4.6): @@ -1153,8 +1157,6 @@ PODS: - React-Core - RNFlashList (1.6.3): - React-Core - - RNFS (2.20.0): - - React-Core - RNGestureHandler (2.14.1): - glog - RCT-Folly (= 2022.05.16.00) @@ -1197,6 +1199,7 @@ PODS: DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - "dr-pogodin-react-native-fs (from `../node_modules/@dr.pogodin/react-native-fs`)" - EASClient (from `../node_modules/expo-eas-client/ios`) - EXConstants (from `../node_modules/expo-constants/ios`) - EXFont (from `../node_modules/expo-font/ios`) @@ -1286,7 +1289,6 @@ DEPENDENCIES: - RNFastImage (from `../node_modules/react-native-fast-image`) - RNFileViewer (from `../node_modules/react-native-file-viewer`) - "RNFlashList (from `../node_modules/@shopify/flash-list`)" - - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNPermissions (from `../node_modules/react-native-permissions`) - RNReanimated (from `../node_modules/react-native-reanimated`) @@ -1316,6 +1318,8 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + dr-pogodin-react-native-fs: + :path: "../node_modules/@dr.pogodin/react-native-fs" EASClient: :path: "../node_modules/expo-eas-client/ios" EXConstants: @@ -1490,8 +1494,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-file-viewer" RNFlashList: :path: "../node_modules/@shopify/flash-list" - RNFS: - :path: "../node_modules/react-native-fs" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" RNPermissions: @@ -1512,6 +1514,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 + dr-pogodin-react-native-fs: c2b2640a9d71537c93620463030c8fafb59170be EASClient: a42ee8bf36c93b3128352faf2ae49405ab4f80bd EXConstants: a5f6276e565d98f9eb4280f81241fc342d641590 EXFont: f20669cb266ef48b004f1eb1f2b20db96cd1df9f @@ -1604,7 +1607,6 @@ SPEC CHECKSUMS: RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592 RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a - RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 15c6ef51acba34c49ff03003806cf5dd6098f383 RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05 RNReanimated: f6b02d8f5eaa2830296411d4ec3b8ef5442dd13d diff --git a/package.json b/package.json index 6d675ec1f..f5a30e495 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@burstware/react-native-portal": "^1.0.2", + "@dr.pogodin/react-native-fs": "2.22.0", "@expo/config-plugins": "7.8.4", "@expo/config-types": "^47.0.0", "@hookform/resolvers": "^2.9.1", @@ -97,7 +98,6 @@ "react-native-document-picker": "^4.1.0", "react-native-fast-image": "^8.5.11", "react-native-file-viewer": "^2.1.4", - "react-native-fs": "^2.16.6", "react-native-gesture-handler": "~2.14.0", "react-native-image-picker": "^7.1.2", "react-native-image-zoom-viewer": "^3.0.1", diff --git a/src/components/modals/AddModal/index.tsx b/src/components/modals/AddModal/index.tsx index 9a77cfca1..5057cdd3e 100644 --- a/src/components/modals/AddModal/index.tsx +++ b/src/components/modals/AddModal/index.tsx @@ -1,13 +1,14 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; import * as FileSystem from 'expo-file-system'; import { launchCameraAsync, requestCameraPermissionsAsync, requestMediaLibraryPermissionsAsync, } from 'expo-image-picker'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { Alert, PermissionsAndroid, Platform, TouchableHighlight, View } from 'react-native'; import DocumentPicker, { DocumentPickerResponse } from 'react-native-document-picker'; -import RNFS from 'react-native-fs'; + import { launchImageLibrary } from 'react-native-image-picker'; import { useDrive } from '@internxt-mobile/hooks/drive'; diff --git a/src/network/NetworkFacade.ts b/src/network/NetworkFacade.ts index cc77779d6..ca4e4b2e8 100644 --- a/src/network/NetworkFacade.ts +++ b/src/network/NetworkFacade.ts @@ -1,3 +1,4 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; import { decryptFile, encryptFile } from '@internxt/rn-crypto'; import { ALGORITHMS, Network } from '@internxt/sdk/dist/network'; import { downloadFile } from '@internxt/sdk/dist/network/download'; @@ -6,7 +7,6 @@ import { uploadFile } from '@internxt/sdk/dist/network/upload'; import { Platform } from 'react-native'; import { validateMnemonic } from 'react-native-bip39'; import { randomBytes } from 'react-native-crypto'; -import * as RNFS from 'react-native-fs'; import uuid from 'react-native-uuid'; import RNFetchBlob from 'rn-fetch-blob'; @@ -114,7 +114,6 @@ export class NetworkFacade { interval !== null && clearInterval(interval); updateProgress(maxEncryptProgress); } - fileHash = ripemd160(Buffer.from(await RNFS.hash(encryptedFilePath, 'sha256'), 'hex')).toString('hex'); }, async (url: string) => { @@ -172,7 +171,7 @@ export class NetworkFacade { throw new Error('Download error code 3'); } - let downloadJob: { jobId: number; promise: Promise }; + let downloadJob: { jobId: number; promise: Promise }; let expectedFileHash: string; const decryptFileFromFs: DecryptFileFromFsFunction = diff --git a/src/network/crypto.ts b/src/network/crypto.ts index 7eef775c4..29b3c899b 100644 --- a/src/network/crypto.ts +++ b/src/network/crypto.ts @@ -1,6 +1,6 @@ -import RNFetchBlob from 'rn-fetch-blob'; -import { stat, read } from 'react-native-fs'; +import { read, stat } from '@dr.pogodin/react-native-fs'; import { createDecipheriv, Decipher } from 'react-native-crypto'; +import RNFetchBlob from 'rn-fetch-blob'; function getAes256CtrDecipher(key: Buffer, iv: Buffer): Decipher { return createDecipheriv('aes-256-ctr', key, iv); diff --git a/src/network/download.ts b/src/network/download.ts index 575cef1d7..3f567d163 100644 --- a/src/network/download.ts +++ b/src/network/download.ts @@ -1,13 +1,13 @@ -import RNFS from 'react-native-fs'; +import * as RNFS from '@dr.pogodin/react-native-fs'; -import { getNetwork } from './NetworkFacade'; +import { FileVersionOneError } from '@internxt/sdk/dist/network/download'; +import FileManager from '../@inxt-js/api/FileManager'; +import { constants } from '../services/AppService'; import { downloadFile as downloadFileV1, LegacyDownloadRequiredError } from '../services/NetworkService/download'; import { downloadFile as downloadFileV1Legacy } from '../services/NetworkService/downloadLegacy'; -import { constants } from '../services/AppService'; -import { NetworkCredentials } from './requests'; -import { FileVersionOneError } from '@internxt/sdk/dist/network/download'; import { Abortable } from '../types'; -import FileManager from '../@inxt-js/api/FileManager'; +import { getNetwork } from './NetworkFacade'; +import { NetworkCredentials } from './requests'; export type EncryptedFileDownloadedParams = { path: string; diff --git a/src/services/FileSystemService.ts b/src/services/FileSystemService.ts index 926295930..29ad4d91e 100644 --- a/src/services/FileSystemService.ts +++ b/src/services/FileSystemService.ts @@ -1,13 +1,14 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; import { Platform } from 'react-native'; -import RNFS from 'react-native-fs'; -import RNFetchBlob, { RNFetchBlobStat } from 'rn-fetch-blob'; -import FileViewer from 'react-native-file-viewer'; + +import { internxtFS } from '@internxt/mobile-sdk'; import * as FileSystem from 'expo-file-system'; +import { shareAsync } from 'expo-sharing'; import prettysize from 'prettysize'; +import FileViewer from 'react-native-file-viewer'; import Share from 'react-native-share'; import uuid from 'react-native-uuid'; -import { shareAsync } from 'expo-sharing'; -import { internxtFS } from '@internxt/mobile-sdk'; +import RNFetchBlob, { RNFetchBlobStat } from 'rn-fetch-blob'; enum AcceptedEncodings { Utf8 = 'utf8', Ascii = 'ascii', @@ -21,7 +22,7 @@ export interface FileWriter { const ANDROID_URI_PREFIX = 'file://'; -export type UsageStatsResult = Record; +export type UsageStatsResult = Record; class FileSystemService { private timestamp = Date.now(); @@ -50,7 +51,7 @@ class FileSystemService { return `internxt_mobile_runtime_logs_${this.timestamp}.txt`; } - public getDownloadsDir(): string { + public getDownloadsDir(): string | undefined { // MainBundlePath is only available on iOS return Platform.OS === 'ios' ? RNFS.MainBundlePath : RNFS.DownloadDirectoryPath; } diff --git a/src/services/NetworkService/download.ts b/src/services/NetworkService/download.ts index fa40671b6..6134f2129 100644 --- a/src/services/NetworkService/download.ts +++ b/src/services/NetworkService/download.ts @@ -1,18 +1,19 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; import { request } from '@internxt/lib'; -import RNFS from 'react-native-fs'; + import axios, { AxiosRequestConfig } from 'axios'; -import RNFetchBlob from 'rn-fetch-blob'; import { createDecipheriv } from 'react-native-crypto'; +import RNFetchBlob from 'rn-fetch-blob'; import { GenerateFileKey, ripemd160, sha256 } from '../../@inxt-js/lib/crypto'; import { eachLimit } from 'async'; -import fileSystemService from '../FileSystemService'; -import { NetworkCredentials } from '../../types'; -import { Platform } from 'react-native'; import { decryptFile as nativeDecryptFile } from '@internxt/rn-crypto'; import { FileId } from '@internxt/sdk/dist/photos'; +import { Platform } from 'react-native'; +import { NetworkCredentials } from '../../types'; +import fileSystemService from '../FileSystemService'; type FileDecryptedURI = string; diff --git a/src/services/common/filesystem/fileCacheManager.spec.ts b/src/services/common/filesystem/fileCacheManager.spec.ts index ea5f26883..d59e16455 100644 --- a/src/services/common/filesystem/fileCacheManager.spec.ts +++ b/src/services/common/filesystem/fileCacheManager.spec.ts @@ -1,5 +1,5 @@ +import { ReadDirResItemT } from '@dr.pogodin/react-native-fs'; import fileSystemService from '@internxt-mobile/services/FileSystemService'; -import { ReadDirItem } from 'react-native-fs'; import { FileCacheManagerConfigError, FileDoesntExistsError } from './errors'; import { FileCacheManager, FileCacheManagerConfig } from './fileCacheManager'; @@ -132,7 +132,7 @@ describe('File Cache Manager', () => { it('Should remove files in the directory by the oldest mtime if not enough space', async () => { // Total 90MB - const itemsInDir: ReadDirItem[] = [ + const itemsInDir: ReadDirResItemT[] = [ { name: 'file_1.png', ctime: new Date('2022/12/01'), diff --git a/src/services/common/logger/logger.service.ts b/src/services/common/logger/logger.service.ts index f6ec489ac..d30102898 100644 --- a/src/services/common/logger/logger.service.ts +++ b/src/services/common/logger/logger.service.ts @@ -1,5 +1,6 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; import { logger as RNLogger, consoleTransport, fileAsyncTransport } from 'react-native-logs'; -import RNFS from 'react-native-fs'; + import { fs } from '@internxt-mobile/services/FileSystemService'; import { InteractionManager } from 'react-native'; diff --git a/src/services/common/media/image.service.ts b/src/services/common/media/image.service.ts index 9b90a7d4c..e02d28707 100644 --- a/src/services/common/media/image.service.ts +++ b/src/services/common/media/image.service.ts @@ -3,7 +3,8 @@ import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'; import fileSystemService, { fs } from '@internxt-mobile/services/FileSystemService'; import { FileExtension } from '@internxt-mobile/types/drive'; -import RNFS from 'react-native-fs'; +import * as RNFS from '@dr.pogodin/react-native-fs'; + import PdfThumbnail from 'react-native-pdf-thumbnail'; import uuid from 'react-native-uuid'; import RNFetchBlob from 'rn-fetch-blob'; diff --git a/src/services/drive/constants.ts b/src/services/drive/constants.ts index a8a2f3283..c5c7c71ae 100644 --- a/src/services/drive/constants.ts +++ b/src/services/drive/constants.ts @@ -1,4 +1,5 @@ -import RNFS from 'react-native-fs'; +import * as RNFS from '@dr.pogodin/react-native-fs'; + export const DRIVE_ROOT_DIRECTORY = `${RNFS.DocumentDirectoryPath}/drive`; export const DRIVE_THUMBNAILS_DIRECTORY = `${DRIVE_ROOT_DIRECTORY}/thumbnails`; export const DRIVE_CACHE_DIRECTORY = `${DRIVE_ROOT_DIRECTORY}/cache`; diff --git a/src/services/drive/events/driveEvents.ts b/src/services/drive/events/driveEvents.ts index 714a5d8ad..2d74d5709 100644 --- a/src/services/drive/events/driveEvents.ts +++ b/src/services/drive/events/driveEvents.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import * as RNFS from '@dr.pogodin/react-native-fs'; import EventEmitter from 'events'; -import RNFS from 'react-native-fs'; import { Abortable } from '../../../types'; import { DriveEventKey } from '../../../types/drive'; diff --git a/tsconfig.json b/tsconfig.json index 31823ac13..c54b8f53e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "strict": true, "baseUrl": ".", + "jsx": "react-jsx", "paths": { "@internxt-mobile/ui-kit": ["src/components/ui-kit/index.ts"], "@internxt-mobile/hooks/*": ["src/hooks/*"], diff --git a/yarn.lock b/yarn.lock index 3c927cf83..b3e1cc436 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1279,6 +1279,13 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@dr.pogodin/react-native-fs@2.22.0": + version "2.22.0" + resolved "https://registry.yarnpkg.com/@dr.pogodin/react-native-fs/-/react-native-fs-2.22.0.tgz#0d74c10244fdf644a903d1dba15e9f2f1a5509e9" + integrity sha512-ZgStaga81J0LwR3p/CM+SxjiOm1SYShQqB8n8aAvomhicQ6p2+VSwttobTlhtaHgPJW6YUe5Fa0yPIJSZnUPyg== + dependencies: + buffer "^6.0.3" + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" @@ -10741,14 +10748,6 @@ react-native-file-viewer@^2.1.4: resolved "https://registry.yarnpkg.com/react-native-file-viewer/-/react-native-file-viewer-2.1.5.tgz#cd4544f573108e79002b5c7e1ebfce4371885250" integrity sha512-MGC6sx9jsqHdefhVQ6o0akdsPGpkXgiIbpygb2Sg4g4bh7v6K1cardLV1NwGB9A6u1yICOSDT/MOC//9Ez6EUg== -react-native-fs@^2.16.6: - version "2.20.0" - resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.20.0.tgz#05a9362b473bfc0910772c0acbb73a78dbc810f6" - integrity sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ== - dependencies: - base-64 "^0.1.0" - utf8 "^3.0.0" - react-native-gesture-handler@~2.14.0: version "2.14.1" resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz#930640231024b7921435ab476aa501dd4a6b2e01" @@ -13130,11 +13129,6 @@ utf8-byte-length@^1.0.1: resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== -utf8@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From 26d949cc804399d64643888a4fcabe559aa22a44 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Thu, 21 Nov 2024 12:06:37 +0100 Subject: [PATCH 09/29] Updated react native fs library and restored video thumbnail creation --- ios/Internxt.xcodeproj/project.pbxproj | 2 + ios/Podfile.lock | 10 ++++- package.json | 3 +- src/services/common/media/image.service.ts | 45 ++++++++++------------ yarn.lock | 13 +++++-- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/ios/Internxt.xcodeproj/project.pbxproj b/ios/Internxt.xcodeproj/project.pbxproj index b74e9dc32..3b1951d7f 100644 --- a/ios/Internxt.xcodeproj/project.pbxproj +++ b/ios/Internxt.xcodeproj/project.pbxproj @@ -280,6 +280,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Rudder/Rudder.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/dr-pogodin-react-native-fs/RNFS_PrivacyInfo.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( @@ -293,6 +294,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReachabilitySwift.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Rudder.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNFS_PrivacyInfo.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f2561bed1..155b4c776 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,7 +1,7 @@ PODS: - boost (1.83.0) - DoubleConversion (1.1.6) - - dr-pogodin-react-native-fs (2.22.0): + - dr-pogodin-react-native-fs (2.24.6): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -942,6 +942,8 @@ PODS: - React-Mapbuffer (0.73.6): - glog - React-debug + - react-native-create-thumbnail (2.0.0): + - React-Core - react-native-document-picker (4.3.0): - React-Core - react-native-image-picker (7.1.2): @@ -1251,6 +1253,7 @@ DEPENDENCIES: - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - react-native-create-thumbnail (from `../node_modules/react-native-create-thumbnail`) - react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-pdf (from `../node_modules/react-native-pdf`) @@ -1418,6 +1421,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: :path: "../node_modules/react-native/ReactCommon" + react-native-create-thumbnail: + :path: "../node_modules/react-native-create-thumbnail" react-native-document-picker: :path: "../node_modules/react-native-document-picker" react-native-image-picker: @@ -1514,7 +1519,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 - dr-pogodin-react-native-fs: c2b2640a9d71537c93620463030c8fafb59170be + dr-pogodin-react-native-fs: ef429362460ab16ade0132464d024d40baf2c2dc EASClient: a42ee8bf36c93b3128352faf2ae49405ab4f80bd EXConstants: a5f6276e565d98f9eb4280f81241fc342d641590 EXFont: f20669cb266ef48b004f1eb1f2b20db96cd1df9f @@ -1569,6 +1574,7 @@ SPEC CHECKSUMS: React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066 React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab + react-native-create-thumbnail: ab55d24aea01723cf386f18b0b542aabb1982f27 react-native-document-picker: 20f652c2402d3ddc81f396d8167c3bd978add4a2 react-native-image-picker: d3db110a3ded6e48c93aef7e8e51afdde8b16ed0 react-native-pdf: 103940c90d62adfd259f63cca99c7c0c306b514c diff --git a/package.json b/package.json index f5a30e495..e2030616b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@burstware/react-native-portal": "^1.0.2", - "@dr.pogodin/react-native-fs": "2.22.0", + "@dr.pogodin/react-native-fs": "2.24.6", "@expo/config-plugins": "7.8.4", "@expo/config-types": "^47.0.0", "@hookform/resolvers": "^2.9.1", @@ -93,6 +93,7 @@ "react-native": "0.73.6", "react-native-bip39": "^2.3.0", "react-native-collapsible": "^1.6.0", + "react-native-create-thumbnail": "^2.0.0", "react-native-crypto": "^2.2.0", "react-native-device-info": "^8.4.8", "react-native-document-picker": "^4.1.0", diff --git a/src/services/common/media/image.service.ts b/src/services/common/media/image.service.ts index e02d28707..140245961 100644 --- a/src/services/common/media/image.service.ts +++ b/src/services/common/media/image.service.ts @@ -5,6 +5,7 @@ import { FileExtension } from '@internxt-mobile/types/drive'; import * as RNFS from '@dr.pogodin/react-native-fs'; +import { createThumbnail } from 'react-native-create-thumbnail'; import PdfThumbnail from 'react-native-pdf-thumbnail'; import uuid from 'react-native-uuid'; import RNFetchBlob from 'rn-fetch-blob'; @@ -25,20 +26,15 @@ export type ThumbnailGenerateConfig = { height?: number; }; -// added this to omit video extension types, the package that produces the video thumbnail is -// causing compilation errors, leave it to solve in other task -type OmittedExtensions = 'avi' | 'mp4' | 'mov'; -type IncludedFileExtension = Exclude; - class ImageService { private get thumbnailGenerators(): Record< - IncludedFileExtension, + FileExtension, (filePath: string, config: ThumbnailGenerateConfig) => Promise > { return { - // [FileExtension.AVI]: this.generateVideoThumbnail, - // [FileExtension.MP4]: this.generateVideoThumbnail, - // [FileExtension.MOV]: this.generateVideoThumbnail, + [FileExtension.AVI]: this.generateVideoThumbnail, + [FileExtension.MP4]: this.generateVideoThumbnail, + [FileExtension.MOV]: this.generateVideoThumbnail, [FileExtension.JPEG]: this.generateImageThumbnail, [FileExtension.JPG]: this.generateImageThumbnail, [FileExtension.PNG]: this.generateImageThumbnail, @@ -155,7 +151,7 @@ class ImageService { filePath: string, config: { outputPath: string; quality?: number; extension: string; thumbnailFormat: SaveFormat }, ): Promise { - const generator = this.thumbnailGenerators[config.extension.toLowerCase() as IncludedFileExtension]; + const generator = this.thumbnailGenerators[config.extension.toLowerCase() as FileExtension]; if (!generator) { // eslint-disable-next-line no-console @@ -166,24 +162,23 @@ class ImageService { return this.resizeThumbnail(await generator(filePath, config)); } - // TODO: FIND A WAY TO GENERATE VIDEO THUMBNAILS /** * Generates a thumbnail for a video file */ - // public generateVideoThumbnail = async (filePath: string): Promise => { - // const result = await createThumbnail({ - // url: fileSystemService.pathToUri(filePath), - // dirSize: 100, - // }); - - // return { - // size: result.size, - // type: 'JPEG', - // width: result.width, - // height: result.height, - // path: result.path, - // }; - // }; + public generateVideoThumbnail = async (filePath: string): Promise => { + const result = await createThumbnail({ + url: fileSystemService.pathToUri(filePath), + dirSize: 100, + }); + + return { + size: result.size, + type: 'JPEG', + width: result.width, + height: result.height, + path: result.path, + }; + }; /** * Generates a thumbnail for an image diff --git a/yarn.lock b/yarn.lock index b3e1cc436..4a2bbf3f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1279,10 +1279,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@dr.pogodin/react-native-fs@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@dr.pogodin/react-native-fs/-/react-native-fs-2.22.0.tgz#0d74c10244fdf644a903d1dba15e9f2f1a5509e9" - integrity sha512-ZgStaga81J0LwR3p/CM+SxjiOm1SYShQqB8n8aAvomhicQ6p2+VSwttobTlhtaHgPJW6YUe5Fa0yPIJSZnUPyg== +"@dr.pogodin/react-native-fs@2.24.6": + version "2.24.6" + resolved "https://registry.yarnpkg.com/@dr.pogodin/react-native-fs/-/react-native-fs-2.24.6.tgz#a673628d0b4b26f28cab211af98a9e83c12ae90b" + integrity sha512-DMEL/FLf+LrRnegBDbLHDRCAv/flhrZwbSOzHmwGPjAd8gF6Bn48z8gFmrwlxcAtR0rxo1K1L8gWzddyR/LV8A== dependencies: buffer "^6.0.3" @@ -10712,6 +10712,11 @@ react-native-collapsible@^1.6.0: resolved "https://registry.yarnpkg.com/react-native-collapsible/-/react-native-collapsible-1.6.2.tgz#3b67fa402a6ba3c291022f5db8f345083862c3d8" integrity sha512-MCOBVJWqHNjnDaGkvxX997VONmJeebh6wyJxnHEgg0L1PrlcXU1e/bo6eK+CDVFuMrCafw8Qh4DOv/C4V/+Iew== +react-native-create-thumbnail@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-create-thumbnail/-/react-native-create-thumbnail-2.0.0.tgz#4eae9b4198cd484a1420608b53ce5872209b9a25" + integrity sha512-1GA0PHGlhSirKUqbtlLLk++3Wkd/fo8H8YUi71Lp5Y2z+AxZMKmY1/IcB9ooDiMzI1Pxr006Kq99JWuXowoykg== + react-native-crypto@^2.0.1, react-native-crypto@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/react-native-crypto/-/react-native-crypto-2.2.0.tgz#c999ed7c96064f830e1f958687f53d0c44025770" From 2a0d00e7213493b1410f764b8e38e43b61c8df2d Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Wed, 4 Dec 2024 11:21:32 +0100 Subject: [PATCH 10/29] Update project version to new release and added logs to register possible errors --- android/app/build.gradle | 2 +- android/app/src/main/res/values/strings.xml | 2 +- ios/Internxt.xcodeproj/project.pbxproj | 4 +-- ios/Internxt/Info.plist | 2 +- ios/Internxt/Supporting/Expo.plist | 2 +- package.json | 2 +- src/components/modals/AddModal/index.tsx | 12 ++++++-- .../DrivePreviewScreen/DrivePreviewScreen.tsx | 14 ++++----- src/store/slices/drive/index.ts | 30 ++++++++++--------- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f89304bbe..671d0d7e5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -89,7 +89,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 95 - versionName "1.5.38" + versionName "1.6.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) } diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index ff9c51baf..55cf690b4 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -2,5 +2,5 @@ Internxt cover false - 1.5.38 + 1.6.0 \ No newline at end of file diff --git a/ios/Internxt.xcodeproj/project.pbxproj b/ios/Internxt.xcodeproj/project.pbxproj index 3b1951d7f..a5b382c70 100644 --- a/ios/Internxt.xcodeproj/project.pbxproj +++ b/ios/Internxt.xcodeproj/project.pbxproj @@ -345,7 +345,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.internxt.snacks; - PRODUCT_NAME = Internxt; + PRODUCT_NAME = "Internxt"; SWIFT_OBJC_BRIDGING_HEADER = "Internxt/Internxt-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -376,7 +376,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.internxt.snacks; - PRODUCT_NAME = Internxt; + PRODUCT_NAME = "Internxt"; SWIFT_OBJC_BRIDGING_HEADER = "Internxt/Internxt-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/Internxt/Info.plist b/ios/Internxt/Info.plist index ce8ab22ab..fe9b133e1 100644 --- a/ios/Internxt/Info.plist +++ b/ios/Internxt/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.5.38 + 1.6.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/Internxt/Supporting/Expo.plist b/ios/Internxt/Supporting/Expo.plist index 94c4a058b..1ed739e0e 100644 --- a/ios/Internxt/Supporting/Expo.plist +++ b/ios/Internxt/Supporting/Expo.plist @@ -9,7 +9,7 @@ EXUpdatesLaunchWaitMs 0 EXUpdatesRuntimeVersion - 1.5.38 + 1.6.0 EXUpdatesURL https://u.expo.dev/680f4feb-6315-4a50-93ec-36dcd0b831d2 diff --git a/package.json b/package.json index e2030616b..94fbf5c4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "drive-mobile", - "version": "v1.5.38", + "version": "v1.6.0", "private": true, "license": "GNU", "scripts": { diff --git a/src/components/modals/AddModal/index.tsx b/src/components/modals/AddModal/index.tsx index 5057cdd3e..92da3e5e2 100644 --- a/src/components/modals/AddModal/index.tsx +++ b/src/components/modals/AddModal/index.tsx @@ -450,6 +450,7 @@ function AddModal(): JSX.Element { } errorService.reportError(error); + logger.error('Error on handleUploadFiles function:', JSON.stringify(err)); notificationsService.show({ type: NotificationType.Error, @@ -506,6 +507,8 @@ function AddModal(): JSX.Element { 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, @@ -535,6 +538,9 @@ function AddModal(): JSX.Element { if (err.message === 'User canceled document picker') { return; } + + logger.error('Error on hadleUploadFromCameraRoll function:', JSON.stringify(err)); + notificationsService.show({ type: NotificationType.Error, text1: strings.formatString(strings.errors.uploadFile, err.message) as string, @@ -601,10 +607,12 @@ function AddModal(): JSX.Element { } } } - } catch (err) { + } catch (error) { + logger.error('Error on handleTakePhotoAndUpload function:', JSON.stringify(error)); + notificationsService.show({ type: NotificationType.Error, - text1: strings.formatString(strings.errors.uploadFile, (err as Error).message) as string, + text1: strings.formatString(strings.errors.uploadFile, (error as Error)?.message) as string, }); } } diff --git a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx index 1a5e42db1..2d7032fc3 100644 --- a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx +++ b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx @@ -1,4 +1,4 @@ -import { GeneratedThumbnail } from '@internxt-mobile/services/common'; +import { GeneratedThumbnail, imageService } from '@internxt-mobile/services/common'; import { time } from '@internxt-mobile/services/common/time'; import errorService from '@internxt-mobile/services/ErrorService'; import { fs } from '@internxt-mobile/services/FileSystemService'; @@ -51,12 +51,12 @@ export const DrivePreviewScreen: React.FC> VIDEO_PREVIEW_TYPES.includes(downloadingFile.data.type as FileExtension) && !generatedThumbnail ) { - // imageService - // .generateVideoThumbnail(downloadingFile.downloadedFilePath) - // .then((generatedThumbnail) => { - // setGeneratedThumbnail(generatedThumbnail); - // }) - // .catch((err) => errorService.reportError(err)); + imageService + .generateVideoThumbnail(downloadingFile.downloadedFilePath) + .then((generatedThumbnail) => { + setGeneratedThumbnail(generatedThumbnail); + }) + .catch((err) => errorService.reportError(err)); } }, [downloadingFile?.downloadedFilePath]); diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts index c3ba1f6e9..5b0ab7e5c 100644 --- a/src/store/slices/drive/index.ts +++ b/src/store/slices/drive/index.ts @@ -1,31 +1,31 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { DriveFileData, DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import analytics, { DriveAnalyticsEvent } from '../../../services/AnalyticsService'; -import { NotificationType } from '../../../types'; +import { logger } from '@internxt-mobile/services/common'; +import drive from '@internxt-mobile/services/drive'; +import { items } from '@internxt/lib'; +import { isValidFilename } from 'src/helpers'; +import authService from 'src/services/AuthService'; +import errorService from 'src/services/ErrorService'; +import { ErrorCodes } from 'src/types/errors'; import { RootState } from '../..'; import strings from '../../../../assets/lang/strings'; +import fileSystemService from '../../../services/FileSystemService'; import notificationsService from '../../../services/NotificationsService'; +import { NotificationType } from '../../../types'; import { + DownloadingFile, + DriveEventKey, DriveItemData, + DriveItemFocused, DriveItemStatus, DriveListItem, - UploadingFile, - DownloadingFile, - DriveEventKey, DriveNavigationStack, DriveNavigationStackItem, - DriveItemFocused, + UploadingFile, } from '../../../types/drive'; -import fileSystemService from '../../../services/FileSystemService'; -import { items } from '@internxt/lib'; -import drive from '@internxt-mobile/services/drive'; -import authService from 'src/services/AuthService'; -import errorService from 'src/services/ErrorService'; -import { ErrorCodes } from 'src/types/errors'; -import { isValidFilename } from 'src/helpers'; -import { logger } from '@internxt-mobile/services/common'; export enum ThunkOperationStatus { SUCCESS = 'SUCCESS', @@ -231,6 +231,7 @@ const downloadFileThunk = createAsyncThunk< if (!fileAlreadyExists) { trackDownloadStart(); downloadProgressCallback(0); + await download({ fileId, to: destinationPath }); } @@ -242,6 +243,7 @@ const downloadFileThunk = createAsyncThunk< trackDownloadSuccess(); } catch (err) { + logger.error('downloadFileThunk ', JSON.stringify(err)); dispatch(driveActions.updateDownloadingFile({ error: (err as Error).message })); /** * In case something fails, we remove the file in case it exists, that way From 16a230cb9b86d66912c1f50f9210115255b1a43e Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Mon, 9 Dec 2024 07:58:32 +0100 Subject: [PATCH 11/29] Changed error log message --- src/store/slices/drive/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts index 5b0ab7e5c..22ead9609 100644 --- a/src/store/slices/drive/index.ts +++ b/src/store/slices/drive/index.ts @@ -243,7 +243,7 @@ const downloadFileThunk = createAsyncThunk< trackDownloadSuccess(); } catch (err) { - logger.error('downloadFileThunk ', JSON.stringify(err)); + logger.error('Error in downloadFileThunk ', JSON.stringify(err)); dispatch(driveActions.updateDownloadingFile({ error: (err as Error).message })); /** * In case something fails, we remove the file in case it exists, that way From de35eda08e110771ed2868e5654f50cb26343be2 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Thu, 12 Dec 2024 10:35:12 +0100 Subject: [PATCH 12/29] Updated android version code --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 671d0d7e5..32d3284a9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,7 +88,7 @@ android { applicationId 'com.internxt.cloud' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 95 + versionCode 96 versionName "1.6.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) From 4216c168d6a9ff647fd1f1ddb53a1f820d390a6d Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 13 Dec 2024 14:32:57 +0100 Subject: [PATCH 13/29] Fixed iPhone 15 download error related to react-native-fs library --- ios/Internxt.xcodeproj/project.pbxproj | 4 +- ios/Podfile.lock | 4 +- package.json | 2 +- .../modals/DriveItemInfoModal/index.tsx | 62 ++++++++++--------- src/services/drive/file/driveFile.service.ts | 18 +++--- yarn.lock | 14 +++-- 6 files changed, 57 insertions(+), 47 deletions(-) diff --git a/ios/Internxt.xcodeproj/project.pbxproj b/ios/Internxt.xcodeproj/project.pbxproj index a5b382c70..3b1951d7f 100644 --- a/ios/Internxt.xcodeproj/project.pbxproj +++ b/ios/Internxt.xcodeproj/project.pbxproj @@ -345,7 +345,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.internxt.snacks; - PRODUCT_NAME = "Internxt"; + PRODUCT_NAME = Internxt; SWIFT_OBJC_BRIDGING_HEADER = "Internxt/Internxt-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -376,7 +376,7 @@ ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.internxt.snacks; - PRODUCT_NAME = "Internxt"; + PRODUCT_NAME = Internxt; SWIFT_OBJC_BRIDGING_HEADER = "Internxt/Internxt-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 155b4c776..111e9d4df 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,7 +1,7 @@ PODS: - boost (1.83.0) - DoubleConversion (1.1.6) - - dr-pogodin-react-native-fs (2.24.6): + - dr-pogodin-react-native-fs (2.27.0): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1519,7 +1519,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 - dr-pogodin-react-native-fs: ef429362460ab16ade0132464d024d40baf2c2dc + dr-pogodin-react-native-fs: 3a0fa8611f662cafd0059aede51c56099deada1e EASClient: a42ee8bf36c93b3128352faf2ae49405ab4f80bd EXConstants: a5f6276e565d98f9eb4280f81241fc342d641590 EXFont: f20669cb266ef48b004f1eb1f2b20db96cd1df9f diff --git a/package.json b/package.json index 94fbf5c4b..4976f4478 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@burstware/react-native-portal": "^1.0.2", - "@dr.pogodin/react-native-fs": "2.24.6", + "@dr.pogodin/react-native-fs": "2.27.0", "@expo/config-plugins": "7.8.4", "@expo/config-types": "^47.0.0", "@hookform/resolvers": "^2.9.1", diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index 31dfc8541..e3cd3b01c 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -1,43 +1,43 @@ import prettysize from 'prettysize'; -import React, { useRef, useState } from 'react'; -import { PermissionsAndroid, Platform, TouchableOpacity, View } from 'react-native'; +import { useRef, useState } from 'react'; +import { Platform, TouchableOpacity, View } from 'react-native'; -import strings from '../../../../assets/lang/strings'; -import { FolderIcon, getFileTypeIcon } from '../../../helpers'; -import globalStyle from '../../../styles/global'; -import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { uiActions } from '../../../store/slices/ui'; -import { driveActions } from '../../../store/slices/drive'; -import BottomModalOption from '../../BottomModalOption'; -import BottomModal from '../BottomModal'; +import Portal from '@burstware/react-native-portal'; +import { useDrive } from '@internxt-mobile/hooks/drive'; +import AuthService from '@internxt-mobile/services/AuthService'; +import errorService from '@internxt-mobile/services/ErrorService'; +import { fs } from '@internxt-mobile/services/FileSystemService'; +import { notifications } from '@internxt-mobile/services/NotificationsService'; +import { logger } from '@internxt-mobile/services/common'; +import { time } from '@internxt-mobile/services/common/time'; +import drive from '@internxt-mobile/services/drive'; +import { driveLocalDB } from '@internxt-mobile/services/drive/database'; +import { Abortable } from '@internxt-mobile/types/index'; +import * as driveUseCases from '@internxt-mobile/useCases/drive'; import { - Link, - Trash, ArrowsOutCardinal, - Eye, ArrowSquareOut, DownloadSimple, + Eye, + Link, PencilSimple, + Trash, } from 'phosphor-react-native'; +import AppProgressBar from 'src/components/AppProgressBar'; +import AppText from 'src/components/AppText'; +import { SLEEP_BECAUSE_MAYBE_BACKEND_IS_NOT_RETURNING_FRESHLY_MODIFIED_OR_CREATED_ITEMS_YET } from 'src/helpers/services'; import { useTailwind } from 'tailwind-rn'; +import strings from '../../../../assets/lang/strings'; +import { FolderIcon, getFileTypeIcon } from '../../../helpers'; import useGetColor from '../../../hooks/useColor'; -import { time } from '@internxt-mobile/services/common/time'; -import AppText from 'src/components/AppText'; -import { SharedLinkSettingsModal } from '../SharedLinkSettingsModal'; -import * as driveUseCases from '@internxt-mobile/useCases/drive'; -import { useDrive } from '@internxt-mobile/hooks/drive'; -import { driveLocalDB } from '@internxt-mobile/services/drive/database'; -import { DriveItemData } from '@internxt-mobile/types/drive'; -import Portal from '@burstware/react-native-portal'; -import { fs } from '@internxt-mobile/services/FileSystemService'; -import errorService from '@internxt-mobile/services/ErrorService'; -import drive from '@internxt-mobile/services/drive'; -import AuthService from '@internxt-mobile/services/AuthService'; -import { notifications } from '@internxt-mobile/services/NotificationsService'; -import { Abortable } from '@internxt-mobile/types/index'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { driveActions } from '../../../store/slices/drive'; +import { uiActions } from '../../../store/slices/ui'; +import globalStyle from '../../../styles/global'; +import BottomModalOption from '../../BottomModalOption'; +import BottomModal from '../BottomModal'; import CenterModal from '../CenterModal'; -import AppProgressBar from 'src/components/AppProgressBar'; -import { SLEEP_BECAUSE_MAYBE_BACKEND_IS_NOT_RETURNING_FRESHLY_MODIFIED_OR_CREATED_ITEMS_YET } from 'src/helpers/services'; +import { SharedLinkSettingsModal } from '../SharedLinkSettingsModal'; function DriveItemInfoModal(): JSX.Element { const tailwind = useTailwind(); @@ -135,6 +135,7 @@ function DriveItemInfoModal(): JSX.Element { return downloadPath; }; + const handleExportFile = async () => { try { if (!item.fileId) { @@ -162,6 +163,7 @@ function DriveItemInfoModal(): JSX.Element { }); } catch (error) { notifications.error(strings.errors.generic.message); + logger.error('Error on handleExportFile function:', JSON.stringify(error)); errorService.reportError(error); } finally { setExporting(false); @@ -200,6 +202,7 @@ function DriveItemInfoModal(): JSX.Element { notifications.success(strings.messages.driveDownloadSuccess); } catch (error) { notifications.error(strings.errors.generic.message); + logger.error('Error on handleAndroidDownloadFile function:', JSON.stringify(error)); errorService.reportError(error); } finally { setExporting(false); @@ -235,6 +238,7 @@ function DriveItemInfoModal(): JSX.Element { }); } catch (error) { notifications.error(strings.errors.generic.message); + logger.error('Error on handleiOSSaveToFiles function:', JSON.stringify(error)); errorService.reportError(error); } finally { setExporting(false); diff --git a/src/services/drive/file/driveFile.service.ts b/src/services/drive/file/driveFile.service.ts index efe1a450d..ae6db76ba 100644 --- a/src/services/drive/file/driveFile.service.ts +++ b/src/services/drive/file/driveFile.service.ts @@ -1,6 +1,7 @@ -import { createHash } from 'crypto'; import axios from 'axios'; +import { createHash } from 'crypto'; +import { getHeaders } from '../../../helpers/headers'; import { DownloadedThumbnail, DriveFileMetadataPayload, @@ -10,20 +11,19 @@ import { SortDirection, SortType, } from '../../../types/drive'; -import { getHeaders } from '../../../helpers/headers'; import { constants } from '../../AppService'; +import asyncStorageService from '@internxt-mobile/services/AsyncStorageService'; import { SdkManager } from '@internxt-mobile/services/common'; -import { MoveFileResponse, Thumbnail } from '@internxt/sdk/dist/drive/storage/types'; -import { getEnvironmentConfig } from 'src/lib/network'; -import { DRIVE_THUMBNAILS_DIRECTORY } from '../constants'; import fileSystemService, { fs } from '@internxt-mobile/services/FileSystemService'; -import * as networkDownload from 'src/network/download'; -import { Image } from 'react-native'; +import { Abortable, AsyncStorageKey } from '@internxt-mobile/types/index'; +import { MoveFileResponse } from '@internxt/sdk/dist/drive/storage/types'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; +import { Image } from 'react-native'; +import { getEnvironmentConfig } from 'src/lib/network'; +import * as networkDownload from 'src/network/download'; +import { DRIVE_THUMBNAILS_DIRECTORY } from '../constants'; import { driveFileCache } from './driveFileCache.service'; -import { Abortable, AsyncStorageKey } from '@internxt-mobile/types/index'; -import asyncStorageService from '@internxt-mobile/services/AsyncStorageService'; export type ArraySortFunction = (a: DriveListItem, b: DriveListItem) => number; export type DriveFileDownloadOptions = { diff --git a/yarn.lock b/yarn.lock index 4a2bbf3f1..db712eeb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1279,12 +1279,13 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@dr.pogodin/react-native-fs@2.24.6": - version "2.24.6" - resolved "https://registry.yarnpkg.com/@dr.pogodin/react-native-fs/-/react-native-fs-2.24.6.tgz#a673628d0b4b26f28cab211af98a9e83c12ae90b" - integrity sha512-DMEL/FLf+LrRnegBDbLHDRCAv/flhrZwbSOzHmwGPjAd8gF6Bn48z8gFmrwlxcAtR0rxo1K1L8gWzddyR/LV8A== +"@dr.pogodin/react-native-fs@2.27.0": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@dr.pogodin/react-native-fs/-/react-native-fs-2.27.0.tgz#2545a3414b24282aa3b311b97f7e5f9a238916f3" + integrity sha512-1wxXI0Y1LCfUhl5S1p3HxLVvOAS4ooc83KF5uc5gIfgk24JeSYyHbeKDpSwJrZhN5uFtsC+MZG3Gyq8nBV5QFQ== dependencies: buffer "^6.0.3" + http-status-codes "^2.3.0" "@egjs/hammerjs@^2.0.17": version "2.0.17" @@ -7434,6 +7435,11 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-status-codes@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.3.0.tgz#987fefb28c69f92a43aecc77feec2866349a8bfc" + integrity sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA== + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" From 9d8b616505479cb3c3db870e75d89f62b082e859 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 13 Dec 2024 14:33:49 +0100 Subject: [PATCH 14/29] Fixed Android cursor positioning when delete selected text in TextInput --- src/components/AppTextInput/index.tsx | 10 ++++++---- src/components/modals/DriveRenameModal/index.tsx | 7 +++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/AppTextInput/index.tsx b/src/components/AppTextInput/index.tsx index a199fc356..ef9d4caf7 100644 --- a/src/components/AppTextInput/index.tsx +++ b/src/components/AppTextInput/index.tsx @@ -57,14 +57,16 @@ const AppTextInput = (props: AppTextInputProps): JSX.Element => { let newPosition: number; const hasNothingSelected = selection.start === selection.end; + const selectionStart = Math.min(selection.start, selection.end); + if (hasNothingSelected) { newPosition = selection.start + (newText.length - oldText.length); - setSelection({ start: newPosition, end: newPosition }); } else { - const selectionStart = Math.min(selection.start, selection.end); - newPosition = selectionStart + newText.length - oldText.slice(selection.start, selection.end).length; - setSelection({ start: newPosition, end: newPosition }); + newPosition = selectionStart; } + newPosition = Math.max(0, newPosition); + + setSelection({ start: newPosition, end: newPosition }); setTimeout(() => { if (localInputRef.current) { diff --git a/src/components/modals/DriveRenameModal/index.tsx b/src/components/modals/DriveRenameModal/index.tsx index 084b33a16..3d80bfbab 100644 --- a/src/components/modals/DriveRenameModal/index.tsx +++ b/src/components/modals/DriveRenameModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { View } from 'react-native'; import Portal from '@burstware/react-native-portal'; @@ -9,7 +9,6 @@ import AppText from 'src/components/AppText'; import AppTextInput from 'src/components/AppTextInput'; import { useTailwind } from 'tailwind-rn'; import strings from '../../../../assets/lang/strings'; -import { FolderIcon, getFileTypeIcon } from '../../../helpers'; import useGetColor from '../../../hooks/useColor'; import errorService from '../../../services/ErrorService'; import notificationsService from '../../../services/NotificationsService'; @@ -19,6 +18,7 @@ import { uiActions } from '../../../store/slices/ui'; import { NotificationType } from '../../../types'; import AppButton from '../../AppButton'; import CenterModal from '../CenterModal'; + function RenameModal(): JSX.Element { const tailwind = useTailwind(); const getColor = useGetColor(); @@ -103,8 +103,7 @@ function RenameModal(): JSX.Element { onItemRenameFinally(); } }; - const IconFile = getFileTypeIcon(focusedItem?.type || ''); - const IconFolder = FolderIcon; + const onClosed = () => { dispatch(uiActions.setShowRenameModal(false)); setNewName(''); From f7b5f85a0d19071ef8358b9f1b47137ee642672e Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 13 Dec 2024 14:34:40 +0100 Subject: [PATCH 15/29] Updated android gradle versionCode field --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 32d3284a9..fbd5e749e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,7 +88,7 @@ android { applicationId 'com.internxt.cloud' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 96 + versionCode 97 versionName "1.6.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) From 08e313293498bf46fbffc8af13323504a82ff308 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 17 Dec 2024 12:18:14 +0100 Subject: [PATCH 16/29] Limited download file size to 3GB and added folder flag to identify folders --- assets/lang/strings.ts | 2 ++ ios/Internxt/Info.plist | 2 +- .../modals/DriveItemInfoModal/index.tsx | 27 +++++++++++++++++-- src/contexts/Drive/Drive.context.tsx | 8 ++---- .../DriveFolderScreen/DriveFolderScreen.tsx | 4 +-- .../DrivePreviewScreen/DrivePreviewScreen.tsx | 20 +++++++------- src/services/drive/constants.ts | 4 +++ src/store/slices/drive/index.ts | 20 +++++++++++++- 8 files changed, 66 insertions(+), 21 deletions(-) diff --git a/assets/lang/strings.ts b/assets/lang/strings.ts index 9e3b935eb..3ebe0d473 100644 --- a/assets/lang/strings.ts +++ b/assets/lang/strings.ts @@ -647,6 +647,7 @@ const strings = new LocalizedStrings({ confirmDeleteSharedLink: 'Users with the link will lose access to the shared content.', linkDeleted: 'Link deleted successfully', trashEmpty: 'Trash is empty', + downloadLimit: 'The download limit in mobile app is 3GB.', }, errors: { runtimeLogsMissing: 'The logs file is missing or empty', @@ -1345,6 +1346,7 @@ const strings = new LocalizedStrings({ confirmDeleteSharedLink: 'Los usuarios con el link compartido perderán el acceso a este contenido compartido.', linkDeleted: 'Link eliminado correctamente', trashEmpty: 'Papelera vaciada', + downloadLimit: 'El límite de descarga en la app movil son 3GB', }, errors: { runtimeLogsMissing: 'El archivo no se encuentra o está vacío', diff --git a/ios/Internxt/Info.plist b/ios/Internxt/Info.plist index fe9b133e1..afbe342b5 100644 --- a/ios/Internxt/Info.plist +++ b/ios/Internxt/Info.plist @@ -33,7 +33,7 @@ CFBundleVersion - 5 + 11 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index e3cd3b01c..6c7eb56eb 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -7,7 +7,7 @@ import { useDrive } from '@internxt-mobile/hooks/drive'; import AuthService from '@internxt-mobile/services/AuthService'; import errorService from '@internxt-mobile/services/ErrorService'; import { fs } from '@internxt-mobile/services/FileSystemService'; -import { notifications } from '@internxt-mobile/services/NotificationsService'; +import notificationsService, { notifications } from '@internxt-mobile/services/NotificationsService'; import { logger } from '@internxt-mobile/services/common'; import { time } from '@internxt-mobile/services/common/time'; import drive from '@internxt-mobile/services/drive'; @@ -30,6 +30,7 @@ import { useTailwind } from 'tailwind-rn'; import strings from '../../../../assets/lang/strings'; import { FolderIcon, getFileTypeIcon } from '../../../helpers'; import useGetColor from '../../../hooks/useColor'; +import { MAX_SIZE_TO_DOWNLOAD } from '../../../services/drive/constants'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { driveActions } from '../../../store/slices/drive'; import { uiActions } from '../../../store/slices/ui'; @@ -67,6 +68,14 @@ function DriveItemInfoModal(): JSX.Element { dispatch(driveActions.setItemToMove(item)); }; + const isFileDownloadable = (): boolean => { + if (parseInt(item.size?.toString() ?? '0') >= MAX_SIZE_TO_DOWNLOAD['3GB']) { + notificationsService.info(strings.messages.downloadLimit); + return false; + } + return true; + }; + const handleUndoMoveToTrash = async () => { const { success } = await driveUseCases.restoreDriveItems( [ @@ -118,7 +127,6 @@ function DriveItemInfoModal(): JSX.Element { const downloadItem = async (fileId: string, bucketId: string, decryptedFilePath: string) => { const { credentials } = await AuthService.getAuthCredentials(); - const { downloadPath } = await drive.file.downloadFile(credentials.user, bucketId, fileId, { downloadPath: decryptedFilePath, downloadProgressCallback(progress, bytesReceived, totalBytes) { @@ -141,6 +149,11 @@ function DriveItemInfoModal(): JSX.Element { if (!item.fileId) { throw new Error('Item fileID not found'); } + const canDownloadFile = isFileDownloadable(); + if (!canDownloadFile) { + dispatch(uiActions.setShowItemModal(false)); + return; + } const decryptedFilePath = drive.file.getDecryptedFilePath(item.name, item.type); const exists = await drive.file.existsDecrypted(item.name, item.type); @@ -181,6 +194,11 @@ function DriveItemInfoModal(): JSX.Element { if (!item.fileId) { throw new Error('Item fileID not found'); } + const canDownloadFile = isFileDownloadable(); + if (!canDownloadFile) { + dispatch(uiActions.setShowItemModal(false)); + return; + } const decryptedFilePath = drive.file.getDecryptedFilePath(item.name, item.type); @@ -215,6 +233,11 @@ function DriveItemInfoModal(): JSX.Element { if (!item.fileId) { throw new Error('Item fileID not found'); } + const canDownloadFile = isFileDownloadable(); + if (!canDownloadFile) { + dispatch(uiActions.setShowItemModal(false)); + return; + } const decryptedFilePath = drive.file.getDecryptedFilePath(item.name, item.type); diff --git a/src/contexts/Drive/Drive.context.tsx b/src/contexts/Drive/Drive.context.tsx index 2ac5bc661..9ef0fc78f 100644 --- a/src/contexts/Drive/Drive.context.tsx +++ b/src/contexts/Drive/Drive.context.tsx @@ -5,7 +5,6 @@ import React, { useEffect, useRef, useState } from 'react'; import appService from '@internxt-mobile/services/AppService'; import errorService from '@internxt-mobile/services/ErrorService'; -import { BaseLogger } from '@internxt-mobile/services/common'; import { AppStateStatus, NativeEventSubscription } from 'react-native'; import { driveFolderService } from '@internxt-mobile/services/drive/folder'; @@ -48,10 +47,6 @@ interface DriveContextProviderProps { children: React.ReactNode; } -const logger = new BaseLogger({ - tag: 'DRIVE_CONTEXT', -}); - const FILES_LIMIT_PER_PAGE = 50; const FOLDERS_LIMIT_PER_PAGE = 50; @@ -146,6 +141,8 @@ export const DriveContextProvider: React.FC = ({ chil userId: folder.userId, // @ts-expect-error - API is returning status, missing from SDK status: folder.status, + // @ts-expect-error - API is returning status, missing from SDK + isFolder: true, }; return driveFolder; @@ -269,7 +266,6 @@ export const DriveContextProvider: React.FC = ({ chil setViewMode(newViewMode); asyncStorageService.saveItem(AsyncStorageKey.PreferredDriveViewMode, newViewMode); }; - return ( { - const isFolder = driveItem.data.type ? false : true; + const isFolder = driveItem?.data?.isFolder; if (!isFolder) { dispatch( driveActions.setFocusedItem({ diff --git a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx index 2d7032fc3..02d03fb06 100644 --- a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx +++ b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx @@ -90,9 +90,9 @@ export const DrivePreviewScreen: React.FC> const filename = `${focusedItem.name || ''}${focusedItem.type ? `.${focusedItem.type}` : ''}`; const currentProgress = downloadingFile.downloadProgress * 0.5 + downloadingFile.decryptProgress * 0.5; const FileIcon = getFileTypeIcon(focusedItem.type || ''); - const hasImagePreview = IMAGE_PREVIEW_TYPES.includes(downloadingFile.data.type.toLowerCase() as FileExtension); - const hasVideoPreview = VIDEO_PREVIEW_TYPES.includes(downloadingFile.data.type.toLowerCase() as FileExtension); - const hasPdfPreview = PDF_PREVIEW_TYPES.includes(downloadingFile.data.type.toLowerCase() as FileExtension); + const hasImagePreview = IMAGE_PREVIEW_TYPES.includes(downloadingFile.data.type?.toLowerCase() as FileExtension); + const hasVideoPreview = VIDEO_PREVIEW_TYPES.includes(downloadingFile.data.type?.toLowerCase() as FileExtension); + const hasPdfPreview = PDF_PREVIEW_TYPES.includes(downloadingFile.data.type?.toLowerCase() as FileExtension); const getProgressMessage = () => { if (!downloadingFile) { return; @@ -152,12 +152,14 @@ export const DrivePreviewScreen: React.FC> {error} - downloadingFile.retry && downloadingFile.retry()} - > + {downloadingFile && error !== strings.messages.downloadLimit && ( + downloadingFile.retry && downloadingFile.retry()} + > + )} ) : null} {isDownloaded && downloadedFilePath ? ( diff --git a/src/services/drive/constants.ts b/src/services/drive/constants.ts index c5c7c71ae..e65f63f68 100644 --- a/src/services/drive/constants.ts +++ b/src/services/drive/constants.ts @@ -7,3 +7,7 @@ export const MAX_CACHE_DIRECTORY_SIZE_IN_BYTES = 1024 * 1024 * 500; // 10% of the directory size export const MAX_FILE_SIZE_FOR_CACHING = MAX_CACHE_DIRECTORY_SIZE_IN_BYTES * 0.1; + +export const MAX_SIZE_TO_DOWNLOAD = { + '3GB': 3221225472, +}; diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts index 22ead9609..90fb0ffe8 100644 --- a/src/store/slices/drive/index.ts +++ b/src/store/slices/drive/index.ts @@ -12,6 +12,7 @@ import errorService from 'src/services/ErrorService'; import { ErrorCodes } from 'src/types/errors'; import { RootState } from '../..'; import strings from '../../../../assets/lang/strings'; +import { MAX_SIZE_TO_DOWNLOAD } from '../../../services/drive/constants'; import fileSystemService from '../../../services/FileSystemService'; import notificationsService from '../../../services/NotificationsService'; import { NotificationType } from '../../../types'; @@ -117,6 +118,8 @@ const cancelDownloadThunk = createAsyncThunk(' drive.events.emit({ event: DriveEventKey.CancelDownload }); }); +const DOWNLOAD_ERROR_CODES = { MAX_SIZE_TO_DOWNLOAD_REACHED: 1 }; + const downloadFileThunk = createAsyncThunk< void, { @@ -139,6 +142,16 @@ const downloadFileThunk = createAsyncThunk< ) => { logger.info('Starting file download...'); const { user } = getState().auth; + + if (parseInt(size?.toString() ?? '0') >= MAX_SIZE_TO_DOWNLOAD['3GB']) { + dispatch( + driveActions.updateDownloadingFile({ + error: strings.messages.downloadLimit, + }), + ); + return rejectWithValue(DOWNLOAD_ERROR_CODES.MAX_SIZE_TO_DOWNLOAD_REACHED); + } + dispatch( driveActions.updateDownloadingFile({ retry: async () => { @@ -484,7 +497,12 @@ export const driveSlice = createSlice({ }; }) .addCase(downloadFileThunk.fulfilled, () => undefined) - .addCase(downloadFileThunk.rejected, () => undefined); + .addCase(downloadFileThunk.rejected, (_, action) => { + const errorCode = action.payload; + if (errorCode === DOWNLOAD_ERROR_CODES.MAX_SIZE_TO_DOWNLOAD_REACHED) { + notificationsService.info(strings.messages.downloadLimit); + } + }); builder .addCase(createFolderThunk.pending, (state) => { From 5a13d4aad9833a218ce1c1ca70a8fc8f515c247b Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 17 Dec 2024 12:36:00 +0100 Subject: [PATCH 17/29] Changed download limit to 3GB max instead minus than 3GB --- src/components/modals/DriveItemInfoModal/index.tsx | 2 +- src/store/slices/drive/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index 6c7eb56eb..ec53feae7 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -69,7 +69,7 @@ function DriveItemInfoModal(): JSX.Element { }; const isFileDownloadable = (): boolean => { - if (parseInt(item.size?.toString() ?? '0') >= MAX_SIZE_TO_DOWNLOAD['3GB']) { + if (parseInt(item.size?.toString() ?? '0') > MAX_SIZE_TO_DOWNLOAD['3GB']) { notificationsService.info(strings.messages.downloadLimit); return false; } diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts index 90fb0ffe8..20e5c95e7 100644 --- a/src/store/slices/drive/index.ts +++ b/src/store/slices/drive/index.ts @@ -143,7 +143,7 @@ const downloadFileThunk = createAsyncThunk< logger.info('Starting file download...'); const { user } = getState().auth; - if (parseInt(size?.toString() ?? '0') >= MAX_SIZE_TO_DOWNLOAD['3GB']) { + if (parseInt(size?.toString() ?? '0') > MAX_SIZE_TO_DOWNLOAD['3GB']) { dispatch( driveActions.updateDownloadingFile({ error: strings.messages.downloadLimit, From 3a50f08420ec0abbaf91421c91b14067b4e28cdb Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 17 Dec 2024 16:15:59 +0100 Subject: [PATCH 18/29] Test download in chunks --- .../modals/DriveItemInfoModal/index.tsx | 53 +++++++---- src/network/NetworkFacade.ts | 90 ++++++++++++------- src/network/download.ts | 4 +- src/services/drive/file/driveFile.service.ts | 3 + src/store/slices/drive/index.ts | 20 +++-- 5 files changed, 112 insertions(+), 58 deletions(-) diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index ec53feae7..e69ba7fc9 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -125,21 +125,27 @@ function DriveItemInfoModal(): JSX.Element { return; }; - const downloadItem = async (fileId: string, bucketId: string, decryptedFilePath: string) => { + const downloadItem = async (fileId: string, bucketId: string, decryptedFilePath: string, fileSize: number) => { const { credentials } = await AuthService.getAuthCredentials(); - const { downloadPath } = await drive.file.downloadFile(credentials.user, bucketId, fileId, { - downloadPath: decryptedFilePath, - downloadProgressCallback(progress, bytesReceived, totalBytes) { - setDownloadProgress({ - progress, - bytesReceived, - totalBytes, - }); - }, - onAbortableReady(abortable) { - downloadAbortableRef.current = abortable; + const { downloadPath } = await drive.file.downloadFile( + credentials.user, + bucketId, + fileId, + { + downloadPath: decryptedFilePath, + downloadProgressCallback(progress, bytesReceived, totalBytes) { + setDownloadProgress({ + progress, + bytesReceived, + totalBytes, + }); + }, + onAbortableReady(abortable) { + downloadAbortableRef.current = abortable; + }, }, - }); + fileSize, + ); return downloadPath; }; @@ -168,7 +174,12 @@ function DriveItemInfoModal(): JSX.Element { setDownloadProgress({ totalBytes: 0, progress: 0, bytesReceived: 0 }); setExporting(true); - const downloadPath = await downloadItem(item.fileId, item.bucket as string, decryptedFilePath); + const downloadPath = await downloadItem( + item.fileId, + item.bucket as string, + decryptedFilePath, + parseInt(item.size?.toString() ?? '0'), + ); setExporting(false); await fs.shareFile({ title: item.name, @@ -210,7 +221,12 @@ function DriveItemInfoModal(): JSX.Element { // 2. If the file doesn't exists, download it if (!existsDecrypted) { setExporting(true); - await downloadItem(item.fileId, item.bucket as string, decryptedFilePath); + await downloadItem( + item.fileId, + item.bucket as string, + decryptedFilePath, + parseInt(item.size?.toString() ?? '0'), + ); setExporting(false); } @@ -249,7 +265,12 @@ function DriveItemInfoModal(): JSX.Element { // 2. If the file doesn't exists, download it if (!existsDecrypted) { setExporting(true); - await downloadItem(item.fileId, item.bucket as string, decryptedFilePath); + await downloadItem( + item.fileId, + item.bucket as string, + decryptedFilePath, + parseInt(item.size?.toString() ?? '0'), + ); setExporting(false); } diff --git a/src/network/NetworkFacade.ts b/src/network/NetworkFacade.ts index ca4e4b2e8..efe3fccfd 100644 --- a/src/network/NetworkFacade.ts +++ b/src/network/NetworkFacade.ts @@ -11,7 +11,6 @@ import uuid from 'react-native-uuid'; import RNFetchBlob from 'rn-fetch-blob'; import drive from '@internxt-mobile/services/drive'; -import { driveEvents } from '@internxt-mobile/services/drive/events'; import { ripemd160 } from '../@inxt-js/lib/crypto'; import { generateFileKey } from '../lib/network'; import appService from '../services/AppService'; @@ -158,7 +157,14 @@ export class NetworkFacade { return [wrapUploadWithCleanup(), () => null]; } - download(fileId: string, bucketId: string, mnemonic: string, params: DownloadFileParams): [Promise, Abortable] { + download( + fileId: string, + bucketId: string, + mnemonic: string, + params: DownloadFileParams, + fileSize: number, + ): [Promise, Abortable] { + console.log('download'); if (!fileId) { throw new Error('Download error code 1'); } @@ -181,7 +187,7 @@ export class NetworkFacade { const encryptedFileName = `${fileId}.enc`; let encryptedFileIsCached = false; - let totalBytes = 0; + const totalBytes = 0; const downloadPromise = downloadFile( fileId, bucketId, @@ -203,49 +209,65 @@ export class NetworkFacade { encryptedFileIsCached = true; encryptedFileURI = path; } else { + const [{ url }] = downloadables; + + const downloadChunkSize = 50 * 1024 * 1024; + const ranges: { start: number; end: number }[] = []; + for (let start = 0; start < fileSize; start += downloadChunkSize) { + const end = Math.min(start + downloadChunkSize - 1, fileSize - 1); + ranges.push({ start, end }); + } + encryptedFileURI = fileSystemService.tmpFilePath(encryptedFileName); + console.log({ encryptedFileURI }); + // Create an empty file so RNFS can write to it directly await fileSystemService.createEmptyFile(encryptedFileURI); - downloadJob = RNFS.downloadFile({ - fromUrl: downloadables[0].url, - toFile: encryptedFileURI, - discretionary: true, - cacheable: false, - progressDivider: 5, - progressInterval: 150, - begin: () => { - params.downloadProgressCallback(0, 0, 0); - }, - progress: (res) => { - if (res.contentLength) { - totalBytes = res.contentLength; + params.downloadProgressCallback(0, 0, fileSize); + for (const range of ranges) { + console.log({ range }); + try { + const response = await fetch(url, { + signal: params.signal, + headers: { + Range: `bytes=${range.start}-${range.end}`, + }, + }); + console.log({ response }); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to download chunk: ${response.status} ${response.statusText} - ${errorText}`); } - params.downloadProgressCallback( - res.bytesWritten / res.contentLength, - res.bytesWritten, - res.contentLength, - ); - }, - }); - driveEvents.setJobId(downloadJob.jobId); - expectedFileHash = downloadables[0].hash; + const arrayBuffer = await response.arrayBuffer(); + const uint8Array = new Uint8Array(arrayBuffer); + const base64String = Buffer.from(uint8Array).toString('base64'); + params.downloadProgressCallback(range.start / fileSize, range.start, fileSize); + await RNFS.appendFile(encryptedFileURI, base64String, 'base64'); + } catch (error) { + console.error('Error downloading chunk:', error); + throw error; + } + } + + // driveEvents.setJobId(null); + // expectedFileHash = downloadables[0].hash; } }, async (_, key, iv) => { if (!encryptedFileURI) throw new Error('No encrypted file URI found'); // Maybe we should save the expected hash and compare even if the file is cached - if (!encryptedFileIsCached) { - await downloadJob.promise; - const sha256Hash = await RNFS.hash(encryptedFileURI, 'sha256'); - const receivedFileHash = ripemd160(Buffer.from(sha256Hash, 'hex')).toString('hex'); + // if (!encryptedFileIsCached) { + // await downloadJob.promise; + // const sha256Hash = await RNFS.hash(encryptedFileURI, 'sha256'); + // const receivedFileHash = ripemd160(Buffer.from(sha256Hash, 'hex')).toString('hex'); - if (receivedFileHash !== expectedFileHash) { - throw new Error('Hash mismatch'); - } - } + // if (receivedFileHash !== expectedFileHash) { + // throw new Error('Hash mismatch'); + // } + // } params.downloadProgressCallback(1, totalBytes, totalBytes); @@ -296,7 +318,7 @@ export class NetworkFacade { } }; - return [wrapDownloadWithCleanup(), () => RNFS.stopDownload(downloadJob.jobId)]; + return [wrapDownloadWithCleanup(), () => undefined]; } } diff --git a/src/network/download.ts b/src/network/download.ts index 3f567d163..5722dd69e 100644 --- a/src/network/download.ts +++ b/src/network/download.ts @@ -27,11 +27,13 @@ export async function downloadFile( mnemonic: string, creds: NetworkCredentials, params: DownloadFileParams, + fileSize: number, onAbortableReady: (abortable: Abortable) => void, ): Promise { + console.log('downloadFile'); const network = getNetwork(constants.BRIDGE_URL, creds); - const [downloadPromise, abortable] = network.download(fileId, bucketId, mnemonic, params); + const [downloadPromise, abortable] = network.download(fileId, bucketId, mnemonic, params, fileSize); onAbortableReady(abortable); diff --git a/src/services/drive/file/driveFile.service.ts b/src/services/drive/file/driveFile.service.ts index ae6db76ba..7d793a0a4 100644 --- a/src/services/drive/file/driveFile.service.ts +++ b/src/services/drive/file/driveFile.service.ts @@ -301,6 +301,7 @@ class DriveFileService { /** NOOP */ }, }, + 0, function () { /** NOOP */ }, @@ -329,6 +330,7 @@ class DriveFileService { disableCache, signal, }: DriveFileDownloadOptions, + fileSize: number, ) { const noop = () => { /** NOOP */ @@ -353,6 +355,7 @@ class DriveFileService { }, signal, }, + fileSize, (abortable) => { if (onAbortableReady) { onAbortableReady(abortable); diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts index 20e5c95e7..cbac9dd01 100644 --- a/src/store/slices/drive/index.ts +++ b/src/store/slices/drive/index.ts @@ -195,13 +195,19 @@ const downloadFileThunk = createAsyncThunk< return; } - return drive.file.downloadFile(user, bucketId, params.fileId, { - downloadPath: params.to, - decryptionProgressCallback, - downloadProgressCallback, - signal, - onAbortableReady: drive.events.setLegacyAbortable, - }); + return drive.file.downloadFile( + user, + bucketId, + params.fileId, + { + downloadPath: params.to, + decryptionProgressCallback, + downloadProgressCallback, + signal, + onAbortableReady: drive.events.setLegacyAbortable, + }, + size, + ); }; const trackDownloadStart = () => { From 0a5aae13e1e540c6c26c9f39d92f9d76a496ea6e Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Tue, 17 Dec 2024 17:20:09 +0100 Subject: [PATCH 19/29] Download file in chunks with RNFS --- src/network/NetworkFacade.ts | 66 +++++++++++++++++++------------ src/services/FileSystemService.ts | 37 +++++++++++++++++ 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/network/NetworkFacade.ts b/src/network/NetworkFacade.ts index efe3fccfd..0eb30d360 100644 --- a/src/network/NetworkFacade.ts +++ b/src/network/NetworkFacade.ts @@ -187,7 +187,7 @@ export class NetworkFacade { const encryptedFileName = `${fileId}.enc`; let encryptedFileIsCached = false; - const totalBytes = 0; + let totalBytes = 0; const downloadPromise = downloadFile( fileId, bucketId, @@ -209,50 +209,64 @@ export class NetworkFacade { encryptedFileIsCached = true; encryptedFileURI = path; } else { - const [{ url }] = downloadables; - - const downloadChunkSize = 50 * 1024 * 1024; + const downloadChunkSize = 50 * 1024 * 1024; // 50MB const ranges: { start: number; end: number }[] = []; + const chunkFiles: string[] = []; + for (let start = 0; start < fileSize; start += downloadChunkSize) { const end = Math.min(start + downloadChunkSize - 1, fileSize - 1); ranges.push({ start, end }); } + params.downloadProgressCallback(0, 0, 0); - encryptedFileURI = fileSystemService.tmpFilePath(encryptedFileName); - console.log({ encryptedFileURI }); + for (let i = 0; i < ranges.length; i++) { + const range = ranges[i]; + const chunkFileName = `${encryptedFileName}-chunk-${i + 1}`; + const chunkFileURI = fileSystemService.tmpFilePath(chunkFileName); + chunkFiles.push(chunkFileURI); - // Create an empty file so RNFS can write to it directly - await fileSystemService.createEmptyFile(encryptedFileURI); + // Create an empty file for each chunk + await fileSystemService.createEmptyFile(chunkFileURI); - params.downloadProgressCallback(0, 0, fileSize); - for (const range of ranges) { - console.log({ range }); try { - const response = await fetch(url, { - signal: params.signal, + downloadJob = RNFS.downloadFile({ + fromUrl: downloadables[0].url, + toFile: chunkFileURI, + discretionary: true, + cacheable: false, headers: { Range: `bytes=${range.start}-${range.end}`, }, + progressDivider: 5, + progressInterval: 150, + begin: () => { + console.log('begin ', chunkFileName); + }, + progress: (res) => { + if (res.contentLength) { + totalBytes = res.contentLength + range.start; + } + const overallProgress = (res.contentLength + range.start) / fileSize; + console.log({ res }); + params.downloadProgressCallback(overallProgress, res.contentLength + range.start, fileSize); + }, }); - console.log({ response }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Failed to download chunk: ${response.status} ${response.statusText} - ${errorText}`); - } - - const arrayBuffer = await response.arrayBuffer(); - const uint8Array = new Uint8Array(arrayBuffer); - const base64String = Buffer.from(uint8Array).toString('base64'); params.downloadProgressCallback(range.start / fileSize, range.start, fileSize); - await RNFS.appendFile(encryptedFileURI, base64String, 'base64'); + await downloadJob.promise; + params.downloadProgressCallback(range.end / fileSize, range.end, fileSize); } catch (error) { - console.error('Error downloading chunk:', error); + console.error(`Error downloading chunk ${i + 1}:`, error); + + await Promise.all(chunkFiles.map((file) => fileSystemService.deleteFile([file]))); + throw error; } } - // driveEvents.setJobId(null); - // expectedFileHash = downloadables[0].hash; + encryptedFileURI = fileSystemService.tmpFilePath(encryptedFileName); + await fileSystemService.combineFiles(chunkFiles, encryptedFileURI); + + await Promise.all(chunkFiles.map((file) => fileSystemService.deleteFile([file]))); } }, async (_, key, iv) => { diff --git a/src/services/FileSystemService.ts b/src/services/FileSystemService.ts index 29ad4d91e..f4da8b184 100644 --- a/src/services/FileSystemService.ts +++ b/src/services/FileSystemService.ts @@ -29,6 +29,43 @@ class FileSystemService { public async prepareFileSystem() { await this.prepareTmpDir(); } + public async deleteFile(files: string[]): Promise { + try { + await Promise.all( + files.map(async (file) => { + try { + await this.unlinkIfExists(file); + } catch (error) { + console.warn(`Error deleting file ${file}:`, error); + } + }), + ); + } catch (error) { + console.error('Error in bulk file deletion:', error); + throw error; + } + } + public async combineFiles(sourceFiles: string[], destinationFile: string): Promise { + try { + await this.unlinkIfExists(destinationFile); + + await this.createEmptyFile(destinationFile); + + const writeStream = await RNFetchBlob.fs.writeStream(destinationFile, 'base64'); + + for (const sourceFile of sourceFiles) { + const content = await this.readFile(sourceFile); + await writeStream.write(content.toString('base64')); + } + + writeStream.close(); + + await Promise.all(sourceFiles.map((file) => this.unlinkIfExists(file))); + } catch (error) { + console.error('Error combining files:', error); + throw error; + } + } public pathToUri(path: string): string { if (path.startsWith(ANDROID_URI_PREFIX)) return path; From 2f7da8ff2069713b97002f3fa5b79140dc28093e Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Thu, 19 Dec 2024 19:15:42 +0100 Subject: [PATCH 20/29] Bump rn-crypto, added native joinFiles function, added device space check for download --- android/app/src/main/AndroidManifest.xml | 4 +- assets/lang/strings.ts | 3 +- ios/Podfile.lock | 4 +- package.json | 2 +- .../modals/DriveItemInfoModal/index.tsx | 7 +- src/network/NetworkFacade.ts | 83 ++++++++++++++----- src/network/download.ts | 1 - src/services/FileSystemService.ts | 35 ++++---- 8 files changed, 89 insertions(+), 50 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cdc2e15b8..bdb4e338a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - + @@ -8,6 +9,7 @@ + diff --git a/assets/lang/strings.ts b/assets/lang/strings.ts index 3ebe0d473..e7566a18a 100644 --- a/assets/lang/strings.ts +++ b/assets/lang/strings.ts @@ -676,7 +676,6 @@ const strings = new LocalizedStrings({ unknown: 'Unknown error', uploadFile: 'File upload error: {0}', storageLimitReached: 'You have reached your storage limit', - inviteAFriend: 'Error sending invitation: {0}', loadProducts: 'Cannot load products: {0}', passwordsDontMatch: "Passwords don't match", @@ -698,6 +697,7 @@ const strings = new LocalizedStrings({ changePassword: 'Error changing password', loadPrices: 'Error loading prices', cancelSubscription: 'Error cancelling subscription', + notEnoughSpaceOnDevice: 'Not enough storage space available for download', }, }, es: { @@ -1396,6 +1396,7 @@ const strings = new LocalizedStrings({ changePassword: 'Error cambiando contraseña', loadPrices: 'Error cargando precios', cancelSubscription: 'Error cancelando suscripción', + notEnoughSpaceOnDevice: 'No hay suficiente espacio de almacenamiento disponible para la descarga', }, }, }); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 111e9d4df..e3d08c19f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1142,7 +1142,7 @@ PODS: - React-Core - RealmJS (11.10.2): - React - - rn-crypto (0.1.12): + - rn-crypto (0.1.14): - IDZSwiftCommonCrypto (~> 0.13) - React-Core - rn-fetch-blob (0.11.2): @@ -1606,7 +1606,7 @@ SPEC CHECKSUMS: ReactCommon: 447281ad2034ea3252bf81a60d1f77d5afb0b636 ReactNativeLocalization: fb171138cdc80d5d0d4f20243d2fc82c2b3cc48f RealmJS: 73a36da3cbbe85e1bdcbf55683172b51f35070d3 - rn-crypto: 160ce10c618571e5c051c8e12315df0b04ac7c3e + rn-crypto: 11206fba572b93aa936f225fa7e9dec24a330a58 rn-fetch-blob: f525a73a78df9ed5d35e67ea65e79d53c15255bc RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa diff --git a/package.json b/package.json index 4976f4478..b8ad0e610 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@hookform/resolvers": "^2.9.1", "@internxt/lib": "^1.2.0", "@internxt/mobile-sdk": "^0.2.41", - "@internxt/rn-crypto": "^0.1.12", + "@internxt/rn-crypto": "0.1.14", "@internxt/sdk": "^1.4.96", "@react-native-async-storage/async-storage": "1.21.0", "@react-navigation/bottom-tabs": "^6.2.0", diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index e69ba7fc9..afbab9094 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -6,7 +6,7 @@ import Portal from '@burstware/react-native-portal'; import { useDrive } from '@internxt-mobile/hooks/drive'; import AuthService from '@internxt-mobile/services/AuthService'; import errorService from '@internxt-mobile/services/ErrorService'; -import { fs } from '@internxt-mobile/services/FileSystemService'; +import fileSystemService, { fs } from '@internxt-mobile/services/FileSystemService'; import notificationsService, { notifications } from '@internxt-mobile/services/NotificationsService'; import { logger } from '@internxt-mobile/services/common'; import { time } from '@internxt-mobile/services/common/time'; @@ -127,6 +127,11 @@ function DriveItemInfoModal(): JSX.Element { const downloadItem = async (fileId: string, bucketId: string, decryptedFilePath: string, fileSize: number) => { const { credentials } = await AuthService.getAuthCredentials(); + const hasEnoughSpace = await fileSystemService.checkAvailableStorage(fileSize); + if (!hasEnoughSpace) { + throw new Error(strings.errors.notEnoughSpaceOnDevice); + } + const { downloadPath } = await drive.file.downloadFile( credentials.user, bucketId, diff --git a/src/network/NetworkFacade.ts b/src/network/NetworkFacade.ts index 0eb30d360..29b831201 100644 --- a/src/network/NetworkFacade.ts +++ b/src/network/NetworkFacade.ts @@ -1,5 +1,6 @@ import * as RNFS from '@dr.pogodin/react-native-fs'; -import { decryptFile, encryptFile } from '@internxt/rn-crypto'; +import { logger } from '@internxt-mobile/services/common'; +import { decryptFile, encryptFile, joinFiles } from '@internxt/rn-crypto'; import { ALGORITHMS, Network } from '@internxt/sdk/dist/network'; import { downloadFile } from '@internxt/sdk/dist/network/download'; import { Crypto } from '@internxt/sdk/dist/network/types'; @@ -14,6 +15,7 @@ import drive from '@internxt-mobile/services/drive'; import { ripemd160 } from '../@inxt-js/lib/crypto'; import { generateFileKey } from '../lib/network'; import appService from '../services/AppService'; +import { driveEvents } from '../services/drive/events'; import fileSystemService from '../services/FileSystemService'; import { Abortable } from '../types'; import { EncryptedFileDownloadedParams } from './download'; @@ -164,7 +166,6 @@ export class NetworkFacade { params: DownloadFileParams, fileSize: number, ): [Promise, Abortable] { - console.log('download'); if (!fileId) { throw new Error('Download error code 1'); } @@ -178,6 +179,7 @@ export class NetworkFacade { } let downloadJob: { jobId: number; promise: Promise }; + let expectedFileHash: string; const decryptFileFromFs: DecryptFileFromFsFunction = @@ -188,6 +190,25 @@ export class NetworkFacade { const encryptedFileName = `${fileId}.enc`; let encryptedFileIsCached = false; let totalBytes = 0; + let chunkFiles: string[] = []; + + const cleanupChunks = async () => { + if (chunkFiles.length > 0) { + await Promise.all(chunkFiles.map((file) => fileSystemService.deleteFile([file]))); + chunkFiles = []; + } + }; + const abortDownload = () => { + if (downloadJob) { + RNFS.stopDownload(downloadJob.jobId); + } + cleanupChunks(); + }; + + if (params.signal) { + params.signal.addEventListener('abort', abortDownload); + } + const downloadPromise = downloadFile( fileId, bucketId, @@ -209,9 +230,9 @@ export class NetworkFacade { encryptedFileIsCached = true; encryptedFileURI = path; } else { - const downloadChunkSize = 50 * 1024 * 1024; // 50MB + const FIFTY_MB = 50 * 1024 * 1024; + const downloadChunkSize = FIFTY_MB; const ranges: { start: number; end: number }[] = []; - const chunkFiles: string[] = []; for (let start = 0; start < fileSize; start += downloadChunkSize) { const end = Math.min(start + downloadChunkSize - 1, fileSize - 1); @@ -220,12 +241,17 @@ export class NetworkFacade { params.downloadProgressCallback(0, 0, 0); for (let i = 0; i < ranges.length; i++) { + const isFirstChunk = i === 0; + + if (params.signal?.aborted) { + throw new Error('Download aborted'); + } + const range = ranges[i]; const chunkFileName = `${encryptedFileName}-chunk-${i + 1}`; const chunkFileURI = fileSystemService.tmpFilePath(chunkFileName); chunkFiles.push(chunkFileURI); - // Create an empty file for each chunk await fileSystemService.createEmptyFile(chunkFileURI); try { @@ -240,33 +266,46 @@ export class NetworkFacade { progressDivider: 5, progressInterval: 150, begin: () => { - console.log('begin ', chunkFileName); + if (isFirstChunk) { + params.downloadProgressCallback(0, 0, 0); + } }, progress: (res) => { - if (res.contentLength) { - totalBytes = res.contentLength + range.start; - } - const overallProgress = (res.contentLength + range.start) / fileSize; - console.log({ res }); - params.downloadProgressCallback(overallProgress, res.contentLength + range.start, fileSize); + // NOT WORKING ON IOS. CHECK + params.downloadProgressCallback( + (totalBytes + res.bytesWritten) / fileSize, + res.bytesWritten + totalBytes, + fileSize, + ); }, }); - params.downloadProgressCallback(range.start / fileSize, range.start, fileSize); - await downloadJob.promise; - params.downloadProgressCallback(range.end / fileSize, range.end, fileSize); - } catch (error) { - console.error(`Error downloading chunk ${i + 1}:`, error); + // BECAUSE PROGRESS IS NOT WORKING ON IOS + if (isFirstChunk) params.downloadProgressCallback(0, 0, 0); - await Promise.all(chunkFiles.map((file) => fileSystemService.deleteFile([file]))); + driveEvents.setJobId(downloadJob.jobId); + await downloadJob.promise; + totalBytes = downloadChunkSize * (i + 1); + // BECAUSE PROGRESS IS NOT WORKING ON IOS + if (Platform.OS === 'ios') { + params.downloadProgressCallback(totalBytes / fileSize, totalBytes, fileSize); + } + } catch (error) { + await cleanupChunks(); throw error; } } - + expectedFileHash = downloadables[0].hash; encryptedFileURI = fileSystemService.tmpFilePath(encryptedFileName); - await fileSystemService.combineFiles(chunkFiles, encryptedFileURI); - await Promise.all(chunkFiles.map((file) => fileSystemService.deleteFile([file]))); + joinFiles(chunkFiles, encryptedFileURI, async (error) => { + if (error) { + logger.error('Error on joinFiles in download function:', JSON.stringify(error)); + throw error; + } + + await cleanupChunks(); + }); } }, async (_, key, iv) => { @@ -332,7 +371,7 @@ export class NetworkFacade { } }; - return [wrapDownloadWithCleanup(), () => undefined]; + return [wrapDownloadWithCleanup(), abortDownload]; } } diff --git a/src/network/download.ts b/src/network/download.ts index 5722dd69e..af9158ee0 100644 --- a/src/network/download.ts +++ b/src/network/download.ts @@ -30,7 +30,6 @@ export async function downloadFile( fileSize: number, onAbortableReady: (abortable: Abortable) => void, ): Promise { - console.log('downloadFile'); const network = getNetwork(constants.BRIDGE_URL, creds); const [downloadPromise, abortable] = network.download(fileId, bucketId, mnemonic, params, fileSize); diff --git a/src/services/FileSystemService.ts b/src/services/FileSystemService.ts index f4da8b184..0e2cd007a 100644 --- a/src/services/FileSystemService.ts +++ b/src/services/FileSystemService.ts @@ -45,27 +45,6 @@ class FileSystemService { throw error; } } - public async combineFiles(sourceFiles: string[], destinationFile: string): Promise { - try { - await this.unlinkIfExists(destinationFile); - - await this.createEmptyFile(destinationFile); - - const writeStream = await RNFetchBlob.fs.writeStream(destinationFile, 'base64'); - - for (const sourceFile of sourceFiles) { - const content = await this.readFile(sourceFile); - await writeStream.write(content.toString('base64')); - } - - writeStream.close(); - - await Promise.all(sourceFiles.map((file) => this.unlinkIfExists(file))); - } catch (error) { - console.error('Error combining files:', error); - throw error; - } - } public pathToUri(path: string): string { if (path.startsWith(ANDROID_URI_PREFIX)) return path; @@ -358,6 +337,20 @@ class FileSystemService { await this.unlinkIfExists(RNFetchBlob.fs.dirs.DocumentDir + '/RNFetchBlob_tmp'); await this.clearTempDir(); } + + public async checkAvailableStorage(requiredSpace: number): Promise { + try { + const fsInfo = await RNFS.getFSInfo(); + const freeSpace = fsInfo.freeSpace; + + const spaceWithBuffer = requiredSpace * 1.3; + + return freeSpace >= spaceWithBuffer; + } catch (error) { + console.error('Error checking storage:', error); + throw new Error('Could not check available storage'); + } + } } const fileSystemService = new FileSystemService(); From 6c0ae62e60c37087c9ac9cd4732323519b4b993a Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 08:52:45 +0100 Subject: [PATCH 21/29] Modified download/decrypt percentage message --- src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx index 02d03fb06..b102822a2 100644 --- a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx +++ b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx @@ -88,7 +88,7 @@ export const DrivePreviewScreen: React.FC> return <>; } const filename = `${focusedItem.name || ''}${focusedItem.type ? `.${focusedItem.type}` : ''}`; - const currentProgress = downloadingFile.downloadProgress * 0.5 + downloadingFile.decryptProgress * 0.5; + const currentProgress = downloadingFile.downloadProgress * 0.95 + downloadingFile.decryptProgress * 0.05; const FileIcon = getFileTypeIcon(focusedItem.type || ''); const hasImagePreview = IMAGE_PREVIEW_TYPES.includes(downloadingFile.data.type?.toLowerCase() as FileExtension); const hasVideoPreview = VIDEO_PREVIEW_TYPES.includes(downloadingFile.data.type?.toLowerCase() as FileExtension); @@ -99,7 +99,7 @@ export const DrivePreviewScreen: React.FC> } const progressMessage = strings.formatString( - currentProgress < 0.5 ? strings.screens.drive.downloadingPercent : strings.screens.drive.decryptingPercent, + currentProgress < 0.95 ? strings.screens.drive.downloadingPercent : strings.screens.drive.decryptingPercent, (currentProgress * 100).toFixed(0), ); From bfdc7907a49d03d21fc9b284eb5ad161551963bb Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 10:01:56 +0100 Subject: [PATCH 22/29] Upgraded download limit to 5GB --- assets/lang/strings.ts | 4 ++-- src/components/modals/DriveItemInfoModal/index.tsx | 2 +- src/services/drive/constants.ts | 1 + src/store/slices/drive/index.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/assets/lang/strings.ts b/assets/lang/strings.ts index e7566a18a..36aee324e 100644 --- a/assets/lang/strings.ts +++ b/assets/lang/strings.ts @@ -647,7 +647,7 @@ const strings = new LocalizedStrings({ confirmDeleteSharedLink: 'Users with the link will lose access to the shared content.', linkDeleted: 'Link deleted successfully', trashEmpty: 'Trash is empty', - downloadLimit: 'The download limit in mobile app is 3GB.', + downloadLimit: 'The download limit in mobile app is 5GB.', }, errors: { runtimeLogsMissing: 'The logs file is missing or empty', @@ -1346,7 +1346,7 @@ const strings = new LocalizedStrings({ confirmDeleteSharedLink: 'Los usuarios con el link compartido perderán el acceso a este contenido compartido.', linkDeleted: 'Link eliminado correctamente', trashEmpty: 'Papelera vaciada', - downloadLimit: 'El límite de descarga en la app movil son 3GB', + downloadLimit: 'El límite de descarga en la app movil son 5GB', }, errors: { runtimeLogsMissing: 'El archivo no se encuentra o está vacío', diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index afbab9094..f319ab177 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -69,7 +69,7 @@ function DriveItemInfoModal(): JSX.Element { }; const isFileDownloadable = (): boolean => { - if (parseInt(item.size?.toString() ?? '0') > MAX_SIZE_TO_DOWNLOAD['3GB']) { + if (parseInt(item.size?.toString() ?? '0') > MAX_SIZE_TO_DOWNLOAD['5GB']) { notificationsService.info(strings.messages.downloadLimit); return false; } diff --git a/src/services/drive/constants.ts b/src/services/drive/constants.ts index e65f63f68..64d281cad 100644 --- a/src/services/drive/constants.ts +++ b/src/services/drive/constants.ts @@ -10,4 +10,5 @@ export const MAX_FILE_SIZE_FOR_CACHING = MAX_CACHE_DIRECTORY_SIZE_IN_BYTES * 0.1 export const MAX_SIZE_TO_DOWNLOAD = { '3GB': 3221225472, + '5GB': 5368709120, }; diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts index cbac9dd01..14ad52424 100644 --- a/src/store/slices/drive/index.ts +++ b/src/store/slices/drive/index.ts @@ -143,7 +143,7 @@ const downloadFileThunk = createAsyncThunk< logger.info('Starting file download...'); const { user } = getState().auth; - if (parseInt(size?.toString() ?? '0') > MAX_SIZE_TO_DOWNLOAD['3GB']) { + if (parseInt(size?.toString() ?? '0') > MAX_SIZE_TO_DOWNLOAD['5GB']) { dispatch( driveActions.updateDownloadingFile({ error: strings.messages.downloadLimit, From d72096bee966018cb76d7a57f652d2043ab73b94 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 12:06:31 +0100 Subject: [PATCH 23/29] Remove chunks before start the download, added decrypting message when download is finished and skip space check if is not possible to check it --- assets/lang/strings.ts | 2 ++ .../modals/DriveItemInfoModal/index.tsx | 21 ++++++++++++++++--- src/network/NetworkFacade.ts | 16 ++++++++++++++ .../DrivePreviewScreen/DrivePreviewScreen.tsx | 6 +++++- src/services/FileSystemService.ts | 1 - 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/assets/lang/strings.ts b/assets/lang/strings.ts index 36aee324e..7fb800b1e 100644 --- a/assets/lang/strings.ts +++ b/assets/lang/strings.ts @@ -14,6 +14,7 @@ const strings = new LocalizedStrings({ current: 'Current', new: 'New', calculating: 'Calculating', + decrypting: 'Decrypting', atTime: 'at', loading: 'Loading', downloading: 'Downloading...', @@ -709,6 +710,7 @@ const strings = new LocalizedStrings({ current: 'Actual', new: 'Nuevo', calculating: 'Calculando', + decrypting: 'Desencriptando', atTime: 'a las', loading: 'Cargando', security: 'Seguridad', diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx index f319ab177..f12163f83 100644 --- a/src/components/modals/DriveItemInfoModal/index.tsx +++ b/src/components/modals/DriveItemInfoModal/index.tsx @@ -127,9 +127,15 @@ function DriveItemInfoModal(): JSX.Element { const downloadItem = async (fileId: string, bucketId: string, decryptedFilePath: string, fileSize: number) => { const { credentials } = await AuthService.getAuthCredentials(); - const hasEnoughSpace = await fileSystemService.checkAvailableStorage(fileSize); - if (!hasEnoughSpace) { - throw new Error(strings.errors.notEnoughSpaceOnDevice); + try { + const hasEnoughSpace = await fileSystemService.checkAvailableStorage(fileSize); + if (!hasEnoughSpace) { + notifications.error(strings.errors.notEnoughSpaceOnDevice); + throw new Error(strings.errors.notEnoughSpaceOnDevice); + } + } catch (error) { + logger.error('Error on downloadItem function:', JSON.stringify(error)); + errorService.reportError(error); } const { downloadPath } = await drive.file.downloadFile( @@ -353,6 +359,15 @@ function DriveItemInfoModal(): JSX.Element { if (!downloadProgress.totalBytes) { return strings.generic.calculating + '...'; } + + if ( + item?.size && + downloadProgress?.bytesReceived && + downloadProgress?.bytesReceived >= parseInt(item?.size?.toString()) + ) { + return strings.generic.decrypting + '...'; + } + const bytesReceivedStr = prettysize(downloadProgress.bytesReceived); const totalBytesStr = prettysize(downloadProgress.totalBytes); return `${bytesReceivedStr} ${strings.modals.downloadingFile.of} ${totalBytesStr}`; diff --git a/src/network/NetworkFacade.ts b/src/network/NetworkFacade.ts index 29b831201..394749bbb 100644 --- a/src/network/NetworkFacade.ts +++ b/src/network/NetworkFacade.ts @@ -205,6 +205,21 @@ export class NetworkFacade { cleanupChunks(); }; + const cleanupExistingChunks = async (encFileName: string) => { + try { + const tmpDir = fileSystemService.getTemporaryDir(); + const files = await RNFS.readDir(tmpDir); + const chunkPattern = new RegExp(`${encFileName}\\-chunk-\\d+$`); + + const existingChunks = files.filter((file) => chunkPattern.test(file.name)); + if (existingChunks.length > 0) { + await Promise.all(existingChunks.map((file) => fileSystemService.deleteFile([file.path]))); + } + } catch (error) { + logger.error('Error cleaning up existing chunks:', JSON.stringify(error)); + } + }; + if (params.signal) { params.signal.addEventListener('abort', abortDownload); } @@ -230,6 +245,7 @@ export class NetworkFacade { encryptedFileIsCached = true; encryptedFileURI = path; } else { + await cleanupExistingChunks(encryptedFileName); const FIFTY_MB = 50 * 1024 * 1024; const downloadChunkSize = FIFTY_MB; const ranges: { start: number; end: number }[] = []; diff --git a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx index b102822a2..0eacb0f78 100644 --- a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx +++ b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx @@ -20,6 +20,7 @@ import { useAppDispatch, useAppSelector } from 'src/store/hooks'; import { uiActions } from 'src/store/slices/ui'; import { getLineHeight } from 'src/styles/global'; import { useTailwind } from 'tailwind-rn'; +import { driveThunks } from '../../../store/slices/drive'; import { DriveImagePreview } from './DriveImagePreview'; import { DrivePdfPreview } from './DrivePdfPreview'; import { DRIVE_PREVIEW_HEADER_HEIGHT, DrivePreviewScreenHeader } from './DrivePreviewScreenHeader'; @@ -242,7 +243,10 @@ export const DrivePreviewScreen: React.FC> { + dispatch(driveThunks.cancelDownloadThunk()); + props.navigation.goBack(); + }} onActionsButtonPress={handleActionsButtonPress} /> diff --git a/src/services/FileSystemService.ts b/src/services/FileSystemService.ts index 0e2cd007a..a9fbf5e10 100644 --- a/src/services/FileSystemService.ts +++ b/src/services/FileSystemService.ts @@ -347,7 +347,6 @@ class FileSystemService { return freeSpace >= spaceWithBuffer; } catch (error) { - console.error('Error checking storage:', error); throw new Error('Could not check available storage'); } } From 03d0e61664cc793286c1456161b0cf3ff406cd37 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 13:19:35 +0100 Subject: [PATCH 24/29] Updated yarn lock --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index db712eeb0..c48d12eb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1759,10 +1759,10 @@ resolved "https://npm.pkg.github.com/download/@internxt/prettier-config/1.0.2/5bd220b8de76734448db5475b3e0c01f9d22c19b#5bd220b8de76734448db5475b3e0c01f9d22c19b" integrity sha512-t4HiqvCbC7XgQepwWlIaFJe3iwW7HCf6xOSU9nKTV0tiGqOPz7xMtIgLEloQrDA34Cx4PkOYBXrvFPV6RxSFAA== -"@internxt/rn-crypto@^0.1.12": - version "0.1.12" - resolved "https://npm.pkg.github.com/download/@internxt/rn-crypto/0.1.12/7650e6442987720aae1723edb139f73ee2669b98#7650e6442987720aae1723edb139f73ee2669b98" - integrity sha512-IsrJph8uJaO4yz2amUjga3d6hlsEDvWFF7mF+QcAqY2XzsdTaZWH3n6o2G+1IGy9OXeTImou8ob3LMUpGSzu6Q== +"@internxt/rn-crypto@0.1.14": + version "0.1.14" + resolved "https://npm.pkg.github.com/download/@internxt/rn-crypto/0.1.14/9b9f99227744f050b4f197e880ad5dfabca77854#9b9f99227744f050b4f197e880ad5dfabca77854" + integrity sha512-9ATRLw4O77tltmfB6Yk0gNfbHTSpQ/1h5wR80YqnnHQkk4k7W/9wCKQb0binMCV+XtZKhDJMgsK11S0SB+durw== dependencies: buffer "^6.0.3" From ac5b43bd2e6258157d7a25c22bdb12e393453cf8 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 14:35:02 +0100 Subject: [PATCH 25/29] Minor fix on android cursor position --- src/components/AppTextInput/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AppTextInput/index.tsx b/src/components/AppTextInput/index.tsx index ef9d4caf7..e883cbb77 100644 --- a/src/components/AppTextInput/index.tsx +++ b/src/components/AppTextInput/index.tsx @@ -62,7 +62,8 @@ const AppTextInput = (props: AppTextInputProps): JSX.Element => { if (hasNothingSelected) { newPosition = selection.start + (newText.length - oldText.length); } else { - newPosition = selectionStart; + const insertedLength = newText.length - (oldText.length - (selection.end - selection.start)); + newPosition = selectionStart + insertedLength; } newPosition = Math.max(0, newPosition); From d0ea213a82cd6d4deed931679db797e558f4b7dc Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 14:41:59 +0100 Subject: [PATCH 26/29] Updated android gradle versionCode --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fbd5e749e..a4f1c27f7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,7 +88,7 @@ android { applicationId 'com.internxt.cloud' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 97 + versionCode 98 versionName "1.6.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) From 25fb6ef10db7cc28579fa080560f97eb8d256850 Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 17:36:36 +0100 Subject: [PATCH 27/29] Removed unnecessary android permission --- android/app/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bdb4e338a..0950d4d66 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ - From 577502c8a3e6e60ca8dac3d42b7395c7ec59578d Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Fri, 20 Dec 2024 19:13:34 +0100 Subject: [PATCH 28/29] Updated gradle build version --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a4f1c27f7..fc9e0cd28 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,7 +88,7 @@ android { applicationId 'com.internxt.cloud' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 98 + versionCode 99 versionName "1.6.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) From cda9c2e87dc53ccb1d04f769ac662514603324ca Mon Sep 17 00:00:00 2001 From: Ramon Candel Date: Mon, 23 Dec 2024 16:41:42 +0100 Subject: [PATCH 29/29] Remove unnecessary tyr catch and added comment --- src/network/NetworkFacade.ts | 1 + src/services/FileSystemService.ts | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/network/NetworkFacade.ts b/src/network/NetworkFacade.ts index 394749bbb..e676fc755 100644 --- a/src/network/NetworkFacade.ts +++ b/src/network/NetworkFacade.ts @@ -327,6 +327,7 @@ export class NetworkFacade { async (_, key, iv) => { if (!encryptedFileURI) throw new Error('No encrypted file URI found'); + // commented because it is giving errors, we should check if it is necessary // Maybe we should save the expected hash and compare even if the file is cached // if (!encryptedFileIsCached) { // await downloadJob.promise; diff --git a/src/services/FileSystemService.ts b/src/services/FileSystemService.ts index a9fbf5e10..112d2c233 100644 --- a/src/services/FileSystemService.ts +++ b/src/services/FileSystemService.ts @@ -9,6 +9,7 @@ import FileViewer from 'react-native-file-viewer'; import Share from 'react-native-share'; import uuid from 'react-native-uuid'; import RNFetchBlob, { RNFetchBlobStat } from 'rn-fetch-blob'; + enum AcceptedEncodings { Utf8 = 'utf8', Ascii = 'ascii', @@ -33,11 +34,7 @@ class FileSystemService { try { await Promise.all( files.map(async (file) => { - try { - await this.unlinkIfExists(file); - } catch (error) { - console.warn(`Error deleting file ${file}:`, error); - } + await this.unlinkIfExists(file); }), ); } catch (error) {