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 +299 -11
- package/android/src/main/java/org/wonday/pdf/FileDownloader.java +292 -0
- package/android/src/main/java/org/wonday/pdf/FileManager.java +123 -0
- package/android/src/main/java/org/wonday/pdf/LicenseVerifier.java +311 -0
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +769 -0
- package/android/src/main/java/org/wonday/pdf/RNPDFPackage.java +7 -0
- package/index.js +58 -0
- package/ios/RNPDFPdf/PDFExporter.h +16 -0
- package/ios/RNPDFPdf/PDFExporter.m +537 -0
- package/package.json +7 -6
- package/src/components/AnalyticsPanel.jsx +243 -0
- package/src/components/BookmarkIndicator.jsx +66 -0
- package/src/components/BookmarkListModal.jsx +378 -0
- package/src/components/BookmarkModal.jsx +253 -0
- package/src/components/BottomSheet.jsx +121 -0
- package/src/components/ExportMenu.jsx +223 -0
- package/src/components/LoadingOverlay.jsx +52 -0
- package/src/components/OperationsMenu.jsx +231 -0
- package/src/components/SidePanel.jsx +95 -0
- package/src/components/Toast.jsx +140 -0
- package/src/components/Toolbar.jsx +135 -0
- package/src/managers/AnalyticsManager.js +695 -0
- package/src/managers/BookmarkManager.js +538 -0
- package/src/managers/ExportManager.js +687 -0
- package/src/managers/FileManager.js +89 -0
- package/src/utils/ErrorHandler.js +179 -0
- package/src/utils/TestData.js +112 -0
package/README.md
CHANGED
|
@@ -3,19 +3,41 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/react-native-pdf-jsi)
|
|
4
4
|
[](https://www.npmjs.com/package/react-native-pdf-jsi)
|
|
5
5
|
[](https://github.com/126punith/react-native-enhanced-pdf)
|
|
6
|
+
[](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
|
-
- 📄 **
|
|
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
|
|
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
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
-
|
|
1586
|
-
-
|
|
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
|
+
|