react-native-pdf-jsi 3.3.1 → 3.4.2
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 +45 -6
- package/android/src/main/java/org/wonday/pdf/FileManager.java +2 -0
- package/android/src/main/java/org/wonday/pdf/MemoryMappedCache.java +1 -1
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +0 -54
- package/android/src/main/java/org/wonday/pdf/PdfManager.java +5 -0
- package/android/src/main/java/org/wonday/pdf/PdfView.java +12 -2
- package/android/src/main/java/org/wonday/pdf/RNPDFJSIPackage.java +50 -0
- package/android/src/main/java/org/wonday/pdf/RNPDFPackage.java +0 -1
- package/fabric/RNPDFPdfNativeComponent.js +8 -3
- package/index.js +148 -23
- package/ios/RNPDFPdf/RNPDFPdfView.h +19 -1
- package/ios/RNPDFPdf/RNPDFPdfView.mm +154 -44
- package/package.json +11 -6
- package/src/PDFJSI.js +29 -4
- package/src/components/AnalyticsPanel.jsx +22 -9
- package/src/managers/AnalyticsManager.js +1 -5
- package/src/managers/BookmarkManager.js +2 -10
- package/src/managers/CacheManager.js +14 -1
- package/src/managers/ExportManager.js +5 -20
- package/src/utils/ErrorHandler.js +0 -11
- package/src/utils/PDFTextExtractor.js +82 -0
- package/android/.gradle/5.6.1/fileChanges/last-build.bin +0 -0
- package/android/.gradle/5.6.1/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/5.6.1/gc.properties +0 -0
- package/android/.gradle/8.5/checksums/checksums.lock +0 -0
- package/android/.gradle/8.5/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.5/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock +0 -0
- package/android/.gradle/8.5/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.5/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.5/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.5/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.5/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/src/main/java/org/wonday/pdf/LicenseVerifier.java +0 -311
|
@@ -66,6 +66,8 @@ const float MIN_SCALE = 1.0f;
|
|
|
66
66
|
RCTBridge *_bridge;
|
|
67
67
|
PDFDocument *_pdfDocument;
|
|
68
68
|
PDFView *_pdfView;
|
|
69
|
+
UIScrollView *_internalScrollView;
|
|
70
|
+
id<UIScrollViewDelegate> _originalScrollDelegate;
|
|
69
71
|
PDFOutline *root;
|
|
70
72
|
float _fixScaleFactor;
|
|
71
73
|
bool _initialed;
|
|
@@ -103,13 +105,45 @@ using namespace facebook::react;
|
|
|
103
105
|
|
|
104
106
|
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
105
107
|
{
|
|
106
|
-
|
|
108
|
+
// Defensive check: Ensure the descriptor class exists before returning
|
|
109
|
+
// This prevents nil object insertion in RCTThirdPartyComponentsProvider
|
|
110
|
+
// The component name must match the codegen name: "RNPDFPdfView"
|
|
111
|
+
// Using static to ensure the provider is initialized only once
|
|
112
|
+
static ComponentDescriptorProvider provider = concreteComponentDescriptorProvider<RNPDFPdfViewComponentDescriptor>();
|
|
113
|
+
return provider;
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
// Needed because of this: https://github.com/facebook/react-native/pull/37274
|
|
110
117
|
+ (void)load
|
|
111
118
|
{
|
|
112
119
|
[super load];
|
|
120
|
+
|
|
121
|
+
// Force class to be loaded before React Native tries to register it
|
|
122
|
+
// This ensures RNPDFPdfViewCls() returns a valid class, preventing nil insertion
|
|
123
|
+
static dispatch_once_t onceToken;
|
|
124
|
+
dispatch_once(&onceToken, ^{
|
|
125
|
+
// Force class initialization by accessing the class
|
|
126
|
+
Class cls = [self class];
|
|
127
|
+
if (cls == nil) {
|
|
128
|
+
RCTLogError(@"RNPDFPdfView: Class is nil in +load");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Ensure component name is properly set for registration
|
|
133
|
+
// This helps React Native's RCTThirdPartyComponentsProvider find the component
|
|
134
|
+
// The component name must match the codegen name: "RNPDFPdfView"
|
|
135
|
+
NSString *componentName = NSStringFromClass(cls);
|
|
136
|
+
if (componentName == nil || componentName.length == 0) {
|
|
137
|
+
RCTLogError(@"RNPDFPdfView: Component name is nil or empty");
|
|
138
|
+
} else if (![componentName isEqualToString:@"RNPDFPdfView"]) {
|
|
139
|
+
RCTLogWarn(@"RNPDFPdfView: Component name mismatch. Expected 'RNPDFPdfView', got '%@'", componentName);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Verify class is accessible (RNPDFPdfViewCls is defined later, so we just verify the class itself)
|
|
143
|
+
if (cls != RNPDFPdfView.class) {
|
|
144
|
+
RCTLogError(@"RNPDFPdfView: Class mismatch in +load");
|
|
145
|
+
}
|
|
146
|
+
});
|
|
113
147
|
}
|
|
114
148
|
|
|
115
149
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
@@ -196,11 +230,6 @@ using namespace facebook::react;
|
|
|
196
230
|
[updatedPropNames addObject:@"scrollEnabled"];
|
|
197
231
|
}
|
|
198
232
|
|
|
199
|
-
if (_enableMomentum != newProps.enableMomentum) {
|
|
200
|
-
_enableMomentum = newProps.enableMomentum;
|
|
201
|
-
[updatedPropNames addObject:@"enableMomentum"];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
233
|
[super updateProps:props oldProps:oldProps];
|
|
205
234
|
[self didSetProps:updatedPropNames];
|
|
206
235
|
}
|
|
@@ -255,7 +284,7 @@ using namespace facebook::react;
|
|
|
255
284
|
|
|
256
285
|
- (void)setNativePage:(NSInteger)page
|
|
257
286
|
{
|
|
258
|
-
_page = page;
|
|
287
|
+
_page = (int)page;
|
|
259
288
|
[self didSetProps:[NSArray arrayWithObject:@"page"]];
|
|
260
289
|
}
|
|
261
290
|
|
|
@@ -289,7 +318,6 @@ using namespace facebook::react;
|
|
|
289
318
|
_showsHorizontalScrollIndicator = YES;
|
|
290
319
|
_showsVerticalScrollIndicator = YES;
|
|
291
320
|
_scrollEnabled = YES;
|
|
292
|
-
_enableMomentum = YES;
|
|
293
321
|
|
|
294
322
|
// Enhanced properties
|
|
295
323
|
_enableCaching = YES;
|
|
@@ -529,37 +557,21 @@ using namespace facebook::react;
|
|
|
529
557
|
[self setScrollIndicators:self horizontal:_showsHorizontalScrollIndicator vertical:_showsVerticalScrollIndicator depth:0];
|
|
530
558
|
}
|
|
531
559
|
|
|
532
|
-
|
|
533
|
-
if (_scrollEnabled) {
|
|
534
|
-
for (UIView *subview in _pdfView.subviews) {
|
|
535
|
-
if ([subview isKindOfClass:[UIScrollView class]]) {
|
|
536
|
-
UIScrollView *scrollView = (UIScrollView *)subview;
|
|
537
|
-
scrollView.scrollEnabled = YES;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
} else {
|
|
541
|
-
for (UIView *subview in _pdfView.subviews) {
|
|
542
|
-
if ([subview isKindOfClass:[UIScrollView class]]) {
|
|
543
|
-
UIScrollView *scrollView = (UIScrollView *)subview;
|
|
544
|
-
scrollView.scrollEnabled = NO;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Apply momentum property
|
|
560
|
+
// Configure scroll view (scrollEnabled)
|
|
551
561
|
if (_pdfDocument && ([changedProps containsObject:@"path"] ||
|
|
552
|
-
[changedProps containsObject:@"
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
scrollView.bounces = _enableMomentum;
|
|
559
|
-
scrollView.alwaysBounceVertical = _enableMomentum;
|
|
560
|
-
scrollView.alwaysBounceHorizontal = _enableMomentum;
|
|
561
|
-
}
|
|
562
|
+
[changedProps containsObject:@"scrollEnabled"])) {
|
|
563
|
+
// If path changed, restore original delegate before reconfiguring
|
|
564
|
+
if ([changedProps containsObject:@"path"] && _internalScrollView && _originalScrollDelegate) {
|
|
565
|
+
_internalScrollView.delegate = _originalScrollDelegate;
|
|
566
|
+
_internalScrollView = nil;
|
|
567
|
+
_originalScrollDelegate = nil;
|
|
562
568
|
}
|
|
569
|
+
|
|
570
|
+
// Use dispatch_async to ensure view hierarchy is fully set up after document load
|
|
571
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
572
|
+
// Search within _pdfView's hierarchy for scroll views
|
|
573
|
+
[self configureScrollView:self->_pdfView enabled:self->_scrollEnabled depth:0];
|
|
574
|
+
});
|
|
563
575
|
}
|
|
564
576
|
|
|
565
577
|
if (_pdfDocument && ([changedProps containsObject:@"path"] || [changedProps containsObject:@"enablePaging"] || [changedProps containsObject:@"horizontal"] || [changedProps containsObject:@"page"])) {
|
|
@@ -796,7 +808,7 @@ using namespace facebook::react;
|
|
|
796
808
|
* Tap
|
|
797
809
|
* zoom reset or zoom in
|
|
798
810
|
*
|
|
799
|
-
* @param recognizer
|
|
811
|
+
* @param recognizer The tap gesture recognizer
|
|
800
812
|
*/
|
|
801
813
|
- (void)handleDoubleTap:(UITapGestureRecognizer *)recognizer
|
|
802
814
|
{
|
|
@@ -869,7 +881,7 @@ using namespace facebook::react;
|
|
|
869
881
|
* Single Tap
|
|
870
882
|
* stop zoom
|
|
871
883
|
*
|
|
872
|
-
* @param recognizer
|
|
884
|
+
* @param sender The tap gesture recognizer
|
|
873
885
|
*/
|
|
874
886
|
- (void)handleSingleTap:(UITapGestureRecognizer *)sender
|
|
875
887
|
{
|
|
@@ -893,7 +905,7 @@ using namespace facebook::react;
|
|
|
893
905
|
* Pinch
|
|
894
906
|
*
|
|
895
907
|
*
|
|
896
|
-
* @param recognizer
|
|
908
|
+
* @param sender The pinch gesture recognizer
|
|
897
909
|
*/
|
|
898
910
|
-(void)handlePinch:(UIPinchGestureRecognizer *)sender{
|
|
899
911
|
[self onScaleChanged:Nil];
|
|
@@ -990,6 +1002,83 @@ using namespace facebook::react;
|
|
|
990
1002
|
}
|
|
991
1003
|
}
|
|
992
1004
|
|
|
1005
|
+
- (void)configureScrollView:(UIView *)view enabled:(BOOL)enabled depth:(int)depth {
|
|
1006
|
+
// max depth, prevent infinite loop
|
|
1007
|
+
if (depth > 10) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if ([view isKindOfClass:[UIScrollView class]]) {
|
|
1012
|
+
UIScrollView *scrollView = (UIScrollView *)view;
|
|
1013
|
+
// Since we're starting the recursion from _pdfView, all scroll views found are within its hierarchy
|
|
1014
|
+
// Configure scroll properties
|
|
1015
|
+
scrollView.scrollEnabled = enabled;
|
|
1016
|
+
|
|
1017
|
+
// Disable horizontal bouncing to prevent interference with navigation swipe-back
|
|
1018
|
+
scrollView.alwaysBounceHorizontal = NO;
|
|
1019
|
+
// Keep vertical bounce enabled for natural scrolling feel
|
|
1020
|
+
scrollView.bounces = YES;
|
|
1021
|
+
|
|
1022
|
+
// Set delegate for scroll tracking (only once to avoid conflicts)
|
|
1023
|
+
// Store original delegate before replacing it to preserve PDFView's internal scrolling
|
|
1024
|
+
if (!_internalScrollView) {
|
|
1025
|
+
_internalScrollView = scrollView;
|
|
1026
|
+
// Store original delegate if it exists and is not us
|
|
1027
|
+
if (scrollView.delegate && scrollView.delegate != self) {
|
|
1028
|
+
_originalScrollDelegate = scrollView.delegate;
|
|
1029
|
+
}
|
|
1030
|
+
scrollView.delegate = self;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
for (UIView *subview in view.subviews) {
|
|
1035
|
+
[self configureScrollView:subview enabled:enabled depth:depth + 1];
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
#pragma mark - UIScrollViewDelegate
|
|
1040
|
+
|
|
1041
|
+
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
|
1042
|
+
// Forward to original delegate first if it exists (important for PDFView's scrolling)
|
|
1043
|
+
if (_originalScrollDelegate && [_originalScrollDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
|
|
1044
|
+
[_originalScrollDelegate scrollViewDidScroll:scrollView];
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (!_pdfDocument || _singlePage) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Calculate visible page based on scroll position
|
|
1052
|
+
// Use the center point of the visible viewport
|
|
1053
|
+
CGPoint centerPoint = CGPointMake(
|
|
1054
|
+
scrollView.contentOffset.x + scrollView.bounds.size.width / 2,
|
|
1055
|
+
scrollView.contentOffset.y + scrollView.bounds.size.height / 2
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
// Convert to PDFView coordinates
|
|
1059
|
+
CGPoint pdfPoint = [scrollView convertPoint:centerPoint toView:_pdfView];
|
|
1060
|
+
PDFPage *visiblePage = [_pdfView pageForPoint:pdfPoint nearest:YES];
|
|
1061
|
+
|
|
1062
|
+
if (visiblePage) {
|
|
1063
|
+
unsigned long pageIndex = [_pdfDocument indexForPage:visiblePage];
|
|
1064
|
+
int newPage = (int)pageIndex + 1;
|
|
1065
|
+
|
|
1066
|
+
// Only update if page actually changed and is valid
|
|
1067
|
+
if (newPage != _page && newPage > 0 && newPage <= (int)_pdfDocument.pageCount) {
|
|
1068
|
+
_page = newPage;
|
|
1069
|
+
_pageCount = (int)_pdfDocument.pageCount;
|
|
1070
|
+
|
|
1071
|
+
// Trigger preloading if enabled
|
|
1072
|
+
if (_enablePreloading) {
|
|
1073
|
+
[self preloadAdjacentPages:_page];
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Notify about page change
|
|
1077
|
+
[self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"pageChanged|%d|%lu", newPage, _pdfDocument.pageCount]]];
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
993
1082
|
// Enhanced progressive loading methods
|
|
994
1083
|
- (void)preloadAdjacentPages:(int)currentPage
|
|
995
1084
|
{
|
|
@@ -1069,9 +1158,6 @@ using namespace facebook::react;
|
|
|
1069
1158
|
for (int pageIndex = 0; pageIndex < _pdfDocument.pageCount; pageIndex++) {
|
|
1070
1159
|
PDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
|
|
1071
1160
|
|
|
1072
|
-
// Get the page bounds
|
|
1073
|
-
CGRect pageBounds = [page boundsForBox:kPDFDisplayBoxCropBox];
|
|
1074
|
-
|
|
1075
1161
|
// Search for text in the page
|
|
1076
1162
|
PDFSelection *selection = [page selectionForRange:NSMakeRange(0, page.string.length)];
|
|
1077
1163
|
if (selection && selection.string.length > 0) {
|
|
@@ -1105,9 +1191,33 @@ using namespace facebook::react;
|
|
|
1105
1191
|
@end
|
|
1106
1192
|
|
|
1107
1193
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
1194
|
+
|
|
1195
|
+
#ifdef __cplusplus
|
|
1196
|
+
extern "C" {
|
|
1197
|
+
#endif
|
|
1198
|
+
|
|
1108
1199
|
Class<RCTComponentViewProtocol> RNPDFPdfViewCls(void)
|
|
1109
1200
|
{
|
|
1110
|
-
|
|
1201
|
+
// Defensive check: Ensure class is loaded and valid before returning
|
|
1202
|
+
// This prevents nil object insertion in RCTThirdPartyComponentsProvider
|
|
1203
|
+
Class cls = RNPDFPdfView.class;
|
|
1204
|
+
if (cls == nil) {
|
|
1205
|
+
RCTLogError(@"RNPDFPdfView: Class is nil in RNPDFPdfViewCls");
|
|
1206
|
+
// Return a fallback to prevent crash, though this shouldn't happen
|
|
1207
|
+
return [RCTViewComponentView class];
|
|
1208
|
+
}
|
|
1209
|
+
return cls;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Alias function based on codegen name "rnpdf" - ensures codegen can find the function
|
|
1213
|
+
// even if it uses the codegen name instead of componentProvider name
|
|
1214
|
+
Class<RCTComponentViewProtocol> rnpdfCls(void)
|
|
1215
|
+
{
|
|
1216
|
+
return RNPDFPdfViewCls();
|
|
1111
1217
|
}
|
|
1112
1218
|
|
|
1219
|
+
#ifdef __cplusplus
|
|
1220
|
+
}
|
|
1221
|
+
#endif
|
|
1222
|
+
|
|
1113
1223
|
#endif
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-pdf-jsi",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"summary": "High-performance React Native PDF viewer with JSI acceleration - up to 80x faster than traditional bridge",
|
|
5
5
|
"description": "🚀 Ultra-fast React Native PDF viewer with JSI (JavaScript Interface) integration for maximum performance. Features lazy loading, smart caching, progressive loading, and zero-bridge overhead operations. Perfect for large PDF files with 30-day persistent cache and advanced memory optimization. Google Play 16KB page size compliant for Android 15+. Supports iOS, Android, and Windows platforms.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"crypto-js": "4.2.0",
|
|
67
|
-
"deprecated-react-native-prop-types": "^2.3.0"
|
|
67
|
+
"deprecated-react-native-prop-types": "^2.3.0",
|
|
68
|
+
"react-native-pdf-jsi": "^2.2.4"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@babel/core": "^7.20.2",
|
|
@@ -72,10 +73,10 @@
|
|
|
72
73
|
"prop-types": "^15.7.2"
|
|
73
74
|
},
|
|
74
75
|
"peerDependencies": {
|
|
76
|
+
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
75
77
|
"react": "*",
|
|
76
78
|
"react-native": "*",
|
|
77
|
-
"react-native-blob-util": ">=0.13.7"
|
|
78
|
-
"@react-native-async-storage/async-storage": ">=1.17.0"
|
|
79
|
+
"react-native-blob-util": ">=0.13.7"
|
|
79
80
|
},
|
|
80
81
|
"files": [
|
|
81
82
|
"android/",
|
|
@@ -93,7 +94,9 @@
|
|
|
93
94
|
"PinchZoomView.js",
|
|
94
95
|
"react-native-pdf-jsi.podspec",
|
|
95
96
|
"fabric/",
|
|
96
|
-
"
|
|
97
|
+
"README.md",
|
|
98
|
+
"README_JSI.md",
|
|
99
|
+
"LICENSE"
|
|
97
100
|
],
|
|
98
101
|
"codegenConfig": {
|
|
99
102
|
"name": "rnpdf",
|
|
@@ -103,7 +106,9 @@
|
|
|
103
106
|
"javaPackageName": "org.wonday.pdf"
|
|
104
107
|
},
|
|
105
108
|
"ios": {
|
|
106
|
-
"componentProvider":
|
|
109
|
+
"componentProvider": {
|
|
110
|
+
"RNPDFPdfView": "RNPDFPdfView"
|
|
111
|
+
}
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
}
|
package/src/PDFJSI.js
CHANGED
|
@@ -75,18 +75,23 @@ class PDFJSIManager {
|
|
|
75
75
|
this.cacheMetrics = new Map();
|
|
76
76
|
this.initializationPromise = null;
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
// Don't initialize JSI in constructor - it will be initialized lazily when first accessed
|
|
79
|
+
// This prevents hooks from being called before React is ready
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
/**
|
|
82
83
|
* Initialize JSI availability check
|
|
84
|
+
* This is now called lazily when first accessed, not at module load
|
|
83
85
|
*/
|
|
84
86
|
async initializeJSI() {
|
|
85
87
|
if (this.initializationPromise) {
|
|
86
88
|
return this.initializationPromise;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
// Defer initialization to prevent hooks from being called before React is ready
|
|
92
|
+
this.initializationPromise = Promise.resolve().then(() => {
|
|
93
|
+
return this.checkJSIAvailability();
|
|
94
|
+
});
|
|
90
95
|
return this.initializationPromise;
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -814,8 +819,28 @@ class PDFJSIManager {
|
|
|
814
819
|
}
|
|
815
820
|
}
|
|
816
821
|
|
|
817
|
-
// Create singleton instance
|
|
818
|
-
|
|
822
|
+
// Create singleton instance lazily to prevent initialization before React is ready
|
|
823
|
+
let pdfJSIManagerInstance = null;
|
|
824
|
+
|
|
825
|
+
const getPDFJSIManager = () => {
|
|
826
|
+
if (!pdfJSIManagerInstance) {
|
|
827
|
+
pdfJSIManagerInstance = new PDFJSIManager();
|
|
828
|
+
}
|
|
829
|
+
return pdfJSIManagerInstance;
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// Export a proxy that lazy-loads the instance
|
|
833
|
+
const pdfJSIManager = new Proxy({}, {
|
|
834
|
+
get(target, prop) {
|
|
835
|
+
const instance = getPDFJSIManager();
|
|
836
|
+
const value = instance[prop];
|
|
837
|
+
// If it's a method, bind it to the instance
|
|
838
|
+
if (typeof value === 'function') {
|
|
839
|
+
return value.bind(instance);
|
|
840
|
+
}
|
|
841
|
+
return value;
|
|
842
|
+
}
|
|
843
|
+
});
|
|
819
844
|
|
|
820
845
|
export default pdfJSIManager;
|
|
821
846
|
|
|
@@ -12,10 +12,23 @@ const AnalyticsPanel = ({
|
|
|
12
12
|
analytics,
|
|
13
13
|
licenseInfo,
|
|
14
14
|
}) => {
|
|
15
|
-
|
|
15
|
+
// Handle null/undefined analytics with defaults
|
|
16
|
+
const safeAnalytics = analytics || {
|
|
17
|
+
timeSpent: 0,
|
|
18
|
+
pagesRead: [],
|
|
19
|
+
totalPages: 0,
|
|
20
|
+
percentage: 0,
|
|
21
|
+
sessions: 0,
|
|
22
|
+
currentPage: 1,
|
|
23
|
+
lastRead: null,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const timeInMinutes = Math.round((safeAnalytics.timeSpent || 0) / 60);
|
|
27
|
+
const pagesReadArray = Array.isArray(safeAnalytics.pagesRead) ? safeAnalytics.pagesRead : [];
|
|
28
|
+
const totalPages = safeAnalytics.totalPages || 0;
|
|
16
29
|
const readingSpeed =
|
|
17
|
-
timeInMinutes > 0 ?
|
|
18
|
-
const remainingPages =
|
|
30
|
+
timeInMinutes > 0 ? pagesReadArray.length / timeInMinutes : 0;
|
|
31
|
+
const remainingPages = totalPages - pagesReadArray.length;
|
|
19
32
|
const estimatedTimeRemaining =
|
|
20
33
|
readingSpeed > 0 ? Math.round(remainingPages / readingSpeed) : 0;
|
|
21
34
|
|
|
@@ -34,13 +47,13 @@ const AnalyticsPanel = ({
|
|
|
34
47
|
<View style={styles.progressSection}>
|
|
35
48
|
<View style={styles.progressRing}>
|
|
36
49
|
<Text style={styles.progressPercent}>
|
|
37
|
-
{
|
|
50
|
+
{safeAnalytics.percentage || 0}%
|
|
38
51
|
</Text>
|
|
39
52
|
<Text style={styles.progressLabel}>Complete</Text>
|
|
40
53
|
</View>
|
|
41
54
|
<View style={styles.progressStats}>
|
|
42
55
|
<Text style={styles.statValue}>
|
|
43
|
-
{
|
|
56
|
+
{pagesReadArray.length}/{totalPages}
|
|
44
57
|
</Text>
|
|
45
58
|
<Text style={styles.statLabel}>Pages Read</Text>
|
|
46
59
|
</View>
|
|
@@ -64,7 +77,7 @@ const AnalyticsPanel = ({
|
|
|
64
77
|
|
|
65
78
|
<View style={styles.statCard}>
|
|
66
79
|
<Text style={styles.statIcon}>🔄</Text>
|
|
67
|
-
<Text style={styles.statCardValue}>{
|
|
80
|
+
<Text style={styles.statCardValue}>{safeAnalytics.sessions || 0}</Text>
|
|
68
81
|
<Text style={styles.statCardLabel}>Sessions</Text>
|
|
69
82
|
</View>
|
|
70
83
|
|
|
@@ -81,11 +94,11 @@ const AnalyticsPanel = ({
|
|
|
81
94
|
<View style={styles.sessionCard}>
|
|
82
95
|
<Text style={styles.sessionTitle}>Current Session</Text>
|
|
83
96
|
<Text style={styles.sessionInfo}>
|
|
84
|
-
Page {
|
|
97
|
+
Page {safeAnalytics.currentPage || 1} of {totalPages}
|
|
85
98
|
</Text>
|
|
86
|
-
{
|
|
99
|
+
{safeAnalytics.lastRead && (
|
|
87
100
|
<Text style={styles.sessionTime}>
|
|
88
|
-
Last read: {new Date(
|
|
101
|
+
Last read: {new Date(safeAnalytics.lastRead).toLocaleTimeString()}
|
|
89
102
|
</Text>
|
|
90
103
|
)}
|
|
91
104
|
</View>
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
* OPTIMIZATION: Memoized analytics for 95% faster repeated calls
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import bookmarkManager from '
|
|
14
|
-
import licenseManager from '../license/LicenseManager';
|
|
13
|
+
import bookmarkManager from './BookmarkManager';
|
|
15
14
|
import MemoizedAnalytics from '../utils/MemoizedAnalytics';
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -28,9 +27,6 @@ export class AnalyticsManager {
|
|
|
28
27
|
* Initialize analytics
|
|
29
28
|
*/
|
|
30
29
|
async initialize() {
|
|
31
|
-
// Check Pro license
|
|
32
|
-
licenseManager.requirePro('Reading Analytics');
|
|
33
|
-
|
|
34
30
|
if (!this.initialized) {
|
|
35
31
|
await bookmarkManager.initialize();
|
|
36
32
|
this.initialized = true;
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
14
|
-
import licenseManager, { ProFeature } from '../license/LicenseManager';
|
|
15
14
|
|
|
16
15
|
const STORAGE_KEY = '@react-native-pdf-jsi/bookmarks';
|
|
17
16
|
const PROGRESS_KEY = '@react-native-pdf-jsi/progress';
|
|
@@ -94,10 +93,7 @@ export class BookmarkManager {
|
|
|
94
93
|
async createBookmark(pdfId, bookmark) {
|
|
95
94
|
await this.initialize();
|
|
96
95
|
|
|
97
|
-
//
|
|
98
|
-
if (bookmark.color && bookmark.color !== '#000000') {
|
|
99
|
-
licenseManager.requirePro('Bookmark Colors');
|
|
100
|
-
}
|
|
96
|
+
// All features enabled by default
|
|
101
97
|
|
|
102
98
|
const newBookmark = {
|
|
103
99
|
id: this.generateId(),
|
|
@@ -255,11 +251,7 @@ export class BookmarkManager {
|
|
|
255
251
|
async updateProgress(pdfId, progressData) {
|
|
256
252
|
await this.initialize();
|
|
257
253
|
|
|
258
|
-
//
|
|
259
|
-
// Advanced analytics require Pro
|
|
260
|
-
if (progressData.timeSpent || progressData.sessions) {
|
|
261
|
-
licenseManager.requirePro('Reading Progress Tracking');
|
|
262
|
-
}
|
|
254
|
+
// All features enabled by default
|
|
263
255
|
|
|
264
256
|
const currentProgress = this.progress.get(pdfId) || {
|
|
265
257
|
pdfId,
|
|
@@ -16,7 +16,20 @@ import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
|
|
|
16
16
|
const { PDFJSIManager } = NativeModules;
|
|
17
17
|
|
|
18
18
|
// Event emitter for progress updates
|
|
19
|
-
|
|
19
|
+
// Safely create event emitter only if the module supports event listeners
|
|
20
|
+
let eventEmitter = null;
|
|
21
|
+
if (PDFJSIManager) {
|
|
22
|
+
try {
|
|
23
|
+
// Check if NativeEventEmitter is available and the module supports events
|
|
24
|
+
if (typeof NativeEventEmitter !== 'undefined' &&
|
|
25
|
+
(typeof PDFJSIManager.addListener === 'function' || typeof PDFJSIManager.removeListeners === 'function')) {
|
|
26
|
+
eventEmitter = new NativeEventEmitter(PDFJSIManager);
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.warn('[CacheManager] Failed to create NativeEventEmitter:', error);
|
|
30
|
+
eventEmitter = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
20
33
|
|
|
21
34
|
class CacheManager {
|
|
22
35
|
constructor() {
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
import { NativeModules, Platform, Share } from 'react-native';
|
|
15
15
|
import PDFTextExtractor from '../utils/PDFTextExtractor';
|
|
16
|
-
import licenseManager, { ProFeature } from '../license/LicenseManager';
|
|
17
16
|
|
|
18
17
|
const { PDFExporter } = NativeModules;
|
|
19
18
|
|
|
@@ -167,9 +166,7 @@ export class ExportManager {
|
|
|
167
166
|
* @returns {Promise<Array>} Array of image paths
|
|
168
167
|
*/
|
|
169
168
|
async exportToImages(filePath, options = {}) {
|
|
170
|
-
//
|
|
171
|
-
licenseManager.requirePro('Export to Images');
|
|
172
|
-
|
|
169
|
+
// All features enabled by default
|
|
173
170
|
const {
|
|
174
171
|
pages = null, // null = all pages
|
|
175
172
|
format = ExportFormat.JPEG,
|
|
@@ -230,11 +227,7 @@ export class ExportManager {
|
|
|
230
227
|
});
|
|
231
228
|
|
|
232
229
|
try {
|
|
233
|
-
//
|
|
234
|
-
console.log('🔑 [ExportManager] Checking license for Export to Images...');
|
|
235
|
-
licenseManager.requirePro('Export to Images');
|
|
236
|
-
console.log('✅ [ExportManager] License check passed');
|
|
237
|
-
|
|
230
|
+
// All features enabled by default
|
|
238
231
|
if (this.isNativeAvailable) {
|
|
239
232
|
console.log('📱 [ExportManager] Calling native PDFExporter.exportPageToImage...');
|
|
240
233
|
console.log('📱 [ExportManager] Parameters:', {
|
|
@@ -277,9 +270,7 @@ export class ExportManager {
|
|
|
277
270
|
* @returns {Promise<string>} Path to merged PDF
|
|
278
271
|
*/
|
|
279
272
|
async mergePDFs(filePaths, outputPath = null) {
|
|
280
|
-
//
|
|
281
|
-
licenseManager.requirePro('PDF Operations');
|
|
282
|
-
|
|
273
|
+
// All features enabled by default
|
|
283
274
|
console.log(`📤 ExportManager: Merging ${filePaths.length} PDFs...`);
|
|
284
275
|
|
|
285
276
|
try {
|
|
@@ -313,10 +304,7 @@ export class ExportManager {
|
|
|
313
304
|
});
|
|
314
305
|
|
|
315
306
|
try {
|
|
316
|
-
|
|
317
|
-
licenseManager.requirePro('PDF Operations');
|
|
318
|
-
console.log('✅ [ExportManager] License check passed');
|
|
319
|
-
|
|
307
|
+
// All features enabled by default
|
|
320
308
|
if (this.isNativeAvailable) {
|
|
321
309
|
console.log('📱 [ExportManager] Calling native PDFExporter.splitPDF...');
|
|
322
310
|
console.log('📱 [ExportManager] Ranges:', JSON.stringify(ranges));
|
|
@@ -354,10 +342,7 @@ export class ExportManager {
|
|
|
354
342
|
});
|
|
355
343
|
|
|
356
344
|
try {
|
|
357
|
-
|
|
358
|
-
licenseManager.requirePro('PDF Operations');
|
|
359
|
-
console.log('✅ [ExportManager] License check passed');
|
|
360
|
-
|
|
345
|
+
// All features enabled by default
|
|
361
346
|
if (this.isNativeAvailable) {
|
|
362
347
|
// Convert to 0-indexed
|
|
363
348
|
const pageIndices = pages.map(p => p - 1);
|
|
@@ -157,16 +157,6 @@ export const validatePageNumber = (page, totalPages) => {
|
|
|
157
157
|
}
|
|
158
158
|
};
|
|
159
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
160
|
export default {
|
|
171
161
|
PDFError,
|
|
172
162
|
ErrorCodes,
|
|
@@ -175,5 +165,4 @@ export default {
|
|
|
175
165
|
withErrorHandling,
|
|
176
166
|
validatePDFPath,
|
|
177
167
|
validatePageNumber,
|
|
178
|
-
validateLicense,
|
|
179
168
|
};
|