react-native-optimized-pdf 1.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,123 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ const { View, Text, TouchableOpacity, TextInput, StyleSheet } = require('react-native');
3
+ import type { PdfNavigationControlsProps } from '../types';
4
+
5
+ /**
6
+ * Navigation controls for PDF pages
7
+ * Provides previous/next buttons and page number input
8
+ */
9
+ export const PdfNavigationControls: React.FC<PdfNavigationControlsProps> = ({
10
+ currentPage,
11
+ totalPages,
12
+ onNextPage,
13
+ onPrevPage,
14
+ onPageChange,
15
+ style,
16
+ }) => {
17
+ const [inputPage, setInputPage] = useState((currentPage + 1).toString());
18
+
19
+ useEffect(() => {
20
+ setInputPage((currentPage + 1).toString());
21
+ }, [currentPage]);
22
+
23
+ const handleChangePageInput = (text: string) => {
24
+ setInputPage(text);
25
+ };
26
+
27
+ const handleEndEditingPageInput = () => {
28
+ const pageNumber = parseInt(inputPage, 10);
29
+ if (!isNaN(pageNumber) && pageNumber >= 1 && pageNumber <= totalPages) {
30
+ onPageChange(pageNumber - 1);
31
+ } else {
32
+ // Reset to current page if invalid
33
+ setInputPage((currentPage + 1).toString());
34
+ }
35
+ };
36
+
37
+ const isFirstPage = currentPage === 0;
38
+ const isLastPage = currentPage === totalPages - 1;
39
+
40
+ return (
41
+ <View style={[styles.container, style]}>
42
+ <TouchableOpacity
43
+ onPress={onPrevPage}
44
+ disabled={isFirstPage}
45
+ style={[styles.navButton, isFirstPage && styles.disabledButton]}
46
+ accessibilityLabel="Previous page"
47
+ accessibilityRole="button"
48
+ >
49
+ <Text style={styles.navButtonText}>{'<'}</Text>
50
+ </TouchableOpacity>
51
+
52
+ <TextInput
53
+ style={styles.pageInput}
54
+ value={inputPage}
55
+ keyboardType="number-pad"
56
+ returnKeyType="done"
57
+ onChangeText={handleChangePageInput}
58
+ onEndEditing={handleEndEditingPageInput}
59
+ accessibilityLabel={`Current page: ${currentPage + 1} of ${totalPages}`}
60
+ />
61
+
62
+ <Text style={styles.pageInfo}>/ {totalPages}</Text>
63
+
64
+ <TouchableOpacity
65
+ onPress={onNextPage}
66
+ disabled={isLastPage}
67
+ style={[styles.navButton, isLastPage && styles.disabledButton]}
68
+ accessibilityLabel="Next page"
69
+ accessibilityRole="button"
70
+ >
71
+ <Text style={styles.navButtonText}>{'>'}</Text>
72
+ </TouchableOpacity>
73
+ </View>
74
+ );
75
+ };
76
+
77
+ const styles = StyleSheet.create({
78
+ container: {
79
+ position: 'absolute',
80
+ bottom: 20,
81
+ left: 0,
82
+ right: 0,
83
+ flexDirection: 'row',
84
+ justifyContent: 'center',
85
+ alignItems: 'center',
86
+ },
87
+ navButton: {
88
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
89
+ padding: 10,
90
+ marginHorizontal: 20,
91
+ borderRadius: 8,
92
+ minWidth: 44,
93
+ minHeight: 44,
94
+ justifyContent: 'center',
95
+ alignItems: 'center',
96
+ },
97
+ navButtonText: {
98
+ color: '#fff',
99
+ fontSize: 20,
100
+ fontWeight: '600',
101
+ },
102
+ disabledButton: {
103
+ opacity: 0.4,
104
+ },
105
+ pageInfo: {
106
+ color: '#000',
107
+ fontSize: 16,
108
+ fontWeight: '500',
109
+ },
110
+ pageInput: {
111
+ width: 50,
112
+ height: 40,
113
+ borderWidth: 1,
114
+ borderColor: '#000',
115
+ borderRadius: 5,
116
+ textAlign: 'center',
117
+ color: '#000',
118
+ marginHorizontal: 10,
119
+ backgroundColor: '#fff',
120
+ fontWeight: 'bold',
121
+ fontSize: 16,
122
+ },
123
+ });
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ const { View, Text, ActivityIndicator, StyleSheet } = require('react-native');
3
+ import type { PdfLoadingOverlayProps, PdfErrorOverlayProps } from '../types';
4
+
5
+ /**
6
+ * Loading overlay with progress indicator
7
+ */
8
+ export const PdfLoadingOverlay: React.FC<PdfLoadingOverlayProps> = ({ progress, style }) => {
9
+ return (
10
+ <View style={[styles.container, style]}>
11
+ <ActivityIndicator size="large" color="#fff" />
12
+ <Text style={styles.progressText}>{progress}%</Text>
13
+ </View>
14
+ );
15
+ };
16
+
17
+ /**
18
+ * Error overlay for displaying error messages
19
+ */
20
+ export const PdfErrorOverlay: React.FC<PdfErrorOverlayProps> = ({ error, style }) => {
21
+ return (
22
+ <View style={[styles.container, style]}>
23
+ <Text style={styles.errorText}>{error}</Text>
24
+ </View>
25
+ );
26
+ };
27
+
28
+ const styles = StyleSheet.create({
29
+ container: {
30
+ flex: 1,
31
+ justifyContent: 'center',
32
+ alignItems: 'center',
33
+ padding: 20,
34
+ },
35
+ progressText: {
36
+ color: '#fff',
37
+ marginTop: 10,
38
+ fontSize: 16,
39
+ fontWeight: '600',
40
+ },
41
+ errorText: {
42
+ color: '#d32f2f',
43
+ fontSize: 16,
44
+ textAlign: 'center',
45
+ },
46
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Default values and constants for the PDF viewer
3
+ */
4
+
5
+ export const DEFAULT_MAXIMUM_ZOOM = 3;
6
+ export const DEFAULT_ENABLE_ANTIALIASING = true;
7
+ export const DEFAULT_SHOW_NAVIGATION_CONTROLS = true;
8
+
9
+ export const ERROR_MESSAGES = {
10
+ DOWNLOAD_FAILED: 'Failed to download PDF',
11
+ LOAD_FAILED: 'Failed to load PDF',
12
+ INVALID_SOURCE: 'Invalid PDF source',
13
+ } as const;
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { default } from './OptimizedPdfView';
2
+ export { PdfCacheService } from './services/pdfCache';
3
+ export { PdfNavigationControls } from './components/PdfNavigationControls';
4
+ export { PdfLoadingOverlay, PdfErrorOverlay } from './components/PdfOverlays';
5
+ export * from './types';
@@ -0,0 +1,113 @@
1
+ import RNFS from 'react-native-fs';
2
+ import md5 from 'crypto-js/md5';
3
+ import type { PdfSource } from '../types';
4
+
5
+ /**
6
+ * Service for handling PDF caching and downloads
7
+ */
8
+ export class PdfCacheService {
9
+ /**
10
+ * Get the local file path for a cached PDF
11
+ */
12
+ static getCacheFilePath(source: PdfSource): string {
13
+ const fileName = source.cacheFileName || `${md5(source.uri).toString()}.pdf`;
14
+ return `${RNFS.CachesDirectoryPath}/${fileName}`;
15
+ }
16
+
17
+ /**
18
+ * Check if a cached PDF exists and is still valid
19
+ */
20
+ static async isCacheValid(source: PdfSource): Promise<boolean> {
21
+ const localPath = this.getCacheFilePath(source);
22
+ const exists = await RNFS.exists(localPath);
23
+
24
+ if (!exists) {
25
+ return false;
26
+ }
27
+
28
+ // If expiration is not set, cache is always valid
29
+ if (!source.expiration || source.expiration <= 0) {
30
+ return true;
31
+ }
32
+
33
+ // Check if cache has expired
34
+ const stat = await RNFS.stat(localPath);
35
+ const now = Date.now() / 1000;
36
+ return now - stat.mtime < source.expiration;
37
+ }
38
+
39
+ /**
40
+ * Download a PDF file and cache it locally
41
+ * @param source PDF source configuration
42
+ * @param onProgress Optional callback for download progress
43
+ * @returns Local file path
44
+ */
45
+ static async downloadPdf(
46
+ source: PdfSource,
47
+ onProgress?: (percent: number) => void,
48
+ ): Promise<string> {
49
+ const localPath = this.getCacheFilePath(source);
50
+
51
+ // Check if we can use cached version
52
+ if (source.cache !== false && (await this.isCacheValid(source))) {
53
+ return localPath;
54
+ }
55
+
56
+ // Download the file
57
+ const { promise } = RNFS.downloadFile({
58
+ fromUrl: source.uri,
59
+ toFile: localPath,
60
+ background: false,
61
+ headers: source.headers,
62
+ progressDivider: 1,
63
+ progress: (res) => {
64
+ if (res.contentLength > 0 && onProgress) {
65
+ const percent = Math.floor((res.bytesWritten / res.contentLength) * 100);
66
+ onProgress(percent);
67
+ }
68
+ },
69
+ begin: () => {
70
+ // This callback is required for progress to update properly
71
+ },
72
+ });
73
+
74
+ await promise;
75
+ return localPath;
76
+ }
77
+
78
+ /**
79
+ * Clear a specific cached PDF
80
+ */
81
+ static async clearCache(source: PdfSource): Promise<void> {
82
+ const localPath = this.getCacheFilePath(source);
83
+ const exists = await RNFS.exists(localPath);
84
+ if (exists) {
85
+ await RNFS.unlink(localPath);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Clear all cached PDFs
91
+ */
92
+ static async clearAllCache(): Promise<void> {
93
+ const files = await RNFS.readDir(RNFS.CachesDirectoryPath);
94
+ const pdfFiles = files.filter((file) => file.name.endsWith('.pdf'));
95
+ await Promise.all(pdfFiles.map((file) => RNFS.unlink(file.path)));
96
+ }
97
+
98
+ /**
99
+ * Get total size of cached PDFs in bytes
100
+ */
101
+ static async getCacheSize(): Promise<number> {
102
+ const files = await RNFS.readDir(RNFS.CachesDirectoryPath);
103
+ const pdfFiles = files.filter((file) => file.name.endsWith('.pdf'));
104
+ return pdfFiles.reduce((total, file) => total + file.size, 0);
105
+ }
106
+
107
+ /**
108
+ * Ensure the file path has the file:// protocol
109
+ */
110
+ static normalizeFilePath(path: string): string {
111
+ return path.startsWith('file://') ? path : `file://${path}`;
112
+ }
113
+ }
@@ -0,0 +1,112 @@
1
+ import { ViewStyle } from 'react-native';
2
+
3
+ /**
4
+ * Configuration for PDF source
5
+ */
6
+ export interface PdfSource {
7
+ /** URI of the PDF file (remote URL or local file path) */
8
+ uri: string;
9
+ /** Whether to cache the PDF file locally. Default: true */
10
+ cache?: boolean;
11
+ /** Custom filename for the cached file. If not provided, uses MD5 hash of URI */
12
+ cacheFileName?: string;
13
+ /** Cache expiration time in seconds. If not set, cache never expires */
14
+ expiration?: number;
15
+ /** HTTP method for download. Default: 'GET' */
16
+ method?: string;
17
+ /** HTTP headers for download request */
18
+ headers?: Record<string, string>;
19
+ }
20
+
21
+ /**
22
+ * Page dimensions returned by onLoadComplete event
23
+ */
24
+ export interface PdfPageDimensions {
25
+ width: number;
26
+ height: number;
27
+ }
28
+
29
+ /**
30
+ * Error event from native module
31
+ */
32
+ export interface PdfErrorEvent {
33
+ nativeEvent: {
34
+ message: string;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Props for OptimizedPdfView component
40
+ */
41
+ export interface OptimizedPdfViewProps {
42
+ /** PDF source configuration */
43
+ source: PdfSource;
44
+ /** Password for encrypted PDF files */
45
+ password?: string;
46
+ /** Maximum zoom level. Default: 3 */
47
+ maximumZoom?: number;
48
+ /** Enable antialiasing for better rendering quality. Default: true */
49
+ enableAntialiasing?: boolean;
50
+ /** Show built-in navigation controls. Default: true */
51
+ showNavigationControls?: boolean;
52
+ /** Custom style for the container */
53
+ style?: ViewStyle;
54
+ /** Callback when PDF is loaded successfully */
55
+ onLoadComplete?: (currentPage: number, dimensions: PdfPageDimensions) => void;
56
+ /** Callback when an error occurs */
57
+ onError?: (error: PdfErrorEvent) => void;
58
+ /** Callback when page count is available */
59
+ onPageCount?: (numberOfPages: number) => void;
60
+ /** Callback when page changes */
61
+ onPageChange?: (currentPage: number) => void;
62
+ /** Callback when PDF requires a password */
63
+ onPasswordRequired?: () => void;
64
+ }
65
+
66
+ /**
67
+ * Native event for load complete
68
+ */
69
+ export interface NativeLoadCompleteEvent {
70
+ nativeEvent: {
71
+ currentPage: number;
72
+ width: number;
73
+ height: number;
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Native event for page count
79
+ */
80
+ export interface NativePageCountEvent {
81
+ nativeEvent: {
82
+ numberOfPages: number;
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Props for navigation controls component
88
+ */
89
+ export interface PdfNavigationControlsProps {
90
+ currentPage: number;
91
+ totalPages: number;
92
+ onNextPage: () => void;
93
+ onPrevPage: () => void;
94
+ onPageChange: (page: number) => void;
95
+ style?: ViewStyle;
96
+ }
97
+
98
+ /**
99
+ * Props for loading overlay component
100
+ */
101
+ export interface PdfLoadingOverlayProps {
102
+ progress: number;
103
+ style?: ViewStyle;
104
+ }
105
+
106
+ /**
107
+ * Props for error overlay component
108
+ */
109
+ export interface PdfErrorOverlayProps {
110
+ error: string;
111
+ style?: ViewStyle;
112
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2017",
4
+ "module": "commonjs",
5
+ "lib": ["es6"],
6
+ "allowJs": true,
7
+ "jsx": "react-native",
8
+ "noEmit": true,
9
+ "isolatedModules": true,
10
+ "strict": true,
11
+ "moduleResolution": "node",
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true
15
+ },
16
+ "exclude": ["node_modules", "**/__tests__/**"]
17
+ }