shareing and pdf format

This commit is contained in:
2026-04-18 14:26:00 +05:30
parent db40c62154
commit 7436dfe991
4 changed files with 133 additions and 24 deletions

15
package-lock.json generated
View File

@@ -7,11 +7,13 @@
"": { "": {
"name": "LensApp", "name": "LensApp",
"version": "0.0.1", "version": "0.0.1",
"hasInstallScript": true,
"dependencies": { "dependencies": {
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.73.0", "react-native": "0.73.0",
"react-native-document-scanner-plugin": "^2.0.4", "react-native-document-scanner-plugin": "^2.0.4",
"react-native-fs": "^2.20.0", "react-native-fs": "^2.20.0",
"react-native-html-to-pdf": "^1.3.0",
"react-native-image-crop-picker": "^0.51.1", "react-native-image-crop-picker": "^0.51.1",
"react-native-share": "^12.2.6" "react-native-share": "^12.2.6"
}, },
@@ -10495,6 +10497,19 @@
} }
} }
}, },
"node_modules/react-native-html-to-pdf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/react-native-html-to-pdf/-/react-native-html-to-pdf-1.3.0.tgz",
"integrity": "sha512-UCbQO1k5yeajaJrEF7Wqq8ojdgpxsTbICQvtorXmuO1LkU/a6B3/tZyLes4zXCiq/5nTMTNA/k9m5bYStg5S0Q==",
"license": "MIT",
"workspaces": [
"example"
],
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-image-crop-picker": { "node_modules/react-native-image-crop-picker": {
"version": "0.51.1", "version": "0.51.1",
"resolved": "https://registry.npmjs.org/react-native-image-crop-picker/-/react-native-image-crop-picker-0.51.1.tgz", "resolved": "https://registry.npmjs.org/react-native-image-crop-picker/-/react-native-image-crop-picker-0.51.1.tgz",

View File

@@ -15,6 +15,7 @@
"react-native": "0.73.0", "react-native": "0.73.0",
"react-native-document-scanner-plugin": "^2.0.4", "react-native-document-scanner-plugin": "^2.0.4",
"react-native-fs": "^2.20.0", "react-native-fs": "^2.20.0",
"react-native-html-to-pdf": "^1.3.0",
"react-native-image-crop-picker": "^0.51.1", "react-native-image-crop-picker": "^0.51.1",
"react-native-share": "^12.2.6" "react-native-share": "^12.2.6"
}, },

View File

@@ -7,11 +7,13 @@ import {
TouchableOpacity, TouchableOpacity,
StyleSheet, StyleSheet,
Dimensions, Dimensions,
Share,
StatusBar, StatusBar,
Alert, Alert,
ActivityIndicator,
} from 'react-native'; } from 'react-native';
import Share from 'react-native-share';
import {ScannedDocument} from '../types'; import {ScannedDocument} from '../types';
import {generatePdf} from '../utils/generatePdf';
const {width: SW} = Dimensions.get('window'); const {width: SW} = Dimensions.get('window');
@@ -24,30 +26,33 @@ interface Props {
export default function ViewerScreen({document, onBack, onDelete, onRename}: Props) { export default function ViewerScreen({document, onBack, onDelete, onRename}: Props) {
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [generating, setGenerating] = useState(false);
const shareImage = async () => { const sharePdf = async (pageIndices?: number[]) => {
const page = document.pages[currentIndex]; setGenerating(true);
if (!page) return;
try { try {
await Share.share({ const pages =
title: document.name, pageIndices
url: page.uri, ? pageIndices.map(i => document.pages[i]).filter(Boolean)
message: `${document.name} — page ${currentIndex + 1}`, : document.pages;
});
} catch {
// user cancelled
}
};
const shareAll = async () => { const label =
try { pageIndices?.length === 1
await Share.share({ ? `${document.name} — page ${pageIndices[0] + 1}`
title: document.name, : document.name;
message: `${document.name}: ${document.pages.length} pages`,
url: document.pages[0]?.uri, const pdfPath = await generatePdf(pages, label);
await Share.open({
title: label,
type: 'application/pdf',
url: `file://${pdfPath}`,
failOnCancel: false,
}); });
} catch { } catch (err: any) {
// user cancelled Alert.alert('Share failed', err?.message ?? 'Could not generate PDF.');
} finally {
setGenerating(false);
} }
}; };
@@ -72,8 +77,11 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
{currentIndex + 1} / {document.pages.length} {currentIndex + 1} / {document.pages.length}
</Text> </Text>
</View> </View>
<TouchableOpacity onPress={shareAll} style={styles.headerBtn}> <TouchableOpacity
<Text style={styles.shareText}>Share</Text> onPress={() => sharePdf()}
style={styles.headerBtn}
disabled={generating}>
<Text style={styles.shareText}>Share PDF</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@@ -106,10 +114,20 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
)} )}
<View style={styles.toolbar}> <View style={styles.toolbar}>
<TouchableOpacity style={styles.toolBtn} onPress={shareImage}> <TouchableOpacity
style={styles.toolBtn}
onPress={() => sharePdf([currentIndex])}
disabled={generating}>
<Text style={styles.toolIcon}></Text> <Text style={styles.toolIcon}></Text>
<Text style={styles.toolLabel}>Share Page</Text> <Text style={styles.toolLabel}>Share Page</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity
style={styles.toolBtn}
onPress={() => sharePdf()}
disabled={generating}>
<Text style={styles.toolIcon}>📄</Text>
<Text style={styles.toolLabel}>Share All</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.toolBtn} onPress={() => onRename(document.name)}> <TouchableOpacity style={styles.toolBtn} onPress={() => onRename(document.name)}>
<Text style={styles.toolIcon}></Text> <Text style={styles.toolIcon}></Text>
<Text style={styles.toolLabel}>Rename</Text> <Text style={styles.toolLabel}>Rename</Text>
@@ -119,6 +137,13 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
<Text style={[styles.toolLabel, styles.toolLabelDanger]}>Delete</Text> <Text style={[styles.toolLabel, styles.toolLabelDanger]}>Delete</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{generating && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#fff" />
<Text style={styles.loadingText}>Generating PDF</Text>
</View>
)}
</View> </View>
); );
} }
@@ -174,4 +199,12 @@ const styles = StyleSheet.create({
toolIconDanger: {color: '#ff453a'}, toolIconDanger: {color: '#ff453a'},
toolLabel: {fontSize: 11, color: '#007AFF'}, toolLabel: {fontSize: 11, color: '#007AFF'},
toolLabelDanger: {color: '#ff453a'}, toolLabelDanger: {color: '#ff453a'},
loadingOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.65)',
alignItems: 'center',
justifyContent: 'center',
gap: 12,
},
loadingText: {color: '#fff', fontSize: 15},
}); });

60
src/utils/generatePdf.ts Normal file
View File

@@ -0,0 +1,60 @@
import {generatePDF as rnGeneratePDF} from 'react-native-html-to-pdf';
import RNFS from 'react-native-fs';
import {ScannedPage} from '../types';
const PDF_DIR = `${RNFS.DocumentDirectoryPath}/pdfs`;
async function pageToBase64(uri: string): Promise<string> {
const path = uri.startsWith('file://') ? uri.slice(7) : uri;
return RNFS.readFile(path, 'base64');
}
export async function generatePdf(
pages: ScannedPage[],
name: string,
): Promise<string> {
await RNFS.mkdir(PDF_DIR);
const imageHtml = await Promise.all(
pages.map(async p => {
const b64 = await pageToBase64(p.uri);
return `<div class="page"><img src="data:image/jpeg;base64,${b64}" /></div>`;
}),
);
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #fff; }
.page {
width: 100%;
page-break-after: always;
display: flex;
justify-content: center;
align-items: center;
}
.page:last-child { page-break-after: avoid; }
img { width: 100%; height: auto; display: block; }
</style>
</head>
<body>${imageHtml.join('\n')}</body>
</html>
`;
const result = await rnGeneratePDF({
html,
fileName: name.replace(/[^a-z0-9_\-]/gi, '_'),
directory: PDF_DIR,
base64: false,
});
if (!result.filePath) {
throw new Error('PDF generation failed — no output path returned');
}
return result.filePath;
}