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 };
};