diff --git a/app.config.ts b/app.config.ts index 8009a16c0..2bb376758 100644 --- a/app.config.ts +++ b/app.config.ts @@ -146,9 +146,6 @@ const appConfig: ExpoConfig & { extra: AppEnv & { NODE_ENV: AppStage; RELEASE_ID 'react-native-svg', '@shopify/flash-list', 'react-native-pdf', - 'react-native-pdf-thumbnail', - 'react-native-blob-util', - 'react-native-create-thumbnail', 'jail-monkey', ], backgroundColor: { red: 0, green: 0, blue: 0, alpha: 0 }, diff --git a/ios/InternxtShareExtension/ShareExtensionViewController.swift b/ios/InternxtShareExtension/ShareExtensionViewController.swift index a7d23577b..ebe06e778 100644 --- a/ios/InternxtShareExtension/ShareExtensionViewController.swift +++ b/ios/InternxtShareExtension/ShareExtensionViewController.swift @@ -349,6 +349,7 @@ class ShareExtensionViewController: UIViewController { let persistentURL = sharedDataUrl.appendingPathComponent(fileName) do { + try? fileManager.removeItem(atPath: persistentURL.path) try fileManager.copyItem(atPath: tempFilePath, toPath: persistentURL.path) let key = isImage ? "images" : "files" if sharedItems[key] == nil { diff --git a/ios/Podfile b/ios/Podfile index 85d30ef30..bd8d78720 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -72,7 +72,7 @@ target 'Internxt' do end target 'InternxtShareExtension' do - exclude = ["expo-updates", "expo-splash-screen", "expo-dev-client", "react-native-reanimated", "react-native-screens", "react-native-safe-area-context", "react-native-gesture-handler", "react-native-video", "react-native-webview", "react-native-fast-image", "react-native-svg", "@shopify/flash-list", "react-native-pdf", "react-native-pdf-thumbnail", "react-native-create-thumbnail", "jail-monkey"] + exclude = ["expo-updates", "expo-splash-screen", "expo-dev-client", "react-native-reanimated", "react-native-screens", "react-native-safe-area-context", "react-native-gesture-handler", "react-native-video", "react-native-webview", "react-native-fast-image", "react-native-svg", "@shopify/flash-list", "react-native-pdf", "jail-monkey"] use_expo_modules!(exclude: exclude) if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 323bbd126..08a6abc2f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2969,6 +2969,6 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a -PODFILE CHECKSUM: c616d1ada5d84f015b84b48b4ee50b266ab8ecc6 +PODFILE CHECKSUM: 1c3110710c3007a8b81ff873b578769e00b42ac1 COCOAPODS: 1.16.2 diff --git a/package.json b/package.json index f10bc4bb2..1c2da2e8c 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "react-native": "0.81.5", "react-native-blob-util": "^0.24.6", "react-native-capture-protection": "2.4.0", - "react-native-create-thumbnail": "^2.0.0", + "react-native-create-thumbnail": "^2.2.0", "react-native-crypto": "^2.2.1", "react-native-device-info": "^8.4.8", "react-native-fast-image": "^8.5.11", diff --git a/patches/react-native-create-thumbnail+2.2.0.patch b/patches/react-native-create-thumbnail+2.2.0.patch new file mode 100644 index 000000000..901e20561 --- /dev/null +++ b/patches/react-native-create-thumbnail+2.2.0.patch @@ -0,0 +1,372 @@ +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/b0e51eadc74f7bec22fa445c21a664a0/results.bin b/node_modules/react-native-create-thumbnail/android/build/.transforms/b0e51eadc74f7bec22fa445c21a664a0/results.bin +new file mode 100644 +index 0000000..0d259dd +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/.transforms/b0e51eadc74f7bec22fa445c21a664a0/results.bin +@@ -0,0 +1 @@ ++o/classes +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/b0e51eadc74f7bec22fa445c21a664a0/transformed/classes/classes_dex/classes.dex b/node_modules/react-native-create-thumbnail/android/build/.transforms/b0e51eadc74f7bec22fa445c21a664a0/transformed/classes/classes_dex/classes.dex +new file mode 100644 +index 0000000..d52ae3c +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/.transforms/b0e51eadc74f7bec22fa445c21a664a0/transformed/classes/classes_dex/classes.dex differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/results.bin b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/results.bin +new file mode 100644 +index 0000000..7ed749e +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/results.bin +@@ -0,0 +1 @@ ++o/bundleLibRuntimeToDirDebug +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/BuildConfig.dex b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/BuildConfig.dex +new file mode 100644 +index 0000000..4c8d5e2 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/BuildConfig.dex differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/CreateThumbnailModule.dex b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/CreateThumbnailModule.dex +new file mode 100644 +index 0000000..2e7f494 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/CreateThumbnailModule.dex differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/CreateThumbnailPackage.dex b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/CreateThumbnailPackage.dex +new file mode 100644 +index 0000000..05f5f1f +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/reactlibrary/createthumbnail/CreateThumbnailPackage.dex differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +new file mode 100644 +index 0000000..601f245 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/.transforms/ec1d777ecabcb2706897ee00a43d091e/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/generated/source/buildConfig/debug/com/reactlibrary/createthumbnail/BuildConfig.java b/node_modules/react-native-create-thumbnail/android/build/generated/source/buildConfig/debug/com/reactlibrary/createthumbnail/BuildConfig.java +new file mode 100644 +index 0000000..c689236 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/generated/source/buildConfig/debug/com/reactlibrary/createthumbnail/BuildConfig.java +@@ -0,0 +1,10 @@ ++/** ++ * Automatically generated file. DO NOT MODIFY ++ */ ++package com.reactlibrary.createthumbnail; ++ ++public final class BuildConfig { ++ public static final boolean DEBUG = Boolean.parseBoolean("true"); ++ public static final String LIBRARY_PACKAGE_NAME = "com.reactlibrary.createthumbnail"; ++ public static final String BUILD_TYPE = "debug"; ++} +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml b/node_modules/react-native-create-thumbnail/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +new file mode 100644 +index 0000000..c0d992c +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +@@ -0,0 +1,10 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json b/node_modules/react-native-create-thumbnail/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +new file mode 100644 +index 0000000..cc0067c +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +@@ -0,0 +1,18 @@ ++{ ++ "version": 3, ++ "artifactType": { ++ "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", ++ "kind": "Directory" ++ }, ++ "applicationId": "com.reactlibrary.createthumbnail", ++ "variantName": "debug", ++ "elements": [ ++ { ++ "type": "SINGLE", ++ "filters": [], ++ "attributes": [], ++ "outputFile": "AndroidManifest.xml" ++ } ++ ], ++ "elementType": "File" ++} +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties b/node_modules/react-native-create-thumbnail/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +new file mode 100644 +index 0000000..1211b1e +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +@@ -0,0 +1,6 @@ ++aarFormatVersion=1.0 ++aarMetadataVersion=1.0 ++minCompileSdk=1 ++minCompileSdkExtension=0 ++minAndroidGradlePluginVersion=1.0.0 ++coreLibraryDesugaringEnabled=false +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json b/node_modules/react-native-create-thumbnail/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +new file mode 100644 +index 0000000..9e26dfe +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +@@ -0,0 +1 @@ ++{} +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar b/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +new file mode 100644 +index 0000000..a782255 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar b/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +new file mode 100644 +index 0000000..744b164 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt b/node_modules/react-native-create-thumbnail/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +new file mode 100644 +index 0000000..e69de29 +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +new file mode 100644 +index 0000000..4d98f88 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +@@ -0,0 +1 @@ ++#Mon Mar 23 09:47:26 CET 2026 +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +new file mode 100644 +index 0000000..07380c0 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugAssets/merger.xml b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +new file mode 100644 +index 0000000..135ea06 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +new file mode 100644 +index 0000000..4da3de2 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugShaders/merger.xml b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +new file mode 100644 +index 0000000..48e0788 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +@@ -0,0 +1,2 @@ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/BuildConfig.class b/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/BuildConfig.class +new file mode 100644 +index 0000000..4d3d132 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/BuildConfig.class differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/CreateThumbnailModule.class b/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/CreateThumbnailModule.class +new file mode 100644 +index 0000000..2f86f71 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/CreateThumbnailModule.class differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/CreateThumbnailPackage.class b/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/CreateThumbnailPackage.class +new file mode 100644 +index 0000000..85e00f9 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/createthumbnail/CreateThumbnailPackage.class differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt b/node_modules/react-native-create-thumbnail/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +new file mode 100644 +index 0000000..78ac5b8 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +@@ -0,0 +1,2 @@ ++R_DEF: Internal format may change without notice ++local +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt b/node_modules/react-native-create-thumbnail/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +new file mode 100644 +index 0000000..5ea3415 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +@@ -0,0 +1,14 @@ ++1 ++2 ++4 ++5 ++6 ++7 ++7-->/Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:3:5-80 ++7-->/Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:3:22-78 ++8 ++8-->/Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:4:5-79 ++8-->/Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:4:22-77 ++9 ++10 +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml b/node_modules/react-native-create-thumbnail/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +new file mode 100644 +index 0000000..c0d992c +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +@@ -0,0 +1,10 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json b/node_modules/react-native-create-thumbnail/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +new file mode 100644 +index 0000000..0637a08 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +@@ -0,0 +1 @@ ++[] +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt b/node_modules/react-native-create-thumbnail/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +new file mode 100644 +index 0000000..08f4ebe +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +@@ -0,0 +1 @@ ++0 Warning/Error +\ No newline at end of file +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/BuildConfig.class b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/BuildConfig.class +new file mode 100644 +index 0000000..4d3d132 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/BuildConfig.class differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/CreateThumbnailModule.class b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/CreateThumbnailModule.class +new file mode 100644 +index 0000000..2f86f71 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/CreateThumbnailModule.class differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/CreateThumbnailPackage.class b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/CreateThumbnailPackage.class +new file mode 100644 +index 0000000..85e00f9 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/createthumbnail/CreateThumbnailPackage.class differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +new file mode 100644 +index 0000000..eafcfc1 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar differ +diff --git a/node_modules/react-native-create-thumbnail/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt b/node_modules/react-native-create-thumbnail/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +new file mode 100644 +index 0000000..88a2ea6 +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +@@ -0,0 +1 @@ ++com.reactlibrary.createthumbnail +diff --git a/node_modules/react-native-create-thumbnail/android/build/outputs/logs/manifest-merger-debug-report.txt b/node_modules/react-native-create-thumbnail/android/build/outputs/logs/manifest-merger-debug-report.txt +new file mode 100644 +index 0000000..1ac2e6d +--- /dev/null ++++ b/node_modules/react-native-create-thumbnail/android/build/outputs/logs/manifest-merger-debug-report.txt +@@ -0,0 +1,25 @@ ++-- Merging decision tree log --- ++manifest ++ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:1:1-5:12 ++INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:1:1-5:12 ++ package ++ ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:2:11-53 ++ INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml ++ xmlns:android ++ ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:1:11-69 ++uses-permission#android.permission.WRITE_EXTERNAL_STORAGE ++ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:3:5-80 ++ android:name ++ ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:3:22-78 ++uses-permission#android.permission.READ_EXTERNAL_STORAGE ++ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:4:5-79 ++ android:name ++ ADDED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml:4:22-77 ++uses-sdk ++INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml reason: use-sdk injection requested ++INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml ++INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml ++ android:targetSdkVersion ++ INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml ++ android:minSdkVersion ++ INJECTED from /Users/mameo/Internxt_Projects/drive-mobile/node_modules/react-native-create-thumbnail/android/src/main/AndroidManifest.xml +diff --git a/node_modules/react-native-create-thumbnail/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin b/node_modules/react-native-create-thumbnail/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +new file mode 100644 +index 0000000..058b6d1 +Binary files /dev/null and b/node_modules/react-native-create-thumbnail/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin differ +diff --git a/node_modules/react-native-create-thumbnail/ios/CreateThumbnail.m b/node_modules/react-native-create-thumbnail/ios/CreateThumbnail.m +index 4c87cfa..89ba04f 100644 +--- a/node_modules/react-native-create-thumbnail/ios/CreateThumbnail.m ++++ b/node_modules/react-native-create-thumbnail/ios/CreateThumbnail.m +@@ -1,4 +1,5 @@ + #import "CreateThumbnail.h" ++#import + + @implementation CreateThumbnail + +@@ -79,7 +80,18 @@ RCT_EXPORT_METHOD(create:(NSDictionary *)config findEventsWithResolver:(RCTPromi + reject(error.domain, error.description, nil); + }; + +- if ([url_ hasPrefix:@"http://"] || [url_ hasPrefix:@"https://"]) { ++ NSString *fileExt = [[url_ pathExtension] lowercaseString]; ++ BOOL isImageFile = [fileExt isEqualToString:@"jpg"] || ++ [fileExt isEqualToString:@"jpeg"] || ++ [fileExt isEqualToString:@"png"] || ++ [fileExt isEqualToString:@"heic"] || ++ [fileExt isEqualToString:@"heif"]; ++ ++ if (isImageFile) { ++ NSURL *imageURL = [url_ hasPrefix:@"file://"] ? [NSURL URLWithString:url] : [NSURL fileURLWithPath:url]; ++ CGFloat maxSize = MAX(maxWidth, maxHeight); ++ [self generateImageThumbnailAtURL:imageURL maxSize:maxSize completion:completionBlock failure:failBlock]; ++ } else if ([url_ hasPrefix:@"http://"] || [url_ hasPrefix:@"https://"]) { + AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:vidURL options:@{@"AVURLAssetHTTPHeaderFieldsKey": headers}]; + [self generateThumbImage:asset atTime:timeStamp maxWidth:maxWidth maxHeight:maxHeight timeToleranceMs:timeToleranceMs completion:completionBlock failure:failBlock]; + } else { +@@ -120,6 +132,36 @@ RCT_EXPORT_METHOD(create:(NSDictionary *)config findEventsWithResolver:(RCTPromi + return; + } + ++- (void) generateImageThumbnailAtURL:(NSURL *)imageURL maxSize:(CGFloat)maxSize completion:(void (^)(UIImage* thumbnail))completion failure:(void (^)(NSError* error))failure { ++ @autoreleasepool { ++ CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL); ++ if (!source) { ++ NSError *error = [NSError errorWithDomain:@"CreateThumbnail" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Could not create image source from URL"}]; ++ failure(error); ++ return; ++ } ++ ++ NSDictionary *options = @{ ++ (NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxSize), ++ (NSString *)kCGImageSourceCreateThumbnailWithTransform: @YES, ++ (NSString *)kCGImageSourceCreateThumbnailFromImageAlways: @YES ++ }; ++ ++ CGImageRef cgImageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef)options); ++ CFRelease(source); ++ ++ if (!cgImageRef) { ++ NSError *error = [NSError errorWithDomain:@"CreateThumbnail" code:2 userInfo:@{NSLocalizedDescriptionKey: @"Could not generate thumbnail from image source"}]; ++ failure(error); ++ return; ++ } ++ ++ UIImage *thumbnail = [UIImage imageWithCGImage:cgImageRef]; ++ CGImageRelease(cgImageRef); ++ completion(thumbnail); ++ } ++} ++ + - (void) generateThumbImage:(AVURLAsset *)asset atTime:(int)timeStamp maxWidth:(CGFloat)maxWidth maxHeight:(CGFloat)maxHeight timeToleranceMs:(int)timeToleranceMs completion:(void (^)(UIImage* thumbnail))completion failure:(void (^)(NSError* error))failure { + AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; + generator.appliesPreferredTrackTransform = YES; +@@ -141,6 +183,7 @@ RCT_EXPORT_METHOD(create:(NSDictionary *)config findEventsWithResolver:(RCTPromi + - (void) generateLocalMediaThumbImage:(AVAsset *)asset atTime:(int)timeStamp completion:(void (^)(UIImage* thumbnail))completion failure:(void (^)(NSError* error))failure { + AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; + imageGenerator.appliesPreferredTrackTransform = YES; ++ imageGenerator.maximumSize = CGSizeMake(512, 512); + CMTime time = CMTimeMake(timeStamp, 1000); + NSError *error = nil; + CMTime actualTime; diff --git a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx index 9acc3d127..6f2869661 100644 --- a/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx +++ b/src/screens/drive/DrivePreviewScreen/DrivePreviewScreen.tsx @@ -1,4 +1,4 @@ -import { GeneratedThumbnail, imageService } from '@internxt-mobile/services/common'; +import { GeneratedThumbnail, generateVideoThumbnail } 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'; @@ -6,7 +6,7 @@ import { notifications } from '@internxt-mobile/services/NotificationsService'; import { FileExtension, Thumbnail } from '@internxt-mobile/types/drive/file'; import { RootStackScreenProps } from '@internxt-mobile/types/navigation'; import strings from 'assets/lang/strings'; -import { WarningCircle } from 'phosphor-react-native'; +import { WarningCircleIcon } from 'phosphor-react-native'; import React, { useEffect, useRef, useState } from 'react'; import { Animated, useWindowDimensions, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -57,8 +57,7 @@ export const DrivePreviewScreen: React.FC> VIDEO_PREVIEW_TYPES.has(downloadingFile.data.type as FileExtension) && !generatedThumbnail ) { - imageService - .generateVideoThumbnail(downloadingFile.downloadedFilePath) + generateVideoThumbnail(downloadingFile.downloadedFilePath) .then((generatedThumbnail) => { setGeneratedThumbnail(generatedThumbnail); }) @@ -184,8 +183,8 @@ export const DrivePreviewScreen: React.FC> {error ? ( - - {error} + + {error} {downloadingFile && error !== strings.messages.downloadLimit && ( void; } -export const canGenerateThumbnail = (fileExtension: string): boolean => { - const extension = fileExtension.toLowerCase() as FileExtension; - return IMAGE_PREVIEW_TYPES.has(extension) || VIDEO_PREVIEW_TYPES.has(extension) || PDF_PREVIEW_TYPES.has(extension); -}; +export const canGenerateThumbnail = isThumbnailSupported; export const shouldRegenerateThumbnail = (params: ThumbnailRegenerationParams): boolean => { const { downloadedFilePath, fileExtension, hasThumbnails } = params; diff --git a/src/services/common/media/image.service.ts b/src/services/common/media/image.service.ts index 2e991d5d7..2e6a56bb1 100644 --- a/src/services/common/media/image.service.ts +++ b/src/services/common/media/image.service.ts @@ -1,24 +1,13 @@ -import { manipulateAsync, SaveFormat } from 'expo-image-manipulator'; +import { ImageManipulator, SaveFormat } from 'expo-image-manipulator'; +import * as RNFS from '@dr.pogodin/react-native-fs'; import fileSystemService, { fs } from '@internxt-mobile/services/FileSystemService'; -import { FileExtension } from '@internxt-mobile/types/drive/file'; -import * as RNFS from '@dr.pogodin/react-native-fs'; +import { isThumbnailSupported } from './thumbnail.constants'; +import { generateThumbnail as generateThumbnailShared } from './thumbnail.generation'; +export type { GeneratedThumbnail } from './thumbnail.types'; -import ReactNativeBlobUtil from 'react-native-blob-util'; -import { createThumbnail } from 'react-native-create-thumbnail'; -import PdfThumbnail from 'react-native-pdf-thumbnail'; -import uuid from 'react-native-uuid'; - -export type GeneratedThumbnail = { - size: number; - type: string; - width: number; - height: number; - path: string; -}; export const PROFILE_PICTURE_CACHE_KEY = 'PROFILE_PICTURE'; -const MAX_THUMBNAIL_WIDTH = 512; export type ThumbnailGenerateConfig = { outputPath: string; quality?: number; @@ -27,23 +16,6 @@ export type ThumbnailGenerateConfig = { }; class ImageService { - private get thumbnailGenerators(): Record< - FileExtension, - (filePath: string, config: ThumbnailGenerateConfig) => Promise - > { - return { - [FileExtension.AVI]: this.generateVideoThumbnail, - [FileExtension.MP4]: this.generateVideoThumbnail, - [FileExtension.MOV]: this.generateVideoThumbnail, - [FileExtension.JPEG]: this.generateImageThumbnail, - [FileExtension.JPG]: this.generateImageThumbnail, - [FileExtension.PNG]: this.generateImageThumbnail, - [FileExtension.HEIC]: this.generateImageThumbnail, - [FileExtension.PDF]: this.generatePdfThumbnail, - }; - } - public readonly BASE64_PREFIX = 'data:image/png;base64,'; - public async resize({ uri, width, @@ -67,21 +39,10 @@ class ImageService { } }; - const result = await manipulateAsync( - getRequiredUriFormat(), - [ - { - resize: { - width, - height, - }, - }, - ], - { - format: SaveFormat.JPEG, - compress: quality / 100, - }, - ); + const imageManipulatorContext = ImageManipulator.manipulate(getRequiredUriFormat()); + imageManipulatorContext.resize({ width, height }); + const imageRef = await imageManipulatorContext.renderAsync(); + const result = await imageRef.saveAsync({ format: SaveFormat.JPEG, compress: quality / 100 }); const stat = await fileSystemService.statRNFS(result.uri); if (outputPath && !(await fileSystemService.exists(outputPath))) { @@ -95,10 +56,6 @@ class ImageService { }; } - public async pathToBase64(uri: string): Promise { - return await ReactNativeBlobUtil.fs.readFile(uri, 'base64'); - } - /** * Cache an image from an URL and stores it using a cacheKey, can be * retrieved using getCachedImage() method @@ -150,102 +107,13 @@ class ImageService { public async generateThumbnail( filePath: string, config: { outputPath: string; quality?: number; extension: string; thumbnailFormat: SaveFormat }, - ): Promise { - const generator = this.thumbnailGenerators[config.extension.toLowerCase() as FileExtension]; - - if (!generator) { + ) { + if (!isThumbnailSupported(config.extension)) { // eslint-disable-next-line no-console console.error(`Cannot generate thumbnail for extension ${config.extension}`); - return null; } - return this.resizeThumbnail(await generator(filePath, config)); - } - - /** - * 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, - }; - }; - - /** - * Generates a thumbnail for an image - */ - public generateImageThumbnail = async ( - filePath: string, - config: ThumbnailGenerateConfig, - ): Promise => { - const result = await this.resize({ - uri: filePath, - outputPath: config.outputPath, - width: config.width || MAX_THUMBNAIL_WIDTH, - height: config.height, - format: 'JPEG', - quality: 80, - }); - - return { - size: result.size, - type: 'JPEG', - width: result.width, - height: result.height, - path: result.path, - }; - }; - - /** Generates a thumbnail from a PDF file */ - public generatePdfThumbnail = async ( - filePath: string, - config: ThumbnailGenerateConfig, - ): Promise => { - const neededPath = filePath.startsWith('file:///') ? filePath : `file:///${filePath}`; - const result = await PdfThumbnail.generate(neededPath, 0, config.quality || 80); - // The library has some problems if the URI contains spaces - const outputPath = decodeURI(result.uri); - if (!(await fileSystemService.exists(config.outputPath))) { - await fileSystemService.moveFile(outputPath, config.outputPath); - } - - const stat = await fileSystemService.statRNFS(config.outputPath); - return { - path: config.outputPath, - width: result.width, - height: result.height, - size: stat.size, - type: 'JPEG', - }; - }; - - private async resizeThumbnail(originThumbnail: GeneratedThumbnail): Promise { - const destination = fileSystemService.tmpFilePath(uuid.v4().toString()); - - const result = await this.resize({ - uri: originThumbnail.path, - width: MAX_THUMBNAIL_WIDTH, - quality: 80, - format: 'JPEG', - outputPath: destination, - }); - - return { - width: result.width, - height: result.height, - path: result.path, - size: result.size, - type: 'JPEG', - }; + return generateThumbnailShared(filePath, config.extension); } } diff --git a/src/services/common/media/index.ts b/src/services/common/media/index.ts index 011879980..ab8e9ca9e 100644 --- a/src/services/common/media/index.ts +++ b/src/services/common/media/index.ts @@ -1 +1,4 @@ export * from './image.service'; +export * from './thumbnail.constants'; +export * from './thumbnail.types'; +export * from './thumbnail.generation'; diff --git a/src/services/common/media/thumbnail.constants.ts b/src/services/common/media/thumbnail.constants.ts new file mode 100644 index 000000000..e15530479 --- /dev/null +++ b/src/services/common/media/thumbnail.constants.ts @@ -0,0 +1,24 @@ +import { FileExtension } from '@internxt-mobile/types/drive/file'; + +export const THUMBNAIL_MAX_WIDTH = 512; +export const THUMBNAIL_JPEG_COMPRESS = 0.8; +export const VIDEO_THUMBNAIL_DIR_SIZE = 100; +export const PDF_THUMBNAIL_QUALITY = 80; + +export const IMAGE_THUMBNAIL_EXTENSIONS = new Set([ + FileExtension.JPG, + FileExtension.JPEG, + FileExtension.PNG, + FileExtension.HEIC, +]); +export const VIDEO_THUMBNAIL_EXTENSIONS = new Set([FileExtension.MP4, FileExtension.MOV, FileExtension.AVI]); +export const PDF_THUMBNAIL_EXTENSIONS = new Set([FileExtension.PDF]); + +export const isThumbnailSupported = (extension: string): boolean => { + const extensionLower = extension.toLowerCase(); + return ( + IMAGE_THUMBNAIL_EXTENSIONS.has(extensionLower) || + VIDEO_THUMBNAIL_EXTENSIONS.has(extensionLower) || + PDF_THUMBNAIL_EXTENSIONS.has(extensionLower) + ); +}; diff --git a/src/services/common/media/thumbnail.generation.ts b/src/services/common/media/thumbnail.generation.ts new file mode 100644 index 000000000..3924526a9 --- /dev/null +++ b/src/services/common/media/thumbnail.generation.ts @@ -0,0 +1,63 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; +import { ImageManipulator, SaveFormat } from 'expo-image-manipulator'; +import { Platform } from 'react-native'; +import { createThumbnail } from 'react-native-create-thumbnail'; +import PdfThumbnail from 'react-native-pdf-thumbnail'; + +import { + IMAGE_THUMBNAIL_EXTENSIONS, + PDF_THUMBNAIL_QUALITY, + THUMBNAIL_JPEG_COMPRESS, + THUMBNAIL_MAX_WIDTH, + VIDEO_THUMBNAIL_DIR_SIZE, + VIDEO_THUMBNAIL_EXTENSIONS, +} from './thumbnail.constants'; +import type { GeneratedThumbnail } from './thumbnail.types'; + +const toFileUri = (path: string): string => (path.startsWith('file://') ? path : `file://${path}`); + +const statSize = async (path: string): Promise => Number((await RNFS.stat(path)).size); + +const generateImageThumbnailAndroid = async (sourcePath: string): Promise => { + const imageManipulatorContext = ImageManipulator.manipulate(toFileUri(sourcePath)); + imageManipulatorContext.resize({ width: THUMBNAIL_MAX_WIDTH }); + const imageRef = await imageManipulatorContext.renderAsync(); + const result = await imageRef.saveAsync({ format: SaveFormat.JPEG, compress: THUMBNAIL_JPEG_COMPRESS }); + imageRef.release(); + imageManipulatorContext.release(); + const path = result.uri.replace('file://', ''); + return { path, width: result.width, height: result.height, size: await statSize(path), type: 'JPEG' }; +}; + +const generateMediaThumbnail = async (sourcePath: string): Promise => { + const result = await createThumbnail({ + url: toFileUri(sourcePath), + dirSize: VIDEO_THUMBNAIL_DIR_SIZE, + maxWidth: THUMBNAIL_MAX_WIDTH, + maxHeight: THUMBNAIL_MAX_WIDTH, + }); + const path = result.path.replace('file://', ''); + return { path, width: result.width, height: result.height, size: await statSize(path), type: 'JPEG' }; +}; + +// iOS uses the patched react-native-create-thumbnail +// instead of expo-image-manipulator: subsampled decode avoids loading the full bitmap +// into memory, preventing jetsam kills in the share extension +export const generateImageThumbnail = async (sourcePath: string): Promise => + Platform.OS === 'android' ? generateImageThumbnailAndroid(sourcePath) : generateMediaThumbnail(sourcePath); + +export const generateVideoThumbnail = (sourcePath: string): Promise => + generateMediaThumbnail(sourcePath); + +export const generatePdfThumbnail = async (sourcePath: string): Promise => { + const result = await PdfThumbnail.generate(toFileUri(sourcePath), 0, PDF_THUMBNAIL_QUALITY); + const path = result.uri.replace('file://', ''); + return { path, width: result.width, height: result.height, size: await statSize(path), type: 'JPEG' }; +}; + +export const generateThumbnail = async (sourcePath: string, extension: string): Promise => { + const extensionLower = extension.toLowerCase(); + if (IMAGE_THUMBNAIL_EXTENSIONS.has(extensionLower)) return generateImageThumbnail(sourcePath); + if (VIDEO_THUMBNAIL_EXTENSIONS.has(extensionLower)) return generateVideoThumbnail(sourcePath); + return generatePdfThumbnail(sourcePath); +}; diff --git a/src/services/common/media/thumbnail.types.ts b/src/services/common/media/thumbnail.types.ts new file mode 100644 index 000000000..e0b0d8f16 --- /dev/null +++ b/src/services/common/media/thumbnail.types.ts @@ -0,0 +1,7 @@ +export interface GeneratedThumbnail { + path: string; + width: number; + height: number; + size: number; + type: string; +} diff --git a/src/shareExtension/ShareExtensionView.android.tsx b/src/shareExtension/ShareExtensionView.android.tsx index b290e78b2..879a6b0fe 100644 --- a/src/shareExtension/ShareExtensionView.android.tsx +++ b/src/shareExtension/ShareExtensionView.android.tsx @@ -20,6 +20,7 @@ const ShareExtensionView = ({ navigation, route }: RootStackScreenProps<'Android status: uploadStatus, errorType: uploadError, progress: uploadProgress, + thumbnailUri, uploadFiles, reset: resetUpload, } = useShareUpload(); @@ -71,6 +72,7 @@ const ShareExtensionView = ({ navigation, route }: RootStackScreenProps<'Android uploadStatus={uploadStatus} uploadError={uploadError} uploadProgress={uploadProgress} + thumbnailUri={thumbnailUri} onClose={handleClose} onSave={handleSave} onViewInFolder={handleViewInFolder} diff --git a/src/shareExtension/ShareExtensionView.ios.tsx b/src/shareExtension/ShareExtensionView.ios.tsx index 15abd42d1..65110f2cc 100644 --- a/src/shareExtension/ShareExtensionView.ios.tsx +++ b/src/shareExtension/ShareExtensionView.ios.tsx @@ -27,7 +27,7 @@ interface ShareExtensionProps { const ShareExtensionView = ({ photosToken, mnemonic, rootFolderId, bucket, bridgeUser, userId, files, images, videos }: ShareExtensionProps) => { const tailwind = useTailwind(); const { sdkReady, sharedFiles } = useShareExtension({ photosToken, mnemonic, bucket, bridgeUser, userId, files, images, videos }); - const { status: uploadStatus, errorType: uploadError, progress: uploadProgress, uploadFiles, reset: resetUpload } = useShareUpload(); + const { status: uploadStatus, errorType: uploadError, progress: uploadProgress, thumbnailUri, uploadFiles, reset: resetUpload } = useShareUpload(); const filesTooLarge = useMemo(() => isIosTotalSizeTooLargeForUpload(sharedFiles), [sharedFiles]); @@ -67,6 +67,7 @@ const ShareExtensionView = ({ photosToken, mnemonic, rootFolderId, bucket, bridg uploadError={uploadError} uploadProgress={uploadProgress} filesTooLarge={filesTooLarge} + thumbnailUri={thumbnailUri} onClose={close} onSave={handleSave} onViewInFolder={handleViewInFolder} diff --git a/src/shareExtension/components/UploadSuccessCard.tsx b/src/shareExtension/components/UploadSuccessCard.tsx index 10e4141e8..129b75311 100644 --- a/src/shareExtension/components/UploadSuccessCard.tsx +++ b/src/shareExtension/components/UploadSuccessCard.tsx @@ -12,6 +12,7 @@ import { formatBytes, getSharedFileExtension } from '../utils'; interface UploadSuccessCardProps { sharedFiles: SharedFile[]; uploadedFileName: string; + thumbnailUri?: string | null; onClose: () => void; onViewInFolder: () => void; } @@ -19,6 +20,7 @@ interface UploadSuccessCardProps { export const UploadSuccessCard = ({ sharedFiles, uploadedFileName, + thumbnailUri, onClose, onViewInFolder, }: UploadSuccessCardProps) => { @@ -41,6 +43,7 @@ export const UploadSuccessCard = ({ const isImage = firstFile?.mimeType?.startsWith('image/') ?? false; const IconComponent = fileExtension ? getFileTypeIcon(fileExtension.toLowerCase()) : null; const imageUri = firstFile?.uri ?? null; + const displayUri = thumbnailUri ?? (isImage ? imageUri : null); const totalSizeOrNull = sharedFiles.some((sharedFile) => sharedFile.size !== null) ? sharedFiles.reduce((totalSizeAcc, sharedFile) => totalSizeAcc + (sharedFile.size ?? 0), 0) @@ -56,8 +59,8 @@ export const UploadSuccessCard = ({ : (formattedSize ?? ''); const renderFilePreview = () => { - if (isSingleFile && isImage && imageUri) { - return ; + if (isSingleFile && displayUri) { + return ; } if (isSingleFile && IconComponent) { return ; diff --git a/src/shareExtension/hooks/useShareUpload.ts b/src/shareExtension/hooks/useShareUpload.ts index a6c33b562..8bc4e9ac4 100644 --- a/src/shareExtension/hooks/useShareUpload.ts +++ b/src/shareExtension/hooks/useShareUpload.ts @@ -1,5 +1,6 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { Platform } from 'react-native'; +import * as RNFS from '@dr.pogodin/react-native-fs'; import { FileTooLargeError, MissingFileUriError, UploadNetworkError } from '../errors'; import { ShareUploadCredentials, @@ -19,6 +20,7 @@ interface UseShareUploadResult { status: UploadStatus; errorType: UploadErrorType | null; progress: UploadProgress | null; + thumbnailUri: string | null; uploadFiles: ( files: SharedFile[], folderUuid: string, @@ -50,11 +52,18 @@ export const useShareUpload = (): UseShareUploadResult => { const [status, setStatus] = useState('idle'); const [errorType, setErrorType] = useState(null); const [progress, setProgress] = useState(null); + const [thumbnailUri, setThumbnailUri] = useState(null); + const thumbnailUriRef = useRef(null); const reset = useCallback(() => { + if (thumbnailUriRef.current) { + RNFS.unlink(thumbnailUriRef.current).catch(() => undefined); + thumbnailUriRef.current = null; + } setStatus('idle'); setErrorType(null); setProgress(null); + setThumbnailUri(null); }, []); const uploadFiles = useCallback( @@ -69,6 +78,8 @@ export const useShareUpload = (): UseShareUploadResult => { } setStatus('uploading'); + setThumbnailUri(null); + const isSingleFile = files.length === 1; const shareUploadSession = createShareUploadSession(credentials); for (let i = 0; i < files.length; i++) { @@ -90,9 +101,9 @@ export const useShareUpload = (): UseShareUploadResult => { setProgress(buildProgress(0)); - const finalFileName = files.length === 1 && renamedFileName ? renamedFileName : currentSharedFile.fileName; + const finalFileName = isSingleFile && renamedFileName ? renamedFileName : currentSharedFile.fileName; - await shareUploadFile({ + const result = await shareUploadFile({ filePath: currentSharedFile.uri, fileName: getFileNameWithoutExtension(finalFileName), fileExtension: getFileExtension(finalFileName), @@ -106,6 +117,11 @@ export const useShareUpload = (): UseShareUploadResult => { }, onProgress: (bytesUploaded) => setProgress(buildProgress(bytesUploaded)), }); + + if (isSingleFile) { + thumbnailUriRef.current = result.thumbnailLocalUri; + setThumbnailUri(result.thumbnailLocalUri); + } } setStatus('success'); @@ -117,5 +133,5 @@ export const useShareUpload = (): UseShareUploadResult => { [], ); - return { status, errorType, progress, uploadFiles, reset }; + return { status, errorType, progress, thumbnailUri, uploadFiles, reset }; }; diff --git a/src/shareExtension/screens/DriveScreen.tsx b/src/shareExtension/screens/DriveScreen.tsx index 820bc9eb4..3d926d7ad 100644 --- a/src/shareExtension/screens/DriveScreen.tsx +++ b/src/shareExtension/screens/DriveScreen.tsx @@ -23,6 +23,7 @@ interface DriveScreenProps { uploadError: UploadErrorType | null; uploadProgress?: UploadProgress | null; filesTooLarge?: boolean; + thumbnailUri?: string | null; onClose: () => void; onSave: (destinationFolderUuid: string, renamedFileName?: string) => void; onViewInFolder: (folderUuid: string) => void; @@ -36,6 +37,7 @@ export const DriveScreen = ({ uploadError, uploadProgress, filesTooLarge = false, + thumbnailUri, onClose, onSave, onViewInFolder, @@ -184,6 +186,7 @@ export const DriveScreen = ({ diff --git a/src/shareExtension/services/shareThumbnailService.ts b/src/shareExtension/services/shareThumbnailService.ts new file mode 100644 index 000000000..aeb4c8206 --- /dev/null +++ b/src/shareExtension/services/shareThumbnailService.ts @@ -0,0 +1,82 @@ +import * as RNFS from '@dr.pogodin/react-native-fs'; +import { EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types'; +import { uploadFile } from '@internxt/sdk/dist/network/upload'; +import { Buffer } from 'buffer'; +import uuid from 'react-native-uuid'; +import { isThumbnailSupported } from '../../services/common/media/thumbnail.constants'; +import { generateThumbnail } from '../../services/common/media/thumbnail.generation'; +import type { GeneratedThumbnail } from '../../services/common/media/thumbnail.types'; +import { computeRipemd160Digest, encryptFileForUpload } from './shareEncryptionService'; +import ShareSdkManager from './ShareSdkManager'; +import { getTmpPath, ShareUploadSession, uploadEncryptedFile } from './shareUploadService'; + +const uploadAndRegisterThumbnail = async ( + thumbnail: GeneratedThumbnail, + fileUuid: string, + bucket: string, + mnemonic: string, + session: ShareUploadSession, +): Promise => { + const { network, cryptoLib } = session; + const encryptedPath = getTmpPath(`${uuid.v4()}_thumb.enc`); + let encryptedHash: string | undefined; + + try { + const thumbnailFileId = await uploadFile( + network, + cryptoLib, + bucket, + mnemonic, + thumbnail.size, + async (_algorithm, key, iv) => { + await encryptFileForUpload(thumbnail.path, encryptedPath, key as Buffer, iv as Buffer); + const sha256Hex = await RNFS.hash(encryptedPath, 'sha256'); + encryptedHash = computeRipemd160Digest(Buffer.from(sha256Hex, 'hex')).toString('hex'); + }, + async (url: string) => { + if (!encryptedHash) throw new Error('invariant: encryptedHash not assigned'); + await uploadEncryptedFile(url, encryptedPath); + return encryptedHash; + }, + ); + + await ShareSdkManager.storageV2.createThumbnailEntryWithUUID({ + fileUuid, + type: thumbnail.type, + size: thumbnail.size, + maxWidth: thumbnail.width, + maxHeight: thumbnail.height, + bucketId: bucket, + bucketFile: thumbnailFileId, + encryptVersion: EncryptionVersion.Aes03, + }); + } finally { + await RNFS.unlink(encryptedPath).catch(() => undefined); + } +}; + +export const generateAndUploadThumbnail = async ( + sourceFilePath: string, + fileExtension: string, + fileUuid: string, + bucket: string, + mnemonic: string, + session: ShareUploadSession, +): Promise => { + if (!isThumbnailSupported(fileExtension)) return null; + + let thumbnail: GeneratedThumbnail; + try { + thumbnail = await generateThumbnail(sourceFilePath, fileExtension); + } catch { + return null; + } + + try { + await uploadAndRegisterThumbnail(thumbnail, fileUuid, bucket, mnemonic, session); + } catch (error) { + console.error('Failed to upload thumbnail, proceeding without it, error:', error); + } + + return thumbnail.path; +}; diff --git a/src/shareExtension/services/shareUploadService.ts b/src/shareExtension/services/shareUploadService.ts index 804dd0b61..23c5e3402 100644 --- a/src/shareExtension/services/shareUploadService.ts +++ b/src/shareExtension/services/shareUploadService.ts @@ -18,6 +18,7 @@ import { encryptFileIntoMultipartChunks, } from './shareEncryptionService'; import ShareSdkManager from './ShareSdkManager'; +import { generateAndUploadThumbnail } from './shareThumbnailService'; export interface ShareUploadCredentials { bridgeUser: string; @@ -80,7 +81,7 @@ export const createShareUploadSession = (credentials: ShareUploadCredentials): S return { network, cryptoLib: buildSdkEncryptionAdapter() }; }; -const getTmpPath = (filename: string): string => { +export const getTmpPath = (filename: string): string => { const tempBaseDirectory = Platform.OS === 'android' ? ANDROID_TMP_DIR : RNFS.TemporaryDirectoryPath; return `${tempBaseDirectory}${filename}`; }; @@ -157,7 +158,7 @@ const sendFilePutRequest = async ( return response; }; -const uploadEncryptedFile = async ( +export const uploadEncryptedFile = async ( url: string, encryptedFilePath: string, onProgress?: (bytesWritten: number) => void, @@ -185,9 +186,9 @@ const createFileEntry = async ( fileName: string, bucket: string, folderUuid: string, -): Promise => { +) => { const now = new Date().toISOString(); - await ShareSdkManager.storageV2.createFileEntryByUuid({ + return ShareSdkManager.storageV2.createFileEntryByUuid({ fileId, type: fileExtension, size: fileSize, @@ -213,7 +214,7 @@ interface UploadFileContext { onProgress?: (bytesUploaded: number) => void; } -const shareUploadSingleFile = async (context: UploadFileContext): Promise => { +const shareUploadSingleFile = async (context: UploadFileContext): Promise<{ fileUuid: string }> => { const { network, cryptoLib, localPath, fileSize, bucket, mnemonic, folderUuid, fileName, fileExtension, onProgress } = context; const encryptedTempFilePath = getTmpPath(`${uuid.v4()}.enc`); @@ -236,14 +237,15 @@ const shareUploadSingleFile = async (context: UploadFileContext): Promise return encryptedFileHash; }, ); - await createFileEntry(uploadedFileId, fileExtension, fileSize, fileName, bucket, folderUuid); + const fileEntry = await createFileEntry(uploadedFileId, fileExtension, fileSize, fileName, bucket, folderUuid); + return { fileUuid: fileEntry.uuid }; } finally { const exists = await RNFS.exists(encryptedTempFilePath); if (exists) await RNFS.unlink(encryptedTempFilePath); } }; -const shareUploadMultipartFile = async (context: UploadFileContext): Promise => { +const shareUploadMultipartFile = async (context: UploadFileContext): Promise<{ fileUuid: string }> => { const { network, cryptoLib, localPath, fileSize, bucket, mnemonic, folderUuid, fileName, fileExtension, onProgress } = context; const totalPartsCount = Math.ceil(fileSize / PART_SIZE_BYTES); @@ -293,7 +295,8 @@ const shareUploadMultipartFile = async (context: UploadFileContext): Promise @@ -305,7 +308,11 @@ const shareUploadMultipartFile = async (context: UploadFileContext): Promise => { +export interface ShareUploadFileResult { + thumbnailLocalUri: string | null; +} + +export const shareUploadFile = async (params: ShareUploadFileParams): Promise => { const { filePath, fileName, fileExtension, folderUuid, credentials, shareUploadSession, onFileResolved, onProgress } = params; const { mnemonic, bucket } = credentials; @@ -313,8 +320,10 @@ export const shareUploadFile = async (params: ShareUploadFileParams): Promise= MULTIPART_THRESHOLD_BYTES) { - await shareUploadMultipartFile(uploadContext); + uploadResult = await shareUploadMultipartFile(uploadContext); } else { - await shareUploadSingleFile(uploadContext); + uploadResult = await shareUploadSingleFile(uploadContext); + } + + try { + thumbnailLocalUri = await generateAndUploadThumbnail( + localPath, + fileExtension, + uploadResult.fileUuid, + bucket, + mnemonic, + session, + ); + } catch (error) { + console.log('Thumbnail generation/upload failed, proceeding without it, error: ', error); } } finally { if (androidTempCopyPath) await RNFS.unlink(androidTempCopyPath).catch(() => undefined); // On iOS the OS provides a sandboxed temp copy of the shared file; clean it up after upload. if (Platform.OS === 'ios') await RNFS.unlink(localPath).catch(() => undefined); } + + return { thumbnailLocalUri }; };