append preview (#9)

* append preview

* remove alert

* revert page change

* revert allert

* backpress logic for all screens

* shift scanning state

* remove alert

* save on device

* revert save on device

* on press back and outside fix for rename dialog
This commit is contained in:
2026-04-23 00:06:34 +05:30
committed by GitHub
parent 77d4b4bd87
commit 396c743d93
4 changed files with 62 additions and 76 deletions

28
App.tsx
View File

@@ -1,5 +1,5 @@
import React, {useState, useCallback, useEffect} from 'react';
import {BackHandler} from 'react-native';
import {Alert, BackHandler} from 'react-native';
import HomeScreen from './src/screens/HomeScreen';
import ScanScreen from './src/screens/ScanScreen';
import ReviewScreen from './src/screens/ReviewScreen';
@@ -38,6 +38,13 @@ export default function App() {
[createDocument],
);
const confirmDiscardReview = useCallback(() => {
Alert.alert('Discard scan?', 'Going back will discard all scanned pages.', [
{text: 'Stay', style: 'cancel'},
{text: 'Discard', style: 'destructive', onPress: goHome},
]);
}, [goHome]);
const handleView = useCallback((doc: ScannedDocument) => {
setViewingDoc(doc);
setScreen('viewer');
@@ -46,11 +53,22 @@ export default function App() {
useEffect(() => {
if (screen === 'home') return;
const sub = BackHandler.addEventListener('hardwareBackPress', () => {
goHome();
return true;
if (screen === 'scan') {
goHome();
return true;
}
if (screen === 'review') {
confirmDiscardReview();
return true;
}
if (screen === 'viewer') {
goHome();
return true;
}
return false;
});
return () => sub.remove();
}, [screen, goHome]);
}, [screen, goHome, confirmDiscardReview]);
const handleRenameFromViewer = useCallback(
(name: string) => {
@@ -74,7 +92,7 @@ export default function App() {
pages={pendingPages}
onSave={handleSaveDocument}
onAddMore={() => setScreen('scan')}
onCancel={goHome}
onCancel={confirmDiscardReview}
/>
);
}

View File

@@ -8,6 +8,7 @@ import {
StyleSheet,
Dimensions,
StatusBar,
Alert,
} from 'react-native';
import {ScannedPage, FilterMode} from '../types';
import FilterPicker from '../components/FilterPicker';

View File

@@ -2,7 +2,6 @@ import React, {useState, useRef, useCallback, useEffect} from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Alert,
ActivityIndicator,
@@ -34,7 +33,6 @@ async function copyToPermanent(tempUri: string): Promise<string> {
export default function ScanScreen({onComplete, onCancel}: Props) {
const t = useTheme();
const [scanning, setScanning] = useState(false);
const [pageCount, setPageCount] = useState(0);
const pagesRef = useRef<ScannedPage[]>([]);
const scan = useCallback(async () => {
@@ -69,17 +67,8 @@ export default function ScanScreen({onComplete, onCancel}: Props) {
}));
pagesRef.current = [...pagesRef.current, ...newPages];
setPageCount(pagesRef.current.length);
setScanning(false);
Alert.alert(
`${pagesRef.current.length} page${pagesRef.current.length !== 1 ? 's' : ''} scanned`,
'Add more pages or finish?',
[
{text: 'Add More', onPress: scan},
{text: 'Finish', style: 'default', onPress: () => onComplete(pagesRef.current)},
],
);
onComplete(pagesRef.current);
} catch (err: any) {
setScanning(false);
Alert.alert('Scan Error', err?.message ?? 'Could not scan document.');
@@ -97,21 +86,7 @@ export default function ScanScreen({onComplete, onCancel}: Props) {
{scanning && (
<View style={styles.center}>
<ActivityIndicator size="large" color={t.accent} />
<Text style={[styles.hint, {color: t.textSecondary}]}>Opening camera</Text>
</View>
)}
{!scanning && pageCount > 0 && (
<View style={styles.center}>
<Text style={[styles.count, {color: t.text}]}>{pageCount}</Text>
<Text style={[styles.pagesLabel, {color: t.textSecondary}]}>pages ready</Text>
<View style={styles.actions}>
<TouchableOpacity style={[styles.btnSecondary, {borderColor: t.accent}]} onPress={scan}>
<Text style={[styles.btnSecondaryText, {color: t.accent}]}>Add More</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btnPrimary} onPress={() => onComplete(pagesRef.current)}>
<Text style={styles.btnPrimaryText}>Finish</Text>
</TouchableOpacity>
</View>
<Text style={[styles.hint, {color: t.textSecondary}]}>Processing pages</Text>
</View>
)}
</View>
@@ -122,21 +97,4 @@ const styles = StyleSheet.create({
container: {flex: 1},
center: {flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12},
hint: {fontSize: 15, marginTop: 12},
count: {fontSize: 72, fontWeight: '700'},
pagesLabel: {fontSize: 18},
actions: {flexDirection: 'row', gap: 16, marginTop: 24},
btnPrimary: {
backgroundColor: '#007AFF',
paddingHorizontal: 32,
paddingVertical: 14,
borderRadius: 28,
},
btnPrimaryText: {color: '#fff', fontSize: 16, fontWeight: '600'},
btnSecondary: {
borderWidth: 1,
paddingHorizontal: 32,
paddingVertical: 14,
borderRadius: 28,
},
btnSecondaryText: {fontSize: 16, fontWeight: '600'},
});

View File

@@ -140,40 +140,49 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
</TouchableOpacity>
</View>
{generating && (
{generating && (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#fff" />
<Text style={styles.loadingText}>Generating PDF</Text>
</View>
)}
<Modal visible={renaming} transparent animationType="fade">
<View style={styles.modalBg}>
<View style={[styles.modalBox, {backgroundColor: t.surface}]}>
<Text style={[styles.modalTitle, {color: t.text}]}>Rename Document</Text>
<TextInput
style={[styles.modalInput, {backgroundColor: t.bg, color: t.text, borderColor: t.border}]}
value={renameText}
onChangeText={setRenameText}
autoFocus
selectTextOnFocus
placeholderTextColor={t.textSecondary}
/>
<View style={styles.modalActions}>
<TouchableOpacity onPress={() => setRenaming(false)} style={styles.modalBtn}>
<Text style={[styles.modalCancel, {color: t.textSecondary}]}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
if (renameText.trim()) onRename(renameText.trim());
setRenaming(false);
}}
style={styles.modalBtn}>
<Text style={[styles.modalConfirm, {color: t.accent}]}>Rename</Text>
</TouchableOpacity>
<Modal
visible={renaming}
transparent
animationType="fade"
onRequestClose={() => setRenaming(false)}>
<TouchableOpacity
style={styles.modalBg}
activeOpacity={1}
onPress={() => setRenaming(false)}>
<TouchableOpacity activeOpacity={1} onPress={() => {}}>
<View style={[styles.modalBox, {backgroundColor: t.surface}]}>
<Text style={[styles.modalTitle, {color: t.text}]}>Rename Document</Text>
<TextInput
style={[styles.modalInput, {backgroundColor: t.bg, color: t.text, borderColor: t.border}]}
value={renameText}
onChangeText={setRenameText}
autoFocus
selectTextOnFocus
placeholderTextColor={t.textSecondary}
/>
<View style={styles.modalActions}>
<TouchableOpacity onPress={() => setRenaming(false)} style={styles.modalBtn}>
<Text style={[styles.modalCancel, {color: t.danger}]}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
if (renameText.trim()) onRename(renameText.trim());
setRenaming(false);
}}
style={styles.modalBtn}>
<Text style={[styles.modalConfirm, {color: t.accent}]}>Rename</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
</Modal>
</View>
);