29
App.tsx
29
App.tsx
@@ -1,5 +1,5 @@
|
||||
import React, {useState, useCallback, useEffect} from 'react';
|
||||
import {Alert, BackHandler} from 'react-native';
|
||||
import {BackHandler} from 'react-native';
|
||||
import HomeScreen from './src/screens/HomeScreen';
|
||||
import ScanScreen from './src/screens/ScanScreen';
|
||||
import ReviewScreen from './src/screens/ReviewScreen';
|
||||
@@ -30,10 +30,12 @@ export default function App() {
|
||||
|
||||
const handleSaveDocument = useCallback(
|
||||
(pages: ScannedPage[], _filter: FilterMode) => {
|
||||
createDocument(pages);
|
||||
goHome();
|
||||
const doc = createDocument(pages);
|
||||
setPendingPages([]);
|
||||
setViewingDoc(doc);
|
||||
setScreen('viewer');
|
||||
},
|
||||
[createDocument, goHome],
|
||||
[createDocument],
|
||||
);
|
||||
|
||||
const handleView = useCallback((doc: ScannedDocument) => {
|
||||
@@ -51,23 +53,8 @@ export default function App() {
|
||||
}, [screen, goHome]);
|
||||
|
||||
const handleRenameFromViewer = useCallback(
|
||||
(_currentName: string) => {
|
||||
if (!viewingDoc) return;
|
||||
// Alert.prompt is iOS-only; on Android show a simple alert
|
||||
if (Alert.prompt) {
|
||||
Alert.prompt(
|
||||
'Rename',
|
||||
undefined,
|
||||
text => {
|
||||
if (text?.trim()) renameDocument(viewingDoc.id, text.trim());
|
||||
},
|
||||
'plain-text',
|
||||
_currentName,
|
||||
);
|
||||
} else {
|
||||
// Android fallback: navigate home and use HomeScreen rename modal
|
||||
renameDocument(viewingDoc.id, _currentName);
|
||||
}
|
||||
(name: string) => {
|
||||
if (viewingDoc) renameDocument(viewingDoc.id, name);
|
||||
},
|
||||
[viewingDoc, renameDocument],
|
||||
);
|
||||
|
||||
@@ -7,9 +7,10 @@ interface Props {
|
||||
document: ScannedDocument;
|
||||
onPress: () => void;
|
||||
onDelete: () => void;
|
||||
onRename: () => void;
|
||||
}
|
||||
|
||||
export default function DocumentCard({document, onPress, onDelete}: Props) {
|
||||
export default function DocumentCard({document, onPress, onDelete, onRename}: Props) {
|
||||
const thumb = document.pages[0]?.uri;
|
||||
return (
|
||||
<TouchableOpacity testID={`document-card-${document.id}`} style={styles.card} onPress={onPress} activeOpacity={0.8}>
|
||||
@@ -32,9 +33,14 @@ export default function DocumentCard({document, onPress, onDelete}: Props) {
|
||||
{document.pages.length} {document.pages.length === 1 ? 'page' : 'pages'}
|
||||
</Text>
|
||||
</View>
|
||||
<TouchableOpacity testID={`delete-card-${document.id}`} style={styles.deleteBtn} onPress={onDelete} hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
|
||||
<Text style={styles.deleteText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.actions}>
|
||||
<TouchableOpacity testID={`rename-card-${document.id}`} onPress={onRename} hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
|
||||
<Text style={styles.renameText}>✎</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity testID={`delete-card-${document.id}`} onPress={onDelete} hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
|
||||
<Text style={styles.deleteText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
@@ -97,9 +103,14 @@ const styles = StyleSheet.create({
|
||||
color: '#666',
|
||||
fontSize: 12,
|
||||
},
|
||||
deleteBtn: {
|
||||
actions: {
|
||||
padding: 12,
|
||||
justifyContent: 'center',
|
||||
gap: 16,
|
||||
},
|
||||
renameText: {
|
||||
color: '#007AFF',
|
||||
fontSize: 18,
|
||||
},
|
||||
deleteText: {
|
||||
color: '#ff453a',
|
||||
|
||||
@@ -68,6 +68,7 @@ export default function HomeScreen({documents, onScan, onView, onDelete, onRenam
|
||||
document={item}
|
||||
onPress={() => onView(item)}
|
||||
onDelete={() => confirmDelete(item)}
|
||||
onRename={() => startRename(item)}
|
||||
/>
|
||||
)}
|
||||
contentContainerStyle={styles.list}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
StatusBar,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
TextInput,
|
||||
} from 'react-native';
|
||||
import Share from 'react-native-share';
|
||||
import {ScannedDocument} from '../types';
|
||||
@@ -27,6 +29,8 @@ interface Props {
|
||||
export default function ViewerScreen({document, onBack, onDelete, onRename}: Props) {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [renaming, setRenaming] = useState(false);
|
||||
const [renameText, setRenameText] = useState('');
|
||||
|
||||
const sharePdf = async (pageIndices?: number[]) => {
|
||||
setGenerating(true);
|
||||
@@ -129,7 +133,7 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
|
||||
<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={() => { setRenameText(document.name); setRenaming(true); }}>
|
||||
<Text style={styles.toolIcon}>✎</Text>
|
||||
<Text style={styles.toolLabel}>Rename</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -145,6 +149,35 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
|
||||
<Text style={styles.loadingText}>Generating PDF…</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Modal visible={renaming} transparent animationType="fade">
|
||||
<View style={styles.modalBg}>
|
||||
<View style={styles.modalBox}>
|
||||
<Text style={styles.modalTitle}>Rename Document</Text>
|
||||
<TextInput
|
||||
style={styles.modalInput}
|
||||
value={renameText}
|
||||
onChangeText={setRenameText}
|
||||
autoFocus
|
||||
selectTextOnFocus
|
||||
placeholderTextColor="#666"
|
||||
/>
|
||||
<View style={styles.modalActions}>
|
||||
<TouchableOpacity onPress={() => setRenaming(false)} style={styles.modalBtn}>
|
||||
<Text style={styles.modalCancel}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (renameText.trim()) onRename(renameText.trim());
|
||||
setRenaming(false);
|
||||
}}
|
||||
style={styles.modalBtn}>
|
||||
<Text style={styles.modalConfirm}>Rename</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -208,4 +241,18 @@ const styles = StyleSheet.create({
|
||||
gap: 12,
|
||||
},
|
||||
loadingText: {color: '#fff', fontSize: 15},
|
||||
modalBg: {flex: 1, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', padding: 24},
|
||||
modalBox: {backgroundColor: '#1c1c1e', borderRadius: 16, padding: 20, gap: 16},
|
||||
modalTitle: {color: '#fff', fontSize: 17, fontWeight: '600'},
|
||||
modalInput: {
|
||||
backgroundColor: '#2c2c2e',
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
color: '#fff',
|
||||
fontSize: 15,
|
||||
},
|
||||
modalActions: {flexDirection: 'row', justifyContent: 'flex-end', gap: 16},
|
||||
modalBtn: {padding: 4},
|
||||
modalCancel: {color: '#888', fontSize: 15},
|
||||
modalConfirm: {color: '#007AFF', fontSize: 15, fontWeight: '600'},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user