react-native-pdf-jsi 2.2.8 → 3.0.1
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 +312 -176
- 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 +3 -2
- 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
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { NativeModules } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const { FileManager: NativeFileManager, FileDownloader } = NativeModules;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FileManager - JavaScript wrapper for file operations
|
|
7
|
+
* Provides methods for file management, downloading, and folder access
|
|
8
|
+
*/
|
|
9
|
+
class FileManager {
|
|
10
|
+
/**
|
|
11
|
+
* Open the Downloads folder in the device's file manager
|
|
12
|
+
* Uses multiple fallback strategies for maximum compatibility
|
|
13
|
+
* @returns {Promise<boolean>} Resolves to true if folder opened successfully
|
|
14
|
+
*/
|
|
15
|
+
static async openDownloadsFolder() {
|
|
16
|
+
try {
|
|
17
|
+
console.log('📂 [FileManager] Opening Downloads folder');
|
|
18
|
+
|
|
19
|
+
if (!NativeFileManager) {
|
|
20
|
+
throw new Error('FileManager native module not available');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = await NativeFileManager.openDownloadsFolder();
|
|
24
|
+
console.log('✅ [FileManager] Folder opened successfully');
|
|
25
|
+
return result;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('❌ [FileManager] Error opening folder:', error);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Download file to public Downloads folder using MediaStore API
|
|
34
|
+
* Ensures files are immediately visible in file managers
|
|
35
|
+
*
|
|
36
|
+
* @param {string} sourcePath Path to source file in app's cache
|
|
37
|
+
* @param {string} fileName Name for the downloaded file
|
|
38
|
+
* @param {string} mimeType MIME type (application/pdf, image/png, image/jpeg)
|
|
39
|
+
* @returns {Promise<string>} Resolves to public file path
|
|
40
|
+
*/
|
|
41
|
+
static async downloadToPublicFolder(sourcePath, fileName, mimeType = 'application/pdf') {
|
|
42
|
+
try {
|
|
43
|
+
console.log('📥 [FileManager] Downloading to public folder:', fileName);
|
|
44
|
+
|
|
45
|
+
if (!FileDownloader) {
|
|
46
|
+
throw new Error('FileDownloader native module not available');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const publicPath = await FileDownloader.downloadToPublicFolder(sourcePath, fileName, mimeType);
|
|
50
|
+
console.log('✅ [FileManager] File downloaded:', publicPath);
|
|
51
|
+
return publicPath;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('❌ [FileManager] Error downloading file:', error);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Download multiple files to public Downloads folder
|
|
60
|
+
*
|
|
61
|
+
* @param {Array<{sourcePath: string, fileName: string, mimeType: string}>} files Array of file objects
|
|
62
|
+
* @returns {Promise<Array<string>>} Resolves to array of public file paths
|
|
63
|
+
*/
|
|
64
|
+
static async downloadMultipleFiles(files) {
|
|
65
|
+
try {
|
|
66
|
+
console.log(`📥 [FileManager] Downloading ${files.length} files to public folder`);
|
|
67
|
+
|
|
68
|
+
const results = [];
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const { sourcePath, fileName, mimeType = 'application/pdf' } = file;
|
|
71
|
+
const publicPath = await this.downloadToPublicFolder(sourcePath, fileName, mimeType);
|
|
72
|
+
results.push(publicPath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`✅ [FileManager] Downloaded ${results.length} files successfully`);
|
|
76
|
+
return results;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('❌ [FileManager] Error downloading multiple files:', error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default FileManager;
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ErrorHandler - Comprehensive error handling utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {Alert} from 'react-native';
|
|
6
|
+
|
|
7
|
+
export class PDFError extends Error {
|
|
8
|
+
constructor(message, code, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
this.name = 'PDFError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ErrorCodes = {
|
|
17
|
+
// License errors
|
|
18
|
+
LICENSE_INVALID: 'LICENSE_INVALID',
|
|
19
|
+
LICENSE_EXPIRED: 'LICENSE_EXPIRED',
|
|
20
|
+
LICENSE_REQUIRED: 'LICENSE_REQUIRED',
|
|
21
|
+
|
|
22
|
+
// PDF loading errors
|
|
23
|
+
PDF_NOT_FOUND: 'PDF_NOT_FOUND',
|
|
24
|
+
PDF_CORRUPTED: 'PDF_CORRUPTED',
|
|
25
|
+
PDF_PASSWORD_REQUIRED: 'PDF_PASSWORD_REQUIRED',
|
|
26
|
+
PDF_LOAD_TIMEOUT: 'PDF_LOAD_TIMEOUT',
|
|
27
|
+
|
|
28
|
+
// Network errors
|
|
29
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
30
|
+
NETWORK_TIMEOUT: 'NETWORK_TIMEOUT',
|
|
31
|
+
|
|
32
|
+
// Storage errors
|
|
33
|
+
STORAGE_FULL: 'STORAGE_FULL',
|
|
34
|
+
STORAGE_PERMISSION: 'STORAGE_PERMISSION',
|
|
35
|
+
|
|
36
|
+
// Feature errors
|
|
37
|
+
EXPORT_FAILED: 'EXPORT_FAILED',
|
|
38
|
+
OPERATION_FAILED: 'OPERATION_FAILED',
|
|
39
|
+
BOOKMARK_FAILED: 'BOOKMARK_FAILED',
|
|
40
|
+
|
|
41
|
+
// JSI errors
|
|
42
|
+
JSI_NOT_AVAILABLE: 'JSI_NOT_AVAILABLE',
|
|
43
|
+
NATIVE_MODULE_MISSING: 'NATIVE_MODULE_MISSING',
|
|
44
|
+
|
|
45
|
+
// Generic
|
|
46
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const getUserFriendlyMessage = (error) => {
|
|
50
|
+
if (error instanceof PDFError) {
|
|
51
|
+
switch (error.code) {
|
|
52
|
+
case ErrorCodes.LICENSE_INVALID:
|
|
53
|
+
return 'Your license key is invalid. Please check and try again.';
|
|
54
|
+
case ErrorCodes.LICENSE_EXPIRED:
|
|
55
|
+
return 'Your license has expired. Please renew to continue using Pro features.';
|
|
56
|
+
case ErrorCodes.LICENSE_REQUIRED:
|
|
57
|
+
return 'This feature requires a Pro license. Upgrade to unlock!';
|
|
58
|
+
case ErrorCodes.PDF_NOT_FOUND:
|
|
59
|
+
return 'PDF file not found. Please check the file path.';
|
|
60
|
+
case ErrorCodes.PDF_CORRUPTED:
|
|
61
|
+
return 'This PDF file appears to be corrupted or invalid.';
|
|
62
|
+
case ErrorCodes.PDF_PASSWORD_REQUIRED:
|
|
63
|
+
return 'This PDF is password protected. Password support coming soon.';
|
|
64
|
+
case ErrorCodes.NETWORK_ERROR:
|
|
65
|
+
return 'Network error. Please check your internet connection.';
|
|
66
|
+
case ErrorCodes.NETWORK_TIMEOUT:
|
|
67
|
+
return 'Request timed out. Please try again.';
|
|
68
|
+
case ErrorCodes.STORAGE_FULL:
|
|
69
|
+
return 'Not enough storage space. Please free up some space and try again.';
|
|
70
|
+
case ErrorCodes.STORAGE_PERMISSION:
|
|
71
|
+
return 'Storage permission denied. Please grant permission in settings.';
|
|
72
|
+
case ErrorCodes.EXPORT_FAILED:
|
|
73
|
+
return 'Failed to export PDF. Please try again.';
|
|
74
|
+
case ErrorCodes.JSI_NOT_AVAILABLE:
|
|
75
|
+
return 'High-performance mode not available. Using standard mode.';
|
|
76
|
+
default:
|
|
77
|
+
return error.message;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle standard errors
|
|
82
|
+
if (error.message) {
|
|
83
|
+
// Password errors
|
|
84
|
+
if (error.message.includes('Password') || error.message.includes('password')) {
|
|
85
|
+
return 'This PDF requires a password. Password support coming soon.';
|
|
86
|
+
}
|
|
87
|
+
// Network errors
|
|
88
|
+
if (error.message.includes('Network') || error.message.includes('fetch')) {
|
|
89
|
+
return 'Network error. Please check your connection and try again.';
|
|
90
|
+
}
|
|
91
|
+
// File errors
|
|
92
|
+
if (error.message.includes('not found') || error.message.includes('ENOENT')) {
|
|
93
|
+
return 'File not found. Please try again.';
|
|
94
|
+
}
|
|
95
|
+
// Permission errors
|
|
96
|
+
if (error.message.includes('permission') || error.message.includes('denied')) {
|
|
97
|
+
return 'Permission denied. Please grant necessary permissions.';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return error.message;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return 'An unexpected error occurred. Please try again.';
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const handleError = (
|
|
107
|
+
error,
|
|
108
|
+
context,
|
|
109
|
+
showAlert = true,
|
|
110
|
+
onRetry
|
|
111
|
+
) => {
|
|
112
|
+
const message = getUserFriendlyMessage(error);
|
|
113
|
+
console.error(`❌ Error in ${context}:`, error);
|
|
114
|
+
|
|
115
|
+
if (showAlert) {
|
|
116
|
+
const buttons = onRetry
|
|
117
|
+
? [
|
|
118
|
+
{text: 'Cancel', style: 'cancel'},
|
|
119
|
+
{text: 'Retry', onPress: onRetry},
|
|
120
|
+
]
|
|
121
|
+
: [{text: 'OK'}];
|
|
122
|
+
|
|
123
|
+
Alert.alert(`Error: ${context}`, message, buttons);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const withErrorHandling = async (
|
|
128
|
+
fn,
|
|
129
|
+
context,
|
|
130
|
+
onError
|
|
131
|
+
) => {
|
|
132
|
+
try {
|
|
133
|
+
return await fn();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(`❌ Error in ${context}:`, error);
|
|
136
|
+
if (onError) {
|
|
137
|
+
onError(error);
|
|
138
|
+
} else {
|
|
139
|
+
handleError(error, context);
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const validatePDFPath = (path) => {
|
|
146
|
+
if (!path || path.trim() === '') {
|
|
147
|
+
throw new PDFError('PDF path is empty', ErrorCodes.PDF_NOT_FOUND);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const validatePageNumber = (page, totalPages) => {
|
|
152
|
+
if (page < 1 || page > totalPages) {
|
|
153
|
+
throw new PDFError(
|
|
154
|
+
`Page number ${page} is out of range (1-${totalPages})`,
|
|
155
|
+
ErrorCodes.UNKNOWN_ERROR
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const validateLicense = (licenseManager, featureName) => {
|
|
161
|
+
if (!licenseManager.isProActive()) {
|
|
162
|
+
throw new PDFError(
|
|
163
|
+
`${featureName} requires a Pro license`,
|
|
164
|
+
ErrorCodes.LICENSE_REQUIRED,
|
|
165
|
+
{feature: featureName}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default {
|
|
171
|
+
PDFError,
|
|
172
|
+
ErrorCodes,
|
|
173
|
+
getUserFriendlyMessage,
|
|
174
|
+
handleError,
|
|
175
|
+
withErrorHandling,
|
|
176
|
+
validatePDFPath,
|
|
177
|
+
validatePageNumber,
|
|
178
|
+
validateLicense,
|
|
179
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Data - Sample PDFs and Test Scenarios
|
|
3
|
+
* Provides various test cases for Pro feature validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SamplePDFs = {
|
|
7
|
+
SMALL: {
|
|
8
|
+
name: 'React Native Book Sample',
|
|
9
|
+
uri: 'http://samples.leanpub.com/thereactnativebook-sample.pdf',
|
|
10
|
+
cache: true,
|
|
11
|
+
description: 'Small PDF for quick testing',
|
|
12
|
+
expectedPages: 10,
|
|
13
|
+
},
|
|
14
|
+
MEDIUM: {
|
|
15
|
+
name: 'Lorem Ipsum Document',
|
|
16
|
+
uri: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
|
17
|
+
cache: true,
|
|
18
|
+
description: 'Medium-sized PDF',
|
|
19
|
+
expectedPages: 1,
|
|
20
|
+
},
|
|
21
|
+
LARGE: {
|
|
22
|
+
name: 'JavaScript Guide',
|
|
23
|
+
uri: 'https://eloquentjavascript.net/Eloquent_JavaScript.pdf',
|
|
24
|
+
cache: true,
|
|
25
|
+
description: 'Large PDF for performance testing',
|
|
26
|
+
expectedPages: 472,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const TestScenarios = {
|
|
31
|
+
BOOKMARKS: {
|
|
32
|
+
name: 'Test Bookmarks',
|
|
33
|
+
steps: [
|
|
34
|
+
'Open PDF',
|
|
35
|
+
'Navigate to page 3',
|
|
36
|
+
'Create bookmark with Red color',
|
|
37
|
+
'Add notes to bookmark',
|
|
38
|
+
'Navigate to page 5',
|
|
39
|
+
'Create bookmark with Blue color',
|
|
40
|
+
'Open bookmark sidebar',
|
|
41
|
+
'Tap bookmark to jump to page',
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
EXPORT: {
|
|
45
|
+
name: 'Test Export',
|
|
46
|
+
steps: [
|
|
47
|
+
'Open PDF',
|
|
48
|
+
'Navigate to page 1',
|
|
49
|
+
'Tap Export icon',
|
|
50
|
+
'Choose PNG format',
|
|
51
|
+
'Verify export success',
|
|
52
|
+
'Try JPEG export',
|
|
53
|
+
'Test batch export',
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
OPERATIONS: {
|
|
57
|
+
name: 'Test PDF Operations',
|
|
58
|
+
steps: [
|
|
59
|
+
'Open PDF',
|
|
60
|
+
'Tap Operations icon',
|
|
61
|
+
'Test Split (pages 1-2)',
|
|
62
|
+
'Test Extract (pages 1,3,5)',
|
|
63
|
+
'Verify output files',
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
ANALYTICS: {
|
|
67
|
+
name: 'Test Analytics',
|
|
68
|
+
steps: [
|
|
69
|
+
'Open PDF',
|
|
70
|
+
'Navigate through 5 pages',
|
|
71
|
+
'Wait 30 seconds',
|
|
72
|
+
'Open Analytics panel',
|
|
73
|
+
'Verify time tracking',
|
|
74
|
+
'Check reading speed',
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const SampleBookmarks = [
|
|
80
|
+
{
|
|
81
|
+
page: 1,
|
|
82
|
+
name: 'Introduction',
|
|
83
|
+
color: '#FF6B6B',
|
|
84
|
+
notes: 'Start of the document',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
page: 5,
|
|
88
|
+
name: 'Important Section',
|
|
89
|
+
color: '#4ECDC4',
|
|
90
|
+
notes: 'Key information here',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
page: 10,
|
|
94
|
+
name: 'Summary',
|
|
95
|
+
color: '#95E1D3',
|
|
96
|
+
notes: 'Conclusion and takeaways',
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
export default {
|
|
101
|
+
SamplePDFs,
|
|
102
|
+
TestScenarios,
|
|
103
|
+
SampleBookmarks,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|