29
App.tsx
29
App.tsx
@@ -1,5 +1,5 @@
|
|||||||
import React, {useState, useCallback, useEffect} from 'react';
|
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 HomeScreen from './src/screens/HomeScreen';
|
||||||
import ScanScreen from './src/screens/ScanScreen';
|
import ScanScreen from './src/screens/ScanScreen';
|
||||||
import ReviewScreen from './src/screens/ReviewScreen';
|
import ReviewScreen from './src/screens/ReviewScreen';
|
||||||
@@ -30,10 +30,12 @@ export default function App() {
|
|||||||
|
|
||||||
const handleSaveDocument = useCallback(
|
const handleSaveDocument = useCallback(
|
||||||
(pages: ScannedPage[], _filter: FilterMode) => {
|
(pages: ScannedPage[], _filter: FilterMode) => {
|
||||||
createDocument(pages);
|
const doc = createDocument(pages);
|
||||||
goHome();
|
setPendingPages([]);
|
||||||
|
setViewingDoc(doc);
|
||||||
|
setScreen('viewer');
|
||||||
},
|
},
|
||||||
[createDocument, goHome],
|
[createDocument],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleView = useCallback((doc: ScannedDocument) => {
|
const handleView = useCallback((doc: ScannedDocument) => {
|
||||||
@@ -51,23 +53,8 @@ export default function App() {
|
|||||||
}, [screen, goHome]);
|
}, [screen, goHome]);
|
||||||
|
|
||||||
const handleRenameFromViewer = useCallback(
|
const handleRenameFromViewer = useCallback(
|
||||||
(_currentName: string) => {
|
(name: string) => {
|
||||||
if (!viewingDoc) return;
|
if (viewingDoc) renameDocument(viewingDoc.id, name);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[viewingDoc, renameDocument],
|
[viewingDoc, renameDocument],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ interface Props {
|
|||||||
document: ScannedDocument;
|
document: ScannedDocument;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
onDelete: () => 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;
|
const thumb = document.pages[0]?.uri;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity testID={`document-card-${document.id}`} style={styles.card} onPress={onPress} activeOpacity={0.8}>
|
<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'}
|
{document.pages.length} {document.pages.length === 1 ? 'page' : 'pages'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity testID={`delete-card-${document.id}`} style={styles.deleteBtn} onPress={onDelete} hitSlop={{top: 8, bottom: 8, left: 8, right: 8}}>
|
<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>
|
<Text style={styles.deleteText}>✕</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -97,9 +103,14 @@ const styles = StyleSheet.create({
|
|||||||
color: '#666',
|
color: '#666',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
deleteBtn: {
|
actions: {
|
||||||
padding: 12,
|
padding: 12,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
gap: 16,
|
||||||
|
},
|
||||||
|
renameText: {
|
||||||
|
color: '#007AFF',
|
||||||
|
fontSize: 18,
|
||||||
},
|
},
|
||||||
deleteText: {
|
deleteText: {
|
||||||
color: '#ff453a',
|
color: '#ff453a',
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export default function HomeScreen({documents, onScan, onView, onDelete, onRenam
|
|||||||
document={item}
|
document={item}
|
||||||
onPress={() => onView(item)}
|
onPress={() => onView(item)}
|
||||||
onDelete={() => confirmDelete(item)}
|
onDelete={() => confirmDelete(item)}
|
||||||
|
onRename={() => startRename(item)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
contentContainerStyle={styles.list}
|
contentContainerStyle={styles.list}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
StatusBar,
|
StatusBar,
|
||||||
Alert,
|
Alert,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Modal,
|
||||||
|
TextInput,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import Share from 'react-native-share';
|
import Share from 'react-native-share';
|
||||||
import {ScannedDocument} from '../types';
|
import {ScannedDocument} from '../types';
|
||||||
@@ -27,6 +29,8 @@ 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 [generating, setGenerating] = useState(false);
|
||||||
|
const [renaming, setRenaming] = useState(false);
|
||||||
|
const [renameText, setRenameText] = useState('');
|
||||||
|
|
||||||
const sharePdf = async (pageIndices?: number[]) => {
|
const sharePdf = async (pageIndices?: number[]) => {
|
||||||
setGenerating(true);
|
setGenerating(true);
|
||||||
@@ -129,7 +133,7 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
|
|||||||
<Text style={styles.toolIcon}>📄</Text>
|
<Text style={styles.toolIcon}>📄</Text>
|
||||||
<Text style={styles.toolLabel}>Share All</Text>
|
<Text style={styles.toolLabel}>Share All</Text>
|
||||||
</TouchableOpacity>
|
</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.toolIcon}>✎</Text>
|
||||||
<Text style={styles.toolLabel}>Rename</Text>
|
<Text style={styles.toolLabel}>Rename</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -145,6 +149,35 @@ export default function ViewerScreen({document, onBack, onDelete, onRename}: Pro
|
|||||||
<Text style={styles.loadingText}>Generating PDF…</Text>
|
<Text style={styles.loadingText}>Generating PDF…</Text>
|
||||||
</View>
|
</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>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -208,4 +241,18 @@ const styles = StyleSheet.create({
|
|||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
loadingText: {color: '#fff', fontSize: 15},
|
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