react-native-pdf-jsi 2.2.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,19 +3,41 @@
3
3
  [![npm](https://img.shields.io/npm/v/react-native-pdf-jsi.svg?style=flat-square)](https://www.npmjs.com/package/react-native-pdf-jsi)
4
4
  [![Downloads](https://img.shields.io/npm/dm/react-native-pdf-jsi.svg?style=flat-square)](https://www.npmjs.com/package/react-native-pdf-jsi)
5
5
  [![GitHub stars](https://img.shields.io/github/stars/126punith/react-native-enhanced-pdf.svg?style=flat-square)](https://github.com/126punith/react-native-enhanced-pdf)
6
+ [![Documentation](https://img.shields.io/badge/docs-online-blue.svg?style=flat-square)](https://euphonious-faun-24f4bc.netlify.app/)
6
7
 
7
8
  **The fastest React Native PDF viewer with JSI acceleration - up to 80x faster than traditional bridge!**
8
9
 
10
+ ## 🆓 100% FREE - All Features Included!
11
+
12
+ **Every feature is FREE and MIT licensed - no hidden costs, no Pro tier, no subscriptions!**
13
+
14
+ All advanced features that were previously paid are now completely FREE:
15
+ - ✅ **Bookmarks with 10 Colors** - Create, organize with custom colors
16
+ - ✅ **Reading Analytics** - Track progress, sessions, and insights
17
+ - ✅ **Export to Images** - PNG/JPEG export with quality control
18
+ - ✅ **PDF Operations** - Split, merge, extract, rotate, delete pages
19
+ - ✅ **PDF Compression** - Reduce file sizes with smart presets
20
+ - ✅ **Text Extraction** - Extract and search text from PDFs
21
+ - ✅ **File Management** - Download to storage, open folders (Android)
22
+ - ✅ **All Performance Features** - JSI acceleration, smart caching
23
+
24
+ **Use commercially without restrictions - MIT License!**
25
+
26
+ 📚 **[Complete Documentation Website](https://euphonious-faun-24f4bc.netlify.app/)** - API Reference, Guides, and Examples
27
+
9
28
  ### Key Advantages:
29
+ - 🆓 **100% FREE** - All features MIT licensed, no subscriptions or hidden fees
10
30
  - ✅ **Google Play 16KB Compliant** - Ready for Android 15+ requirements
11
- - ⚡ **High Performance** - JSI integration for faster rendering
31
+ - ⚡ **High Performance** - JSI integration for faster rendering (80x faster)
12
32
  - 🚀 **Easy Migration** - Drop-in replacement for existing PDF libraries
13
- - 📄 **Lazy Loading** - Optimized loading for large PDF files
33
+ - 📄 **Advanced Features** - Bookmarks, analytics, export, compression all FREE
14
34
  - 🎯 **Smart Caching** - 30-day persistent cache system
15
- - 🛡️ **Future-Proof** - Built with latest NDK r27+ and modern toolchain
35
+ - 🛡️ **Future-Proof** - Built with latest NDK r28.2+ and modern toolchain
16
36
 
17
37
  A high-performance React Native PDF viewer component with JSI (JavaScript Interface) integration for enhanced speed and efficiency. Perfect for large PDF files with lazy loading, smart caching, progressive loading, and zero-bridge overhead operations.
18
38
 
39
+ **🎓 [Read Full Documentation](https://euphonious-faun-24f4bc.netlify.app/)** - Complete guides, API reference, and examples
40
+
19
41
  ## ✅ **Google Play 16KB Page Size Compliance**
20
42
 
21
43
  Starting November 1, 2025, Google Play will require apps to support 16KB page sizes for devices with Android 15+. **react-native-pdf-jsi is built with NDK r27+ and fully supports Android 15+ requirements**, ensuring your app meets Google Play policy requirements.
@@ -35,6 +57,35 @@ Starting November 1, 2025, Google Play will require apps to support 16KB page si
35
57
  - ✅ **Google Play Approved** - Meets all current and future requirements
36
58
  - ✅ **Drop-in Replacement** - Easy migration from existing libraries
37
59
 
60
+ ## 🎉 Version 3.0.0 - Major Release with Complete Feature Sync!
61
+
62
+ **Complete synchronization of all features from development package with enhanced Android capabilities!**
63
+
64
+ ### 🚀 **What's New in v3.0.0:**
65
+ - **📦 Complete Feature Sync** - All features from local development package now in production
66
+ - **📥 FileDownloader Module** - Native Android module for downloading files to public storage using MediaStore API
67
+ - **📂 FileManager Module** - Native Android module for opening Downloads folder with multiple fallback strategies
68
+ - **🔧 PDFTextExtractor Utility** - JavaScript wrapper for native text extraction with search capabilities
69
+ - **✅ Enhanced Structure** - Complete src/utils directory with all utility modules
70
+ - **✅ Android 10+ Support** - Scoped Storage compliant with MediaStore API for Android 10+
71
+ - **✅ Legacy Support** - Backward compatible with Android 9 and below using legacy storage
72
+ - **✅ Smart Notifications** - Download completion notifications with "Open Folder" action
73
+ - **✅ Text Search & Statistics** - Advanced text extraction with search and statistics features
74
+ - **✅ Production Ready** - All development features now available in stable release
75
+
76
+ ## 🎉 Version 2.2.8 - Android File Download & Management Features!
77
+
78
+ **New Android native modules for file download and folder management with MediaStore API support!**
79
+
80
+ ### 🚀 **What's New in v2.2.8:**
81
+ - **📥 FileDownloader Module** - Native Android module for downloading files to public storage using MediaStore API
82
+ - **📂 FileManager Module** - Native Android module for opening Downloads folder with multiple fallback strategies
83
+ - **✅ Android 10+ Support** - Scoped Storage compliant with MediaStore API for Android 10+
84
+ - **✅ Legacy Support** - Backward compatible with Android 9 and below using legacy storage
85
+ - **✅ Smart Notifications** - Download completion notifications with "Open Folder" action
86
+ - **✅ Immediate Visibility** - Files are immediately visible in file managers after export
87
+ - **✅ Multi-Strategy Folder Opening** - Multiple fallback strategies for maximum device compatibility
88
+
38
89
  ## 🎉 Version 2.2.7 - iOS Codegen Fix & New Architecture Support!
39
90
 
40
91
  **Critical fix for React Native 0.79+ compatibility and iOS codegen integration!**
@@ -170,9 +221,11 @@ Starting November 1, 2025, Google Play will require apps to support 16KB page si
170
221
  - **Enhanced Features**: Additional functionality out of the box
171
222
  - **Easy Upgrade**: Minimal code changes required
172
223
 
173
- ## ✨ Features
224
+ ## ✨ Features - All FREE!
225
+
226
+ **📚 [Explore All Features in Documentation](https://euphonious-faun-24f4bc.netlify.app/docs/features/core-features)**
174
227
 
175
- ### Core Features
228
+ ### Core Features (FREE)
176
229
  * Read a PDF from URL, blob, local file or asset and can cache it
177
230
  * Display horizontally or vertically
178
231
  * Drag and zoom
@@ -180,7 +233,7 @@ Starting November 1, 2025, Google Play will require apps to support 16KB page si
180
233
  * Support password protected PDF
181
234
  * Jump to a specific page in the PDF
182
235
 
183
- ### 🚀 JSI Enhanced Features
236
+ ### 🚀 JSI Enhanced Features (FREE)
184
237
  * **Zero Bridge Overhead** - Direct JavaScript-to-Native communication
185
238
  * **Enhanced Caching** - Multi-level intelligent caching system
186
239
  * **Batch Operations** - Process multiple operations efficiently
@@ -193,6 +246,19 @@ Starting November 1, 2025, Google Play will require apps to support 16KB page si
193
246
  * **React Hooks** - Easy integration with `usePDFJSI` hook
194
247
  * **Enhanced Components** - Drop-in replacement with automatic JSI detection
195
248
 
249
+ ### 🎁 Advanced Features (100% FREE!)
250
+ * **📚 Bookmarks with 10 Colors** - Create, edit, delete bookmarks with custom colors and notes
251
+ * **📊 Reading Analytics** - Track reading sessions, progress, speed, and engagement scores
252
+ * **🖼️ Export to Images** - Export pages to PNG/JPEG with quality control
253
+ * **📝 Export to Text** - Extract text from PDFs with search capabilities
254
+ * **✂️ PDF Operations** - Split, merge, extract, rotate, and delete pages
255
+ * **🗜️ PDF Compression** - Reduce file sizes with 5 smart presets (EMAIL, WEB, MOBILE, PRINT, ARCHIVE)
256
+ * **🔍 Text Extraction** - Extract and search text with statistics and context
257
+ * **📥 File Management** (Android) - Download to public storage, open folders with MediaStore API
258
+ * **🎨 Professional UI Components** - Ready-to-use bookmark, analytics, and export components
259
+
260
+ **All features work immediately - no activation, no license keys, no restrictions!**
261
+
196
262
  ## 📱 Supported Platforms
197
263
 
198
264
  - ✅ **Android** (with full JSI acceleration - up to 80x faster)
@@ -209,8 +275,12 @@ npm install react-native-pdf-jsi react-native-blob-util --save
209
275
  yarn add react-native-pdf-jsi react-native-blob-util
210
276
  ```
211
277
 
278
+ **📚 Need help?** Check our [complete installation guide](https://euphonious-faun-24f4bc.netlify.app/docs/getting-started/installation) with platform-specific instructions.
279
+
212
280
  ## 🚀 **Quick Start**
213
281
 
282
+ **📚 [See Quick Start Guide](https://euphonious-faun-24f4bc.netlify.app/docs/getting-started/quick-start)** for detailed instructions and more examples.
283
+
214
284
  ```jsx
215
285
  // Import the Pdf component from react-native-pdf-jsi
216
286
  const PdfModule = require('react-native-pdf-jsi');
@@ -345,6 +415,8 @@ protected List<ReactPackage> getPackages() {
345
415
 
346
416
  ## 📖 Usage
347
417
 
418
+ **📚 [View Complete Documentation](https://euphonious-faun-24f4bc.netlify.app/)** - Detailed guides, API reference, and working examples
419
+
348
420
  ### Basic Usage
349
421
 
350
422
  ```jsx
@@ -696,6 +768,213 @@ export default function AdvancedJSIExample() {
696
768
  }
697
769
  ```
698
770
 
771
+ ## 📥 **Android File Download & Management** (v2.2.8+)
772
+
773
+ The new Android native modules provide seamless file download and folder management capabilities with full Android 10+ Scoped Storage support.
774
+
775
+ ### FileDownloader Module
776
+
777
+ Download files to public storage with automatic MediaStore API integration for Android 10+ and legacy support for older versions.
778
+
779
+ #### **Features:**
780
+ - ✅ **MediaStore API Support** - Android 10+ Scoped Storage compliant
781
+ - ✅ **Legacy Storage** - Backward compatible with Android 9 and below
782
+ - ✅ **Instant Visibility** - Files appear immediately in file managers
783
+ - ✅ **Smart Notifications** - Download completion notifications with "Open Folder" action
784
+ - ✅ **Multiple Formats** - Supports PDF, PNG, and JPEG files
785
+
786
+ #### **Usage:**
787
+
788
+ ```jsx
789
+ import { NativeModules } from 'react-native';
790
+ const { FileDownloader } = NativeModules;
791
+
792
+ // Download a file to public Downloads/PDFDemoApp folder
793
+ const downloadFile = async () => {
794
+ try {
795
+ const sourcePath = '/path/to/cached/file.pdf';
796
+ const fileName = 'my-document.pdf';
797
+ const mimeType = 'application/pdf'; // or 'image/png', 'image/jpeg'
798
+
799
+ const publicPath = await FileDownloader.downloadToPublicFolder(
800
+ sourcePath,
801
+ fileName,
802
+ mimeType
803
+ );
804
+
805
+ console.log('✅ File downloaded to:', publicPath);
806
+ // Android 10+: /storage/emulated/0/Download/PDFDemoApp/my-document.pdf
807
+
808
+ } catch (error) {
809
+ console.error('❌ Download failed:', error);
810
+ }
811
+ };
812
+ ```
813
+
814
+ #### **API:**
815
+
816
+ ```typescript
817
+ FileDownloader.downloadToPublicFolder(
818
+ sourcePath: string, // Path to source file in app's cache/internal storage
819
+ fileName: string, // Desired file name
820
+ mimeType: string // MIME type: 'application/pdf', 'image/png', 'image/jpeg'
821
+ ): Promise<string> // Returns public file path
822
+ ```
823
+
824
+ #### **How It Works:**
825
+ - **Android 10+**: Uses MediaStore API to create entries in the Downloads collection with proper visibility
826
+ - **Android 9 and below**: Uses legacy `Environment.getExternalStoragePublicDirectory()` with media scanner
827
+ - **Automatic folder creation**: Creates `Downloads/PDFDemoApp` folder if it doesn't exist
828
+ - **Progress notifications**: Shows notification with "Open Folder" action when download completes
829
+
830
+ ### FileManager Module
831
+
832
+ Open the Downloads folder with multiple fallback strategies for maximum compatibility across Android devices.
833
+
834
+ #### **Features:**
835
+ - ✅ **Multi-Strategy Opening** - 4 different fallback strategies
836
+ - ✅ **Maximum Compatibility** - Works with various file manager apps
837
+ - ✅ **Graceful Degradation** - Automatically tries next strategy if one fails
838
+ - ✅ **User-Friendly** - Opens specific folder or general file manager
839
+
840
+ #### **Usage:**
841
+
842
+ ```jsx
843
+ import { NativeModules, Alert } from 'react-native';
844
+ const { FileManager } = NativeModules;
845
+
846
+ // Open Downloads/PDFDemoApp folder
847
+ const openFolder = async () => {
848
+ try {
849
+ await FileManager.openDownloadsFolder();
850
+ console.log('✅ Folder opened successfully');
851
+ } catch (error) {
852
+ // All strategies failed - no file manager available
853
+ Alert.alert(
854
+ 'Info',
855
+ 'Please check Downloads/PDFDemoApp folder in your file manager'
856
+ );
857
+ }
858
+ };
859
+ ```
860
+
861
+ #### **API:**
862
+
863
+ ```typescript
864
+ FileManager.openDownloadsFolder(): Promise<boolean>
865
+ ```
866
+
867
+ #### **Fallback Strategies:**
868
+ 1. **Strategy 1**: Opens specific `Downloads/PDFDemoApp` folder via DocumentsUI
869
+ 2. **Strategy 2**: Opens system Downloads app
870
+ 3. **Strategy 3**: Opens generic Files app
871
+ 4. **Strategy 4**: Shows file picker to let user choose file manager
872
+
873
+ ### Complete Example: Export and Download PDF Pages
874
+
875
+ ```jsx
876
+ import React, { useState } from 'react';
877
+ import { View, Button, Alert, NativeModules } from 'react-native';
878
+
879
+ const { PDFExporter, FileDownloader, FileManager } = NativeModules;
880
+
881
+ const ExportAndDownload = () => {
882
+ const [exporting, setExporting] = useState(false);
883
+
884
+ const exportAndDownloadPages = async (pdfPath, pageNumbers) => {
885
+ setExporting(true);
886
+
887
+ try {
888
+ // Step 1: Export pages to images
889
+ const exportedImages = [];
890
+ for (let page of pageNumbers) {
891
+ const imagePath = await PDFExporter.exportPageToImage(
892
+ pdfPath,
893
+ page - 1, // Convert to 0-indexed
894
+ {
895
+ format: 'png',
896
+ quality: 0.9,
897
+ scale: 2.0
898
+ }
899
+ );
900
+ exportedImages.push(imagePath);
901
+ }
902
+
903
+ // Step 2: Download to public storage
904
+ const downloadedFiles = [];
905
+ for (let i = 0; i < exportedImages.length; i++) {
906
+ const publicPath = await FileDownloader.downloadToPublicFolder(
907
+ exportedImages[i],
908
+ `page-${pageNumbers[i]}.png`,
909
+ 'image/png'
910
+ );
911
+ downloadedFiles.push(publicPath);
912
+ }
913
+
914
+ setExporting(false);
915
+
916
+ // Step 3: Show success and offer to open folder
917
+ Alert.alert(
918
+ '✅ Export Complete',
919
+ `${downloadedFiles.length} pages saved to Downloads/PDFDemoApp`,
920
+ [
921
+ { text: 'Done', style: 'cancel' },
922
+ {
923
+ text: 'Open Folder',
924
+ onPress: async () => {
925
+ try {
926
+ await FileManager.openDownloadsFolder();
927
+ } catch (e) {
928
+ Alert.alert('Info', 'Check Downloads/PDFDemoApp folder');
929
+ }
930
+ }
931
+ }
932
+ ]
933
+ );
934
+
935
+ } catch (error) {
936
+ setExporting(false);
937
+ Alert.alert('Export Failed', error.message);
938
+ }
939
+ };
940
+
941
+ return (
942
+ <View>
943
+ <Button
944
+ title={exporting ? 'Exporting...' : 'Export Pages 1-3'}
945
+ onPress={() => exportAndDownloadPages('/path/to/file.pdf', [1, 2, 3])}
946
+ disabled={exporting}
947
+ />
948
+ </View>
949
+ );
950
+ };
951
+
952
+ export default ExportAndDownload;
953
+ ```
954
+
955
+ ### Android Permissions
956
+
957
+ For Android 10+ (API 29+), the MediaStore API doesn't require `WRITE_EXTERNAL_STORAGE` permission for adding files to public Downloads folder. However, for Android 9 and below, you may need to add:
958
+
959
+ ```xml
960
+ <!-- android/app/src/main/AndroidManifest.xml -->
961
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
962
+ ```
963
+
964
+ ### Customization
965
+
966
+ You can customize the folder name by modifying the `FOLDER_NAME` constant in the native modules:
967
+
968
+ ```java
969
+ // android/src/main/java/org/wonday/pdf/FileDownloader.java
970
+ private static final String FOLDER_NAME = "YourAppName"; // Change this
971
+ ```
972
+
973
+ ```java
974
+ // android/src/main/java/org/wonday/pdf/FileManager.java
975
+ private static final String FOLDER_NAME = "YourAppName"; // Change this
976
+ ```
977
+
699
978
  ## 🛡️ **ProGuard Configuration (Required for Production)**
700
979
 
701
980
  **IMPORTANT**: For production builds, you MUST add ProGuard rules to prevent obfuscation of JSI classes. Without these rules, your app will crash in release mode.
@@ -1579,11 +1858,20 @@ MIT License - see [LICENSE](LICENSE) file for details.
1579
1858
 
1580
1859
  ## 📞 Support
1581
1860
 
1582
- For issues and questions:
1583
- - GitHub Issues: [react-native-enhanced-pdf](https://github.com/126punith/react-native-enhanced-pdf)
1584
- - Performance Issues: Include JSI stats and performance history
1585
- - Build Issues: Include CMake logs and Android NDK version
1586
- - Contact: punithm300@gmail.com
1861
+ **📚 Documentation Website**: https://euphonious-faun-24f4bc.netlify.app/
1862
+
1863
+ Get help with:
1864
+ - **📖 Complete Guides**: https://euphonious-faun-24f4bc.netlify.app/docs/getting-started/installation
1865
+ - **🔧 API Reference**: https://euphonious-faun-24f4bc.netlify.app/docs/api/pdf-component
1866
+ - **💡 Working Examples**: https://euphonious-faun-24f4bc.netlify.app/docs/examples/basic-viewer
1867
+ - **🐛 GitHub Issues**: https://github.com/126punith/react-native-enhanced-pdf/issues
1868
+ - **💬 Discussions**: https://github.com/126punith/react-native-enhanced-pdf/discussions
1869
+ - **📧 Email**: punithm300@gmail.com
1870
+
1871
+ For bug reports, include:
1872
+ - JSI stats and performance history
1873
+ - CMake logs and Android NDK version
1874
+ - Platform and React Native version
1587
1875
 
1588
1876
  ---
1589
1877
 
@@ -0,0 +1,292 @@
1
+ package org.wonday.pdf;
2
+
3
+ import android.app.NotificationChannel;
4
+ import android.app.NotificationManager;
5
+ import android.app.PendingIntent;
6
+ import android.content.ContentResolver;
7
+ import android.content.ContentValues;
8
+ import android.content.Context;
9
+ import android.content.Intent;
10
+ import android.net.Uri;
11
+ import android.os.Build;
12
+ import android.os.Environment;
13
+ import android.provider.MediaStore;
14
+ import android.util.Log;
15
+ import androidx.core.app.NotificationCompat;
16
+ import androidx.core.app.NotificationManagerCompat;
17
+
18
+ import com.facebook.react.bridge.Promise;
19
+ import com.facebook.react.bridge.ReactApplicationContext;
20
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
21
+ import com.facebook.react.bridge.ReactMethod;
22
+
23
+ import java.io.File;
24
+ import java.io.FileInputStream;
25
+ import java.io.InputStream;
26
+ import java.io.OutputStream;
27
+
28
+ /**
29
+ * FileDownloader - Native module for downloading files to public storage using MediaStore API
30
+ * Ensures files are immediately visible in file managers
31
+ */
32
+ public class FileDownloader extends ReactContextBaseJavaModule {
33
+ private static final String TAG = "FileDownloader";
34
+ private static final String FOLDER_NAME = "PDFDemoApp";
35
+ private static final String NOTIFICATION_CHANNEL_ID = "pdf_exports";
36
+ private final ReactApplicationContext reactContext;
37
+
38
+ public FileDownloader(ReactApplicationContext reactContext) {
39
+ super(reactContext);
40
+ this.reactContext = reactContext;
41
+ createNotificationChannel();
42
+ }
43
+
44
+ /**
45
+ * Create notification channel for export notifications (Android O+)
46
+ */
47
+ private void createNotificationChannel() {
48
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
49
+ CharSequence name = "PDF Exports";
50
+ String description = "Notifications for PDF export operations";
51
+ int importance = NotificationManager.IMPORTANCE_DEFAULT;
52
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance);
53
+ channel.setDescription(description);
54
+
55
+ NotificationManager notificationManager = reactContext.getSystemService(NotificationManager.class);
56
+ if (notificationManager != null) {
57
+ notificationManager.createNotificationChannel(channel);
58
+ Log.i(TAG, "📱 [NOTIFICATION] Channel created");
59
+ }
60
+ }
61
+ }
62
+
63
+ @Override
64
+ public String getName() {
65
+ return "FileDownloader";
66
+ }
67
+
68
+ /**
69
+ * Download file to public Downloads folder using MediaStore API
70
+ *
71
+ * @param sourcePath Path to source file in app's cache
72
+ * @param fileName Name for the downloaded file
73
+ * @param mimeType MIME type (application/pdf, image/png, image/jpeg)
74
+ * @param promise Promise to resolve with public file path
75
+ */
76
+ @ReactMethod
77
+ public void downloadToPublicFolder(String sourcePath, String fileName, String mimeType, Promise promise) {
78
+ try {
79
+ Log.i(TAG, "📥 [DOWNLOAD] START - file: " + fileName + ", type: " + mimeType);
80
+ Log.i(TAG, "📁 [SOURCE] " + sourcePath);
81
+
82
+ // Verify source file exists
83
+ File sourceFile = new File(sourcePath);
84
+ if (!sourceFile.exists()) {
85
+ Log.e(TAG, "❌ [ERROR] Source file not found: " + sourcePath);
86
+ promise.reject("FILE_NOT_FOUND", "Source file not found: " + sourcePath);
87
+ return;
88
+ }
89
+
90
+ Log.i(TAG, "📁 [SOURCE] File exists, size: " + sourceFile.length() + " bytes");
91
+
92
+ String publicPath;
93
+
94
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
95
+ // Android 10+ - Use MediaStore API (Scoped Storage)
96
+ publicPath = downloadUsingMediaStore(sourceFile, fileName, mimeType);
97
+ } else {
98
+ // Android 9 and below - Use legacy public directory
99
+ publicPath = downloadUsingLegacyStorage(sourceFile, fileName);
100
+ }
101
+
102
+ Log.i(TAG, "✅ [DOWNLOAD] SUCCESS - " + publicPath);
103
+
104
+ // Show notification with "Open Folder" action
105
+ showDownloadNotification(1, fileName);
106
+
107
+ promise.resolve(publicPath);
108
+
109
+ } catch (Exception e) {
110
+ Log.e(TAG, "❌ [DOWNLOAD] ERROR", e);
111
+ promise.reject("DOWNLOAD_ERROR", e.getMessage());
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Download using MediaStore API (Android 10+)
117
+ */
118
+ private String downloadUsingMediaStore(File sourceFile, String fileName, String mimeType) throws Exception {
119
+ Log.i(TAG, "📱 [MEDIASTORE] Using MediaStore API for Android 10+");
120
+
121
+ ContentResolver resolver = reactContext.getContentResolver();
122
+
123
+ // Set up content values
124
+ ContentValues values = new ContentValues();
125
+ values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
126
+ values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
127
+ values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + FOLDER_NAME);
128
+ values.put(MediaStore.Downloads.IS_PENDING, 1); // Mark as pending while writing
129
+
130
+ Log.i(TAG, "📁 [MEDIASTORE] Creating entry in MediaStore...");
131
+
132
+ // Insert into MediaStore
133
+ Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
134
+
135
+ if (uri == null) {
136
+ throw new Exception("Failed to create MediaStore entry");
137
+ }
138
+
139
+ Log.i(TAG, "📁 [MEDIASTORE] URI created: " + uri.toString());
140
+ Log.i(TAG, "📥 [COPY] Copying file content...");
141
+
142
+ // Copy file content
143
+ try (InputStream in = new FileInputStream(sourceFile);
144
+ OutputStream out = resolver.openOutputStream(uri)) {
145
+
146
+ if (out == null) {
147
+ throw new Exception("Failed to open output stream");
148
+ }
149
+
150
+ byte[] buffer = new byte[8192];
151
+ int bytesRead;
152
+ long totalBytes = 0;
153
+
154
+ while ((bytesRead = in.read(buffer)) != -1) {
155
+ out.write(buffer, 0, bytesRead);
156
+ totalBytes += bytesRead;
157
+ }
158
+
159
+ Log.i(TAG, "✅ [COPY] Copied " + totalBytes + " bytes");
160
+ }
161
+
162
+ // Mark as complete (no longer pending)
163
+ values.clear();
164
+ values.put(MediaStore.Downloads.IS_PENDING, 0);
165
+ resolver.update(uri, values, null, null);
166
+
167
+ Log.i(TAG, "✅ [MEDIASTORE] File published successfully");
168
+
169
+ // Return user-friendly path
170
+ String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
171
+ + "/" + FOLDER_NAME + "/" + fileName;
172
+
173
+ return publicPath;
174
+ }
175
+
176
+ /**
177
+ * Download using legacy storage (Android 9 and below)
178
+ */
179
+ private String downloadUsingLegacyStorage(File sourceFile, String fileName) throws Exception {
180
+ Log.i(TAG, "📱 [LEGACY] Using legacy storage for Android 9 and below");
181
+
182
+ // Get public Downloads directory
183
+ File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
184
+ File appFolder = new File(downloadsDir, FOLDER_NAME);
185
+
186
+ // Create folder if needed
187
+ if (!appFolder.exists()) {
188
+ boolean created = appFolder.mkdirs();
189
+ Log.i(TAG, "📁 [LEGACY] Folder created: " + created);
190
+ }
191
+
192
+ File destFile = new File(appFolder, fileName);
193
+ Log.i(TAG, "📁 [LEGACY] Destination: " + destFile.getAbsolutePath());
194
+ Log.i(TAG, "📥 [COPY] Copying file...");
195
+
196
+ // Copy file
197
+ try (InputStream in = new FileInputStream(sourceFile);
198
+ OutputStream out = new java.io.FileOutputStream(destFile)) {
199
+
200
+ byte[] buffer = new byte[8192];
201
+ int bytesRead;
202
+ long totalBytes = 0;
203
+
204
+ while ((bytesRead = in.read(buffer)) != -1) {
205
+ out.write(buffer, 0, bytesRead);
206
+ totalBytes += bytesRead;
207
+ }
208
+
209
+ Log.i(TAG, "✅ [COPY] Copied " + totalBytes + " bytes");
210
+ }
211
+
212
+ // Trigger media scanner for legacy devices
213
+ android.media.MediaScannerConnection.scanFile(
214
+ reactContext,
215
+ new String[]{destFile.getAbsolutePath()},
216
+ new String[]{getMimeType(fileName)},
217
+ null
218
+ );
219
+
220
+ Log.i(TAG, "✅ [LEGACY] Media scanner notified");
221
+
222
+ return destFile.getAbsolutePath();
223
+ }
224
+
225
+ /**
226
+ * Get MIME type from file extension
227
+ */
228
+ private String getMimeType(String fileName) {
229
+ if (fileName.endsWith(".pdf")) {
230
+ return "application/pdf";
231
+ } else if (fileName.endsWith(".png")) {
232
+ return "image/png";
233
+ } else if (fileName.endsWith(".jpeg") || fileName.endsWith(".jpg")) {
234
+ return "image/jpeg";
235
+ }
236
+ return "application/octet-stream";
237
+ }
238
+
239
+ /**
240
+ * Show notification after successful download with "Open Folder" action
241
+ * @param fileCount Number of files downloaded
242
+ * @param fileName Name of the file (used for notification text)
243
+ */
244
+ private void showDownloadNotification(int fileCount, String fileName) {
245
+ try {
246
+ Log.i(TAG, "📱 [NOTIFICATION] Showing notification for " + fileCount + " file(s)");
247
+
248
+ // Create intent to open Downloads/PDFDemoApp folder
249
+ Intent intent = new Intent(Intent.ACTION_VIEW);
250
+ Uri folderUri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download/" + FOLDER_NAME);
251
+ intent.setDataAndType(folderUri, "resource/folder");
252
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
253
+
254
+ // Create pending intent with FLAG_IMMUTABLE for Android 12+
255
+ PendingIntent pendingIntent = PendingIntent.getActivity(
256
+ reactContext,
257
+ 0,
258
+ intent,
259
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
260
+ ? PendingIntent.FLAG_IMMUTABLE
261
+ : 0
262
+ );
263
+
264
+ // Build notification
265
+ String contentText = fileCount + " file(s) saved to Downloads/" + FOLDER_NAME;
266
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(reactContext, NOTIFICATION_CHANNEL_ID)
267
+ .setSmallIcon(android.R.drawable.ic_menu_save)
268
+ .setContentTitle("✅ Export Complete")
269
+ .setContentText(contentText)
270
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(contentText))
271
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
272
+ .setContentIntent(pendingIntent)
273
+ .setAutoCancel(true)
274
+ .addAction(android.R.drawable.ic_menu_view, "Open Folder", pendingIntent);
275
+
276
+ // Show notification
277
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(reactContext);
278
+ notificationManager.notify(1001, builder.build());
279
+
280
+ Log.i(TAG, "✅ [NOTIFICATION] Notification shown successfully");
281
+
282
+ } catch (Exception e) {
283
+ Log.e(TAG, "❌ [NOTIFICATION] Failed to show notification", e);
284
+ // Don't throw - notification is non-critical
285
+ }
286
+ }
287
+ }
288
+
289
+
290
+
291
+
292
+