shareing and pdf format
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -7,11 +7,13 @@
|
||||
"": {
|
||||
"name": "LensApp",
|
||||
"version": "0.0.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.73.0",
|
||||
"react-native-document-scanner-plugin": "^2.0.4",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-html-to-pdf": "^1.3.0",
|
||||
"react-native-image-crop-picker": "^0.51.1",
|
||||
"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": {
|
||||
"version": "0.51.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-image-crop-picker/-/react-native-image-crop-picker-0.51.1.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"react-native": "0.73.0",
|
||||
"react-native-document-scanner-plugin": "^2.0.4",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-html-to-pdf": "^1.3.0",
|
||||
"react-native-image-crop-picker": "^0.51.1",
|
||||
"react-native-share": "^12.2.6"
|
||||
},
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
Share,
|
||||
StatusBar,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import Share from 'react-native-share';
|
||||
import {ScannedDocument} from '../types';
|
||||
import {generatePdf} from '../utils/generatePdf';
|
||||
|
||||
const {width: SW} = Dimensions.get('window');
|
||||
|
||||
@@ -24,30 +26,33 @@ interface Props {
|
||||
|
||||
export default function ViewerScreen({document, onBack, onDelete, onRename}: Props) {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
|
||||
const shareImage = async () => {
|
||||
const page = document.pages[currentIndex];
|
||||
if (!page) return;
|
||||
const sharePdf = async (pageIndices?: number[]) => {
|
||||
setGenerating(true);
|
||||
try {
|
||||
await Share.share({
|
||||
title: document.name,
|
||||
url: page.uri,
|
||||
message: `${document.name} — page ${currentIndex + 1}`,
|
||||
});
|
||||
} catch {
|
||||
// user cancelled
|
||||
}
|
||||
};
|
||||
const pages =
|
||||
pageIndices
|
||||
? pageIndices.map(i => document.pages[i]).filter(Boolean)
|
||||
: document.pages;
|
||||
|
||||
const shareAll = async () => {
|
||||
try {
|
||||
await Share.share({
|
||||
title: document.name,
|
||||
message: `${document.name}: ${document.pages.length} pages`,
|
||||
url: document.pages[0]?.uri,
|
||||
const label =
|
||||
pageIndices?.length === 1
|
||||
? `${document.name} — page ${pageIndices[0] + 1}`
|
||||
: document.name;
|
||||
|
||||
const pdfPath = await generatePdf(pages, label);
|
||||
|
||||
await Share.open({
|
||||
title: label,
|
||||
type: 'application/pdf',
|
||||
url: `file://${pdfPath}`,
|
||||
failOnCancel: false,
|
||||
});
|
||||
} catch {
|
||||
// user cancelled
|
||||
} catch (err: any) {
|
||||
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}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={shareAll} style={styles.headerBtn}>
|
||||
<Text style={styles.shareText}>Share</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => sharePdf()}
|
||||
style={styles.headerBtn}
|
||||
disabled={generating}>
|
||||
<Text style={styles.shareText}>Share PDF</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -106,10 +114,20 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
|
||||
)}
|
||||
|
||||
<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.toolLabel}>Share Page</Text>
|
||||
</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)}>
|
||||
<Text style={styles.toolIcon}>✎</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>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{generating && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator size="large" color="#fff" />
|
||||
<Text style={styles.loadingText}>Generating PDF…</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -174,4 +199,12 @@ const styles = StyleSheet.create({
|
||||
toolIconDanger: {color: '#ff453a'},
|
||||
toolLabel: {fontSize: 11, color: '#007AFF'},
|
||||
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
60
src/utils/generatePdf.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user