delete fikes
This commit is contained in:
+18
-15
@@ -1,9 +1,10 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, Alert, ActivityIndicator } from 'react-native';
|
import { View, Text, StyleSheet, FlatList, TouchableOpacity, Image, ActivityIndicator } from 'react-native';
|
||||||
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { useBatchStore } from '../hooks/useBatchStore';
|
import { useBatchStore } from '../hooks/useBatchStore';
|
||||||
import { usePdfGeneration } from '../hooks/usePdfGeneration';
|
import { usePdfGeneration } from '../hooks/usePdfGeneration';
|
||||||
|
import { ConfirmModal } from '../components/ConfirmModal';
|
||||||
|
|
||||||
type RootStackParamList = {
|
type RootStackParamList = {
|
||||||
Home: undefined;
|
Home: undefined;
|
||||||
@@ -22,6 +23,7 @@ export function BatchReviewScreen() {
|
|||||||
const batch = getBatch(route.params.batchId);
|
const batch = getBatch(route.params.batchId);
|
||||||
|
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
|
const [confirmDeleteVisible, setConfirmDeleteVisible] = useState(false);
|
||||||
|
|
||||||
if (!batch) {
|
if (!batch) {
|
||||||
return (
|
return (
|
||||||
@@ -44,22 +46,12 @@ export function BatchReviewScreen() {
|
|||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedCount === 0) return;
|
if (selectedCount === 0) return;
|
||||||
const label = selectedCount === 1 ? 'cette photo' : `ces ${selectedCount} photos`;
|
setConfirmDeleteVisible(true);
|
||||||
Alert.alert(
|
};
|
||||||
'Supprimer',
|
|
||||||
`Retirer ${label} du lot ?`,
|
const handleDeleteConfirm = () => {
|
||||||
[
|
|
||||||
{ text: 'Annuler', style: 'cancel' },
|
|
||||||
{
|
|
||||||
text: 'Supprimer',
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: () => {
|
|
||||||
selectedIds.forEach((id) => removePhotoFromBatch(batch.id, id));
|
selectedIds.forEach((id) => removePhotoFromBatch(batch.id, id));
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGroup = () => {
|
const handleGroup = () => {
|
||||||
@@ -131,6 +123,17 @@ export function BatchReviewScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
visible={confirmDeleteVisible}
|
||||||
|
title="Supprimer"
|
||||||
|
message={`Retirer ${selectedCount === 1 ? 'cette photo' : `ces ${selectedCount} photos`} du lot ?`}
|
||||||
|
options={[
|
||||||
|
{ label: 'Annuler' },
|
||||||
|
{ label: 'Supprimer', destructive: true, onPress: handleDeleteConfirm },
|
||||||
|
]}
|
||||||
|
onClose={() => setConfirmDeleteVisible(false)}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-16
@@ -10,7 +10,6 @@ import {
|
|||||||
Pressable,
|
Pressable,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Share,
|
Share,
|
||||||
Alert,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
@@ -20,6 +19,7 @@ import { useFile, useDownloadFile } from '../hooks/useFiles';
|
|||||||
import { fileStore } from '../services/fileStore';
|
import { fileStore } from '../services/fileStore';
|
||||||
import { TagChip } from '../components/TagChip';
|
import { TagChip } from '../components/TagChip';
|
||||||
import { FileThumbnail } from '../components/FileThumbnail';
|
import { FileThumbnail } from '../components/FileThumbnail';
|
||||||
|
import { ConfirmModal } from '../components/ConfirmModal';
|
||||||
import { SyncStatusBadge } from '../components/SyncStatusBadge';
|
import { SyncStatusBadge } from '../components/SyncStatusBadge';
|
||||||
import { GestureHandlerRootView, Gesture, GestureDetector } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView, Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||||
import Animated, { useSharedValue, useAnimatedStyle, withSpring, runOnJS } from 'react-native-reanimated';
|
import Animated, { useSharedValue, useAnimatedStyle, withSpring, runOnJS } from 'react-native-reanimated';
|
||||||
@@ -100,6 +100,7 @@ function DetailItem({ fileId, deviceFile, onSelectImage }: { fileId: string; dev
|
|||||||
|
|
||||||
const [optionsVisible, setOptionsVisible] = useState(false);
|
const [optionsVisible, setOptionsVisible] = useState(false);
|
||||||
const [deleting, setDeleting] = useState(false);
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
const [confirmDeleteVisible, setConfirmDeleteVisible] = useState(false);
|
||||||
const panelOffset = useSharedValue(PANEL_HEIGHT - PANEL_HEADER_VISIBLE);
|
const panelOffset = useSharedValue(PANEL_HEIGHT - PANEL_HEADER_VISIBLE);
|
||||||
const panelStartY = useSharedValue(0);
|
const panelStartY = useSharedValue(0);
|
||||||
const isPanelExpanded = useSharedValue(false);
|
const isPanelExpanded = useSharedValue(false);
|
||||||
@@ -136,17 +137,7 @@ function DetailItem({ fileId, deviceFile, onSelectImage }: { fileId: string; dev
|
|||||||
}
|
}
|
||||||
}, [uri, fileName, file?.url]);
|
}, [uri, fileName, file?.url]);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDeleteConfirm = useCallback(async () => {
|
||||||
setOptionsVisible(false);
|
|
||||||
Alert.alert(
|
|
||||||
'Supprimer',
|
|
||||||
`Supprimer "${fileName}" définitivement ?`,
|
|
||||||
[
|
|
||||||
{ text: 'Annuler', style: 'cancel' },
|
|
||||||
{
|
|
||||||
text: 'Supprimer',
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: async () => {
|
|
||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
try {
|
try {
|
||||||
const bid = localEntry?.backendId ?? (file as any)?.backendResourceId;
|
const bid = localEntry?.backendId ?? (file as any)?.backendResourceId;
|
||||||
@@ -164,12 +155,13 @@ function DetailItem({ fileId, deviceFile, onSelectImage }: { fileId: string; dev
|
|||||||
} catch {} finally {
|
} catch {} finally {
|
||||||
setDeleting(false);
|
setDeleting(false);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}, [fileName, fileId, localEntry, file, navigation]);
|
}, [fileName, fileId, localEntry, file, navigation]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
setOptionsVisible(false);
|
||||||
|
setConfirmDeleteVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const togglePanelJS = useCallback(() => {
|
const togglePanelJS = useCallback(() => {
|
||||||
if (isPanelExpanded.value) {
|
if (isPanelExpanded.value) {
|
||||||
panelOffset.value = withSpring(PANEL_HEIGHT - PANEL_HEADER_VISIBLE);
|
panelOffset.value = withSpring(PANEL_HEIGHT - PANEL_HEADER_VISIBLE);
|
||||||
@@ -387,6 +379,17 @@ function DetailItem({ fileId, deviceFile, onSelectImage }: { fileId: string; dev
|
|||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
visible={confirmDeleteVisible}
|
||||||
|
title="Supprimer"
|
||||||
|
message={`Supprimer "${fileName}" définitivement ?`}
|
||||||
|
options={[
|
||||||
|
{ label: 'Annuler' },
|
||||||
|
{ label: 'Supprimer', destructive: true, onPress: handleDeleteConfirm },
|
||||||
|
]}
|
||||||
|
onClose={() => setConfirmDeleteVisible(false)}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-9
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
||||||
import { View, FlatList, StyleSheet, TouchableOpacity, Text, Dimensions, Alert, Modal, TextInput } from 'react-native';
|
import { View, FlatList, StyleSheet, TouchableOpacity, Text, Dimensions, Modal, TextInput } from 'react-native';
|
||||||
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
useAddTags, useMoveResources, useFolders, useCreateFolder,
|
useAddTags, useMoveResources, useFolders, useCreateFolder,
|
||||||
} from '../hooks/useFiles';
|
} from '../hooks/useFiles';
|
||||||
import { SelectionPanel } from '../components/SelectionPanel';
|
import { SelectionPanel } from '../components/SelectionPanel';
|
||||||
|
import { ConfirmModal, type ConfirmOption } from '../components/ConfirmModal';
|
||||||
import { UnifiedFileItem } from '../types';
|
import { UnifiedFileItem } from '../types';
|
||||||
import { isFolder } from '../types';
|
import { isFolder } from '../types';
|
||||||
import { FileThumbnail } from '../components/FileThumbnail';
|
import { FileThumbnail } from '../components/FileThumbnail';
|
||||||
@@ -104,6 +105,7 @@ export function FolderScreen() {
|
|||||||
const [tagModalMode, setTagModalMode] = useState<'tag' | 'folder'>('tag');
|
const [tagModalMode, setTagModalMode] = useState<'tag' | 'folder'>('tag');
|
||||||
const [tagInput, setTagInput] = useState('');
|
const [tagInput, setTagInput] = useState('');
|
||||||
const [moveModalVisible, setMoveModalVisible] = useState(false);
|
const [moveModalVisible, setMoveModalVisible] = useState(false);
|
||||||
|
const [confirmDeleteState, setConfirmDeleteState] = useState<{ message: string; options: ConfirmOption[] } | null>(null);
|
||||||
|
|
||||||
const selectionMode = selectedIds.size > 0;
|
const selectionMode = selectedIds.size > 0;
|
||||||
|
|
||||||
@@ -142,12 +144,10 @@ export function FolderScreen() {
|
|||||||
return f?.syncStatus === 'synced';
|
return f?.syncStatus === 'synced';
|
||||||
});
|
});
|
||||||
const label = ids.length === 1 ? 'ce fichier' : `ces ${ids.length} fichiers`;
|
const label = ids.length === 1 ? 'ce fichier' : `ces ${ids.length} fichiers`;
|
||||||
const options: Array<{ text: string; style?: 'default' | 'cancel' | 'destructive'; onPress?: () => void }> = [
|
const options: ConfirmOption[] = [];
|
||||||
{ text: 'Annuler', style: 'cancel' },
|
|
||||||
];
|
|
||||||
if (hasSynced) {
|
if (hasSynced) {
|
||||||
options.push({
|
options.push({
|
||||||
text: 'Du device uniquement',
|
label: 'Du device uniquement',
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
const syncedIds = ids.filter((id) => {
|
const syncedIds = ids.filter((id) => {
|
||||||
const f = files.find((fi) => fi.id === id);
|
const f = files.find((fi) => fi.id === id);
|
||||||
@@ -161,8 +161,8 @@ export function FolderScreen() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
options.push({
|
options.push({
|
||||||
text: 'Du device + serveur',
|
label: 'Du device + serveur',
|
||||||
style: 'destructive',
|
destructive: true,
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
const f = files.find((fi) => fi.id === id);
|
const f = files.find((fi) => fi.id === id);
|
||||||
@@ -177,11 +177,12 @@ export function FolderScreen() {
|
|||||||
}
|
}
|
||||||
downloadRegistry.remove(id);
|
downloadRegistry.remove(id);
|
||||||
}
|
}
|
||||||
await queryClient.invalidateQueries({ queryKey: ['files'] });
|
await queryClient.invalidateQueries({ queryKey: ['resources'] });
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Alert.alert('Supprimer', `Supprimer ${label} ?`, options);
|
options.push({ label: 'Annuler' });
|
||||||
|
setConfirmDeleteState({ message: `Supprimer ${label} ?`, options });
|
||||||
}, [selectedIds, files, deleteFile, freeLocalSpace, queryClient]);
|
}, [selectedIds, files, deleteFile, freeLocalSpace, queryClient]);
|
||||||
|
|
||||||
const handleEdit = useCallback(() => {
|
const handleEdit = useCallback(() => {
|
||||||
@@ -358,6 +359,14 @@ export function FolderScreen() {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
visible={confirmDeleteState !== null}
|
||||||
|
title="Supprimer"
|
||||||
|
message={confirmDeleteState?.message}
|
||||||
|
options={confirmDeleteState?.options ?? []}
|
||||||
|
onClose={() => setConfirmDeleteState(null)}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-23
@@ -12,6 +12,7 @@ import { UnifiedFileItem, isFolder } from '../types';
|
|||||||
import { SearchBar, SearchFilters, MediaFilter } from '../components/SearchBar';
|
import { SearchBar, SearchFilters, MediaFilter } from '../components/SearchBar';
|
||||||
import { FileThumbnail } from '../components/FileThumbnail';
|
import { FileThumbnail } from '../components/FileThumbnail';
|
||||||
import { SettingsModal } from '../components/SettingsModal';
|
import { SettingsModal } from '../components/SettingsModal';
|
||||||
|
import { ConfirmModal, type ConfirmOption } from '../components/ConfirmModal';
|
||||||
import { UploadModal } from '../components/UploadModal';
|
import { UploadModal } from '../components/UploadModal';
|
||||||
import { SyncStatusIcon } from '../components/SyncStatusIcon';
|
import { SyncStatusIcon } from '../components/SyncStatusIcon';
|
||||||
import { useSyncQueue } from '../hooks/useSyncQueue';
|
import { useSyncQueue } from '../hooks/useSyncQueue';
|
||||||
@@ -119,6 +120,8 @@ export function HomeScreen() {
|
|||||||
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
||||||
const [globalSyncMode, setGlobalSyncMode] = useState<SyncGlobalMode>(() => safDirectory.getGlobalSyncMode());
|
const [globalSyncMode, setGlobalSyncMode] = useState<SyncGlobalMode>(() => safDirectory.getGlobalSyncMode());
|
||||||
const [globalSyncCellular, setGlobalSyncCellular] = useState(() => safDirectory.getGlobalSyncCellular());
|
const [globalSyncCellular, setGlobalSyncCellular] = useState(() => safDirectory.getGlobalSyncCellular());
|
||||||
|
const [confirmDeleteState, setConfirmDeleteState] = useState<{ message: string; options: ConfirmOption[] } | null>(null);
|
||||||
|
const [removeFolderConfirmId, setRemoveFolderConfirmId] = useState<string | null>(null);
|
||||||
const { pendingCount, isSyncing } = useSyncQueue();
|
const { pendingCount, isSyncing } = useSyncQueue();
|
||||||
useAutoSync();
|
useAutoSync();
|
||||||
|
|
||||||
@@ -247,12 +250,10 @@ export function HomeScreen() {
|
|||||||
return f?.syncStatus === 'synced';
|
return f?.syncStatus === 'synced';
|
||||||
});
|
});
|
||||||
const label = ids.length === 1 ? 'ce fichier' : `ces ${ids.length} fichiers`;
|
const label = ids.length === 1 ? 'ce fichier' : `ces ${ids.length} fichiers`;
|
||||||
const options: Array<{ text: string; style?: 'default' | 'cancel' | 'destructive'; onPress?: () => void }> = [
|
const options: ConfirmOption[] = [];
|
||||||
{ text: 'Annuler', style: 'cancel' },
|
|
||||||
];
|
|
||||||
if (hasSynced) {
|
if (hasSynced) {
|
||||||
options.push({
|
options.push({
|
||||||
text: 'Du device uniquement',
|
label: 'Du device uniquement',
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
const syncedIds = ids.filter((id) => {
|
const syncedIds = ids.filter((id) => {
|
||||||
const f = files.find((fi) => fi.id === id);
|
const f = files.find((fi) => fi.id === id);
|
||||||
@@ -266,8 +267,8 @@ export function HomeScreen() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
options.push({
|
options.push({
|
||||||
text: 'Du device + serveur',
|
label: 'Du device + serveur',
|
||||||
style: 'destructive',
|
destructive: true,
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
const f = files.find((fi) => fi.id === id);
|
const f = files.find((fi) => fi.id === id);
|
||||||
@@ -282,11 +283,12 @@ export function HomeScreen() {
|
|||||||
}
|
}
|
||||||
downloadRegistry.remove(id);
|
downloadRegistry.remove(id);
|
||||||
}
|
}
|
||||||
await queryClient.invalidateQueries({ queryKey: ['files'] });
|
await queryClient.invalidateQueries({ queryKey: ['resources'] });
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Alert.alert('Supprimer', `Supprimer ${label} ?`, options);
|
options.push({ label: 'Annuler' });
|
||||||
|
setConfirmDeleteState({ message: `Supprimer ${label} ?`, options });
|
||||||
}, [selectedIds, files, freeLocalSpace, queryClient]);
|
}, [selectedIds, files, freeLocalSpace, queryClient]);
|
||||||
|
|
||||||
const handleEdit = useCallback(() => {
|
const handleEdit = useCallback(() => {
|
||||||
@@ -339,22 +341,16 @@ export function HomeScreen() {
|
|||||||
}, [refreshFolders]);
|
}, [refreshFolders]);
|
||||||
|
|
||||||
const handleRemoveFolder = useCallback((folderId: string) => {
|
const handleRemoveFolder = useCallback((folderId: string) => {
|
||||||
Alert.alert(
|
setRemoveFolderConfirmId(folderId);
|
||||||
'Supprimer le dossier',
|
}, []);
|
||||||
'Le dossier sera retiré de la liste. Les fichiers resteront sur votre appareil.',
|
|
||||||
[
|
const handleRemoveFolderConfirm = useCallback(() => {
|
||||||
{ text: 'Annuler', style: 'cancel' },
|
if (removeFolderConfirmId) {
|
||||||
{
|
safDirectory.removeFolder(removeFolderConfirmId);
|
||||||
text: 'Supprimer',
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: () => {
|
|
||||||
safDirectory.removeFolder(folderId);
|
|
||||||
refreshFolders();
|
refreshFolders();
|
||||||
},
|
}
|
||||||
},
|
setRemoveFolderConfirmId(null);
|
||||||
]
|
}, [removeFolderConfirmId, refreshFolders]);
|
||||||
);
|
|
||||||
}, [refreshFolders]);
|
|
||||||
|
|
||||||
const handleAddFolder = useCallback(async () => {
|
const handleAddFolder = useCallback(async () => {
|
||||||
setSettingsModalVisible(false);
|
setSettingsModalVisible(false);
|
||||||
@@ -650,6 +646,25 @@ export function HomeScreen() {
|
|||||||
globalSyncCellular={globalSyncCellular}
|
globalSyncCellular={globalSyncCellular}
|
||||||
onSetGlobalSyncCellular={handleSetGlobalSyncCellular}
|
onSetGlobalSyncCellular={handleSetGlobalSyncCellular}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
visible={confirmDeleteState !== null}
|
||||||
|
title="Supprimer"
|
||||||
|
message={confirmDeleteState?.message}
|
||||||
|
options={confirmDeleteState?.options ?? []}
|
||||||
|
onClose={() => setConfirmDeleteState(null)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
visible={removeFolderConfirmId !== null}
|
||||||
|
title="Supprimer le dossier"
|
||||||
|
message="Le dossier sera retiré de la liste. Les fichiers resteront sur votre appareil."
|
||||||
|
options={[
|
||||||
|
{ label: 'Annuler' },
|
||||||
|
{ label: 'Supprimer', destructive: true, onPress: handleRemoveFolderConfirm },
|
||||||
|
]}
|
||||||
|
onClose={() => setRemoveFolderConfirmId(null)}
|
||||||
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity, Modal } from 'react-native';
|
||||||
|
|
||||||
|
export interface ConfirmOption {
|
||||||
|
label: string;
|
||||||
|
onPress?: () => void;
|
||||||
|
destructive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfirmModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
title: string;
|
||||||
|
message?: string;
|
||||||
|
options: ConfirmOption[];
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmModal({ visible, title, message, options, onClose }: ConfirmModalProps) {
|
||||||
|
const stacked = options.length > 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
|
||||||
|
<TouchableOpacity style={styles.overlay} activeOpacity={1} onPress={onClose}>
|
||||||
|
<TouchableOpacity activeOpacity={1} style={styles.container} onPress={() => {}}>
|
||||||
|
<View style={styles.handle} />
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
{message ? <Text style={styles.message}>{message}</Text> : null}
|
||||||
|
<View style={[styles.optionsContainer, stacked && styles.optionsStacked]}>
|
||||||
|
{options.map((opt, i) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={i}
|
||||||
|
style={[
|
||||||
|
stacked ? styles.stackedBtn : styles.sideBtn,
|
||||||
|
opt.destructive && (stacked ? styles.stackedDestructive : styles.sideDestructive),
|
||||||
|
!opt.destructive && !stacked && styles.sideBtnSecondary,
|
||||||
|
!opt.destructive && stacked && styles.stackedSecondary,
|
||||||
|
i < options.length - 1 && stacked && styles.stackedBtnBorder,
|
||||||
|
]}
|
||||||
|
onPress={() => {
|
||||||
|
onClose();
|
||||||
|
opt.onPress?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
stacked ? styles.stackedBtnText : styles.sideBtnText,
|
||||||
|
opt.destructive && (stacked ? styles.stackedDestructiveText : styles.sideDestructiveText),
|
||||||
|
!opt.destructive && !stacked && styles.sideBtnSecondaryText,
|
||||||
|
!opt.destructive && stacked && styles.stackedSecondaryText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 12,
|
||||||
|
paddingBottom: 20,
|
||||||
|
width: '85%',
|
||||||
|
},
|
||||||
|
handle: {
|
||||||
|
width: 36,
|
||||||
|
height: 4,
|
||||||
|
borderRadius: 2,
|
||||||
|
backgroundColor: '#ddd',
|
||||||
|
alignSelf: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#333',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#666',
|
||||||
|
lineHeight: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
optionsContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
optionsStacked: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 0,
|
||||||
|
},
|
||||||
|
sideBtn: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderRadius: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
sideDestructive: {
|
||||||
|
backgroundColor: '#E53935',
|
||||||
|
},
|
||||||
|
sideBtnSecondary: {
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
},
|
||||||
|
sideBtnText: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
sideDestructiveText: {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
sideBtnSecondaryText: {
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
stackedBtn: {
|
||||||
|
paddingVertical: 14,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
stackedBtnBorder: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#f0f0f0',
|
||||||
|
},
|
||||||
|
stackedDestructive: {},
|
||||||
|
stackedSecondary: {},
|
||||||
|
stackedBtnText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
stackedDestructiveText: {
|
||||||
|
color: '#E53935',
|
||||||
|
},
|
||||||
|
stackedSecondaryText: {
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -6,10 +6,10 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
Modal,
|
Modal,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Alert,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import { StoredFolder, SyncMode, SyncGlobalMode, type FolderSource } from '../services/safDirectory';
|
import { StoredFolder, SyncMode, SyncGlobalMode, type FolderSource } from '../services/safDirectory';
|
||||||
|
import { ConfirmModal } from './ConfirmModal';
|
||||||
|
|
||||||
type SettingsView = 'menu' | 'folders' | 'sync';
|
type SettingsView = 'menu' | 'folders' | 'sync';
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ export function SettingsModal({
|
|||||||
onSetGlobalSyncCellular,
|
onSetGlobalSyncCellular,
|
||||||
}: SettingsModalProps) {
|
}: SettingsModalProps) {
|
||||||
const [view, setView] = useState<SettingsView>('menu');
|
const [view, setView] = useState<SettingsView>('menu');
|
||||||
|
const [confirmRemoveFolder, setConfirmRemoveFolder] = useState<StoredFolder | null>(null);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
setView('menu');
|
setView('menu');
|
||||||
@@ -65,22 +66,18 @@ export function SettingsModal({
|
|||||||
|
|
||||||
const handleRemoveFolder = useCallback(
|
const handleRemoveFolder = useCallback(
|
||||||
(folder: StoredFolder) => {
|
(folder: StoredFolder) => {
|
||||||
Alert.alert(
|
setConfirmRemoveFolder(folder);
|
||||||
'Supprimer le dossier',
|
|
||||||
'Le dossier sera retiré de la liste. Les fichiers resteront sur votre appareil.',
|
|
||||||
[
|
|
||||||
{ text: 'Annuler', style: 'cancel' },
|
|
||||||
{
|
|
||||||
text: 'Supprimer',
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: () => onRemoveFolder(folder.id),
|
|
||||||
},
|
},
|
||||||
]
|
[]
|
||||||
);
|
|
||||||
},
|
|
||||||
[onRemoveFolder]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleRemoveFolderConfirm = useCallback(() => {
|
||||||
|
if (confirmRemoveFolder) {
|
||||||
|
onRemoveFolder(confirmRemoveFolder.id);
|
||||||
|
}
|
||||||
|
setConfirmRemoveFolder(null);
|
||||||
|
}, [confirmRemoveFolder, onRemoveFolder]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@@ -128,6 +125,17 @@ export function SettingsModal({
|
|||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
visible={confirmRemoveFolder !== null}
|
||||||
|
title="Supprimer le dossier"
|
||||||
|
message="Le dossier sera retiré de la liste. Les fichiers resteront sur votre appareil."
|
||||||
|
options={[
|
||||||
|
{ label: 'Annuler' },
|
||||||
|
{ label: 'Supprimer', destructive: true, onPress: handleRemoveFolderConfirm },
|
||||||
|
]}
|
||||||
|
onClose={() => setConfirmRemoveFolder(null)}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user