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.
@@ -0,0 +1,253 @@
1
+ /**
2
+ * BookmarkModal - Create/edit bookmark with color picker
3
+ */
4
+
5
+ import React, {useState} from 'react';
6
+ import {
7
+ View,
8
+ Text,
9
+ TextInput,
10
+ TouchableOpacity,
11
+ StyleSheet,
12
+ ScrollView,
13
+ Modal,
14
+ Alert,
15
+ } from 'react-native';
16
+
17
+ const BOOKMARK_COLORS = [
18
+ {name: 'Red', color: '#FF6B6B'},
19
+ {name: 'Blue', color: '#4ECDC4'},
20
+ {name: 'Green', color: '#95E1D3'},
21
+ {name: 'Yellow', color: '#FFE66D'},
22
+ {name: 'Purple', color: '#A78BFA'},
23
+ {name: 'Orange', color: '#FF8C42'},
24
+ {name: 'Pink', color: '#FF6B9D'},
25
+ {name: 'Teal', color: '#3EECAC'},
26
+ {name: 'Indigo', color: '#6366F1'},
27
+ {name: 'Amber', color: '#FBBF24'},
28
+ ];
29
+
30
+ const BookmarkModal = ({
31
+ visible,
32
+ onClose,
33
+ onSave,
34
+ currentPage,
35
+ initialData,
36
+ }) => {
37
+ const [name, setName] = useState(initialData?.name || '');
38
+ const [color, setColor] = useState(initialData?.color || '#FF6B6B');
39
+ const [notes, setNotes] = useState(initialData?.notes || '');
40
+
41
+ const handleSave = () => {
42
+ if (!name.trim()) {
43
+ Alert.alert('Error', 'Please enter a bookmark name');
44
+ return;
45
+ }
46
+
47
+ onSave({name, color, notes});
48
+
49
+ // Reset form
50
+ setName('');
51
+ setNotes('');
52
+ setColor('#FF6B6B');
53
+ };
54
+
55
+ const handleClose = () => {
56
+ setName('');
57
+ setNotes('');
58
+ setColor('#FF6B6B');
59
+ onClose();
60
+ };
61
+
62
+ return (
63
+ <Modal
64
+ visible={visible}
65
+ transparent
66
+ animationType="slide"
67
+ onRequestClose={handleClose}>
68
+ <View style={styles.overlay}>
69
+ <View style={styles.container}>
70
+ <View style={styles.header}>
71
+ <Text style={styles.title}>
72
+ {initialData ? 'Edit' : 'Create'} Bookmark
73
+ </Text>
74
+ <TouchableOpacity onPress={handleClose}>
75
+ <Text style={styles.closeButton}>✕</Text>
76
+ </TouchableOpacity>
77
+ </View>
78
+
79
+ <ScrollView style={styles.form}>
80
+ <Text style={styles.pageInfo}>Page {currentPage}</Text>
81
+
82
+ <Text style={styles.label}>Name</Text>
83
+ <TextInput
84
+ style={styles.input}
85
+ placeholder="Enter bookmark name"
86
+ value={name}
87
+ onChangeText={setName}
88
+ placeholderTextColor="#9CA3AF"
89
+ />
90
+
91
+ <Text style={styles.label}>Notes (optional)</Text>
92
+ <TextInput
93
+ style={[styles.input, styles.textArea]}
94
+ placeholder="Add notes..."
95
+ value={notes}
96
+ onChangeText={setNotes}
97
+ multiline
98
+ numberOfLines={4}
99
+ placeholderTextColor="#9CA3AF"
100
+ />
101
+
102
+ <Text style={styles.label}>Color</Text>
103
+ <ScrollView
104
+ horizontal
105
+ showsHorizontalScrollIndicator={false}
106
+ style={styles.colorPicker}>
107
+ {BOOKMARK_COLORS.map(item => (
108
+ <TouchableOpacity
109
+ key={item.color}
110
+ style={[
111
+ styles.colorOption,
112
+ {backgroundColor: item.color},
113
+ color === item.color && styles.selectedColor,
114
+ ]}
115
+ onPress={() => setColor(item.color)}>
116
+ {color === item.color && (
117
+ <Text style={styles.checkmark}>✓</Text>
118
+ )}
119
+ </TouchableOpacity>
120
+ ))}
121
+ </ScrollView>
122
+ </ScrollView>
123
+
124
+ <View style={styles.footer}>
125
+ <TouchableOpacity
126
+ style={[styles.button, styles.cancelButton]}
127
+ onPress={handleClose}>
128
+ <Text style={styles.cancelButtonText}>Cancel</Text>
129
+ </TouchableOpacity>
130
+ <TouchableOpacity
131
+ style={[styles.button, styles.saveButton]}
132
+ onPress={handleSave}>
133
+ <Text style={styles.saveButtonText}>Save</Text>
134
+ </TouchableOpacity>
135
+ </View>
136
+ </View>
137
+ </View>
138
+ </Modal>
139
+ );
140
+ };
141
+
142
+ const styles = StyleSheet.create({
143
+ overlay: {
144
+ flex: 1,
145
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
146
+ justifyContent: 'flex-end',
147
+ },
148
+ container: {
149
+ backgroundColor: '#FFFFFF',
150
+ borderTopLeftRadius: 20,
151
+ borderTopRightRadius: 20,
152
+ maxHeight: '80%',
153
+ },
154
+ header: {
155
+ flexDirection: 'row',
156
+ justifyContent: 'space-between',
157
+ alignItems: 'center',
158
+ padding: 20,
159
+ borderBottomWidth: 1,
160
+ borderBottomColor: '#E5E7EB',
161
+ },
162
+ title: {
163
+ fontSize: 20,
164
+ fontWeight: 'bold',
165
+ color: '#1F2937',
166
+ },
167
+ closeButton: {
168
+ fontSize: 24,
169
+ color: '#6B7280',
170
+ padding: 4,
171
+ },
172
+ form: {
173
+ padding: 20,
174
+ },
175
+ pageInfo: {
176
+ fontSize: 14,
177
+ color: '#6366F1',
178
+ fontWeight: '600',
179
+ marginBottom: 20,
180
+ },
181
+ label: {
182
+ fontSize: 14,
183
+ fontWeight: '600',
184
+ color: '#374151',
185
+ marginBottom: 8,
186
+ marginTop: 16,
187
+ },
188
+ input: {
189
+ borderWidth: 1,
190
+ borderColor: '#D1D5DB',
191
+ borderRadius: 8,
192
+ padding: 12,
193
+ fontSize: 16,
194
+ color: '#1F2937',
195
+ },
196
+ textArea: {
197
+ height: 100,
198
+ textAlignVertical: 'top',
199
+ },
200
+ colorPicker: {
201
+ flexDirection: 'row',
202
+ marginTop: 8,
203
+ },
204
+ colorOption: {
205
+ width: 48,
206
+ height: 48,
207
+ borderRadius: 24,
208
+ marginRight: 12,
209
+ justifyContent: 'center',
210
+ alignItems: 'center',
211
+ borderWidth: 3,
212
+ borderColor: 'transparent',
213
+ },
214
+ selectedColor: {
215
+ borderColor: '#1F2937',
216
+ },
217
+ checkmark: {
218
+ fontSize: 24,
219
+ color: '#FFFFFF',
220
+ fontWeight: 'bold',
221
+ },
222
+ footer: {
223
+ flexDirection: 'row',
224
+ padding: 20,
225
+ gap: 12,
226
+ borderTopWidth: 1,
227
+ borderTopColor: '#E5E7EB',
228
+ },
229
+ button: {
230
+ flex: 1,
231
+ padding: 16,
232
+ borderRadius: 8,
233
+ alignItems: 'center',
234
+ },
235
+ cancelButton: {
236
+ backgroundColor: '#F3F4F6',
237
+ },
238
+ cancelButtonText: {
239
+ fontSize: 16,
240
+ fontWeight: '600',
241
+ color: '#374151',
242
+ },
243
+ saveButton: {
244
+ backgroundColor: '#6366F1',
245
+ },
246
+ saveButtonText: {
247
+ fontSize: 16,
248
+ fontWeight: '600',
249
+ color: '#FFFFFF',
250
+ },
251
+ });
252
+
253
+ export default BookmarkModal;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * BottomSheet Component - Slide-up menu container
3
+ */
4
+
5
+ import React, {useEffect, useRef} from 'react';
6
+ import {
7
+ View,
8
+ Modal,
9
+ Animated,
10
+ StyleSheet,
11
+ TouchableOpacity,
12
+ Dimensions,
13
+ PanResponder,
14
+ } from 'react-native';
15
+
16
+ const {height: SCREEN_HEIGHT} = Dimensions.get('window');
17
+
18
+ const BottomSheet = ({
19
+ visible,
20
+ onClose,
21
+ children,
22
+ height = SCREEN_HEIGHT * 0.6,
23
+ }) => {
24
+ const translateY = useRef(new Animated.Value(height)).current;
25
+
26
+ useEffect(() => {
27
+ if (visible) {
28
+ Animated.spring(translateY, {
29
+ toValue: 0,
30
+ useNativeDriver: true,
31
+ tension: 50,
32
+ friction: 8,
33
+ }).start();
34
+ } else {
35
+ Animated.timing(translateY, {
36
+ toValue: height,
37
+ duration: 250,
38
+ useNativeDriver: true,
39
+ }).start();
40
+ }
41
+ }, [visible, height]);
42
+
43
+ const panResponder = useRef(
44
+ PanResponder.create({
45
+ onMoveShouldSetPanResponder: (_, gestureState) => {
46
+ return gestureState.dy > 5;
47
+ },
48
+ onPanResponderMove: (_, gestureState) => {
49
+ if (gestureState.dy > 0) {
50
+ translateY.setValue(gestureState.dy);
51
+ }
52
+ },
53
+ onPanResponderRelease: (_, gestureState) => {
54
+ if (gestureState.dy > height * 0.3) {
55
+ onClose();
56
+ } else {
57
+ Animated.spring(translateY, {
58
+ toValue: 0,
59
+ useNativeDriver: true,
60
+ }).start();
61
+ }
62
+ },
63
+ })
64
+ ).current;
65
+
66
+ return (
67
+ <Modal
68
+ visible={visible}
69
+ transparent
70
+ animationType="fade"
71
+ onRequestClose={onClose}>
72
+ <View style={styles.overlay}>
73
+ <TouchableOpacity
74
+ style={styles.backdrop}
75
+ activeOpacity={1}
76
+ onPress={onClose}
77
+ />
78
+ <Animated.View
79
+ style={[
80
+ styles.container,
81
+ {height, transform: [{translateY}]},
82
+ ]}
83
+ {...panResponder.panHandlers}>
84
+ <View style={styles.handle} />
85
+ <View style={styles.content}>{children}</View>
86
+ </Animated.View>
87
+ </View>
88
+ </Modal>
89
+ );
90
+ };
91
+
92
+ const styles = StyleSheet.create({
93
+ overlay: {
94
+ flex: 1,
95
+ justifyContent: 'flex-end',
96
+ },
97
+ backdrop: {
98
+ ...StyleSheet.absoluteFillObject,
99
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
100
+ },
101
+ container: {
102
+ backgroundColor: '#FFFFFF',
103
+ borderTopLeftRadius: 20,
104
+ borderTopRightRadius: 20,
105
+ paddingTop: 8,
106
+ },
107
+ handle: {
108
+ width: 40,
109
+ height: 4,
110
+ backgroundColor: '#D1D5DB',
111
+ borderRadius: 2,
112
+ alignSelf: 'center',
113
+ marginBottom: 8,
114
+ },
115
+ content: {
116
+ flex: 1,
117
+ padding: 16,
118
+ },
119
+ });
120
+
121
+ export default BottomSheet;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * ExportMenu - Export PDF pages to images
3
+ */
4
+
5
+ import React, {useState} from 'react';
6
+ import {
7
+ View,
8
+ Text,
9
+ TouchableOpacity,
10
+ StyleSheet,
11
+ ActivityIndicator,
12
+ Share,
13
+ Alert,
14
+ NativeModules,
15
+ } from 'react-native';
16
+ import BottomSheet from './BottomSheet';
17
+
18
+ const {FileManager} = NativeModules;
19
+
20
+ const ExportMenu = ({
21
+ visible,
22
+ onClose,
23
+ currentPage,
24
+ totalPages,
25
+ pdfPath,
26
+ onExport,
27
+ onShareFiles,
28
+ }) => {
29
+ const [exporting, setExporting] = useState(false);
30
+ const [exportProgress, setExportProgress] = useState('');
31
+
32
+ const handleExport = async (format, quality, pages) => {
33
+ try {
34
+ setExporting(true);
35
+ setExportProgress(`Exporting to ${format.toUpperCase()}...`);
36
+
37
+ const options = {
38
+ format,
39
+ quality,
40
+ };
41
+
42
+ let result;
43
+ if (pages === 'current') {
44
+ setExportProgress(`Exporting page ${currentPage}...`);
45
+ result = await onExport({
46
+ type: 'single',
47
+ page: currentPage,
48
+ ...options,
49
+ });
50
+ } else {
51
+ const pageCount = Math.min(3, totalPages);
52
+ setExportProgress(`Exporting ${pageCount} pages...`);
53
+ result = await onExport({
54
+ type: 'range',
55
+ pages: Array.from({length: pageCount}, (_, i) => i + 1),
56
+ ...options,
57
+ });
58
+ }
59
+
60
+ setExportProgress('');
61
+ setExporting(false);
62
+
63
+ // Show success and offer share
64
+ if (result) {
65
+ // Handle new result format with download info
66
+ const downloadedFiles = result.downloadedFiles || [];
67
+ const message = result.message || `Exported successfully!`;
68
+
69
+ Alert.alert(
70
+ '✅ Export Successful',
71
+ message,
72
+ [
73
+ {text: 'Done', style: 'cancel'},
74
+ {
75
+ text: 'Open Folder',
76
+ onPress: async () => {
77
+ try {
78
+ await FileManager.openDownloadsFolder();
79
+ } catch (e) {
80
+ Alert.alert('Info', 'Please check Downloads/PDFDemoApp folder in your file manager');
81
+ }
82
+ }
83
+ },
84
+ downloadedFiles.length > 0 && onShareFiles && {
85
+ text: 'Share',
86
+ onPress: () => onShareFiles(downloadedFiles),
87
+ },
88
+ ].filter(Boolean)
89
+ );
90
+ }
91
+
92
+ onClose();
93
+ } catch (error) {
94
+ setExporting(false);
95
+ setExportProgress('');
96
+ Alert.alert('Export Failed', error.message);
97
+ }
98
+ };
99
+
100
+ return (
101
+ <BottomSheet visible={visible} onClose={onClose} height={450}>
102
+ <View style={styles.container}>
103
+ <Text style={styles.title}>Export Pages</Text>
104
+
105
+ {exporting ? (
106
+ <View style={styles.progressContainer}>
107
+ <ActivityIndicator size="large" color="#6366F1" />
108
+ <Text style={styles.progressText}>{exportProgress}</Text>
109
+ </View>
110
+ ) : (
111
+ <>
112
+ <Text style={styles.sectionTitle}>Export Current Page</Text>
113
+ <View style={styles.buttonGroup}>
114
+ <TouchableOpacity
115
+ style={[styles.exportButton, styles.pngButton]}
116
+ onPress={() => handleExport('png', 'high', 'current')}>
117
+ <Text style={styles.buttonIcon}>🖼️</Text>
118
+ <Text style={styles.buttonText}>PNG (High)</Text>
119
+ <Text style={styles.buttonHint}>Page {currentPage}</Text>
120
+ </TouchableOpacity>
121
+
122
+ <TouchableOpacity
123
+ style={[styles.exportButton, styles.jpegButton]}
124
+ onPress={() => handleExport('jpeg', 'medium', 'current')}>
125
+ <Text style={styles.buttonIcon}>📸</Text>
126
+ <Text style={styles.buttonText}>JPEG (Med)</Text>
127
+ <Text style={styles.buttonHint}>Page {currentPage}</Text>
128
+ </TouchableOpacity>
129
+ </View>
130
+
131
+ <Text style={styles.sectionTitle}>Export Multiple Pages</Text>
132
+ <View style={styles.buttonGroup}>
133
+ <TouchableOpacity
134
+ style={[styles.exportButton, styles.rangeButton]}
135
+ onPress={() => handleExport('png', 'high', 'range')}>
136
+ <Text style={styles.buttonIcon}>📚</Text>
137
+ <Text style={styles.buttonText}>First {Math.min(3, totalPages)} Pages</Text>
138
+ <Text style={styles.buttonHint}>PNG Format</Text>
139
+ </TouchableOpacity>
140
+
141
+ <TouchableOpacity
142
+ style={[styles.exportButton, styles.rangeButton]}
143
+ onPress={() => handleExport('jpeg', 'medium', 'range')}>
144
+ <Text style={styles.buttonIcon}>📑</Text>
145
+ <Text style={styles.buttonText}>First {Math.min(3, totalPages)} Pages</Text>
146
+ <Text style={styles.buttonHint}>JPEG Format</Text>
147
+ </TouchableOpacity>
148
+ </View>
149
+ </>
150
+ )}
151
+ </View>
152
+ </BottomSheet>
153
+ );
154
+ };
155
+
156
+ const styles = StyleSheet.create({
157
+ container: {
158
+ flex: 1,
159
+ },
160
+ title: {
161
+ fontSize: 22,
162
+ fontWeight: 'bold',
163
+ color: '#1F2937',
164
+ marginBottom: 20,
165
+ },
166
+ sectionTitle: {
167
+ fontSize: 16,
168
+ fontWeight: '600',
169
+ color: '#374151',
170
+ marginTop: 16,
171
+ marginBottom: 12,
172
+ },
173
+ buttonGroup: {
174
+ flexDirection: 'row',
175
+ gap: 12,
176
+ },
177
+ exportButton: {
178
+ flex: 1,
179
+ padding: 16,
180
+ borderRadius: 12,
181
+ alignItems: 'center',
182
+ borderWidth: 2,
183
+ },
184
+ pngButton: {
185
+ backgroundColor: '#EEF2FF',
186
+ borderColor: '#6366F1',
187
+ },
188
+ jpegButton: {
189
+ backgroundColor: '#FEF3C7',
190
+ borderColor: '#F59E0B',
191
+ },
192
+ rangeButton: {
193
+ backgroundColor: '#ECFDF5',
194
+ borderColor: '#10B981',
195
+ },
196
+ buttonIcon: {
197
+ fontSize: 32,
198
+ marginBottom: 8,
199
+ },
200
+ buttonText: {
201
+ fontSize: 14,
202
+ fontWeight: '600',
203
+ color: '#1F2937',
204
+ marginBottom: 4,
205
+ },
206
+ buttonHint: {
207
+ fontSize: 12,
208
+ color: '#6B7280',
209
+ },
210
+ progressContainer: {
211
+ flex: 1,
212
+ justifyContent: 'center',
213
+ alignItems: 'center',
214
+ },
215
+ progressText: {
216
+ marginTop: 16,
217
+ fontSize: 16,
218
+ color: '#374151',
219
+ fontWeight: '500',
220
+ },
221
+ });
222
+
223
+ export default ExportMenu;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * LoadingOverlay Component - Full-screen loading indicator
3
+ */
4
+
5
+ import React from 'react';
6
+ import {
7
+ View,
8
+ Text,
9
+ ActivityIndicator,
10
+ StyleSheet,
11
+ Modal,
12
+ } from 'react-native';
13
+
14
+ const LoadingOverlay = ({
15
+ visible,
16
+ message = 'Loading...',
17
+ }) => {
18
+ return (
19
+ <Modal visible={visible} transparent animationType="fade">
20
+ <View style={styles.overlay}>
21
+ <View style={styles.container}>
22
+ <ActivityIndicator size="large" color="#6366F1" />
23
+ <Text style={styles.message}>{message}</Text>
24
+ </View>
25
+ </View>
26
+ </Modal>
27
+ );
28
+ };
29
+
30
+ const styles = StyleSheet.create({
31
+ overlay: {
32
+ flex: 1,
33
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
34
+ justifyContent: 'center',
35
+ alignItems: 'center',
36
+ },
37
+ container: {
38
+ backgroundColor: '#FFFFFF',
39
+ borderRadius: 12,
40
+ padding: 24,
41
+ alignItems: 'center',
42
+ minWidth: 150,
43
+ },
44
+ message: {
45
+ marginTop: 12,
46
+ fontSize: 16,
47
+ color: '#1F2937',
48
+ fontWeight: '500',
49
+ },
50
+ });
51
+
52
+ export default LoadingOverlay;