sync files
This commit is contained in:
+17
-1
@@ -14,7 +14,7 @@ import { SettingsModal } from '../components/SettingsModal';
|
|||||||
import { SyncStatusIcon } from '../components/SyncStatusIcon';
|
import { SyncStatusIcon } from '../components/SyncStatusIcon';
|
||||||
import { useSyncQueue } from '../hooks/useSyncQueue';
|
import { useSyncQueue } from '../hooks/useSyncQueue';
|
||||||
import { useAutoSync } from '../hooks/useAutoSync';
|
import { useAutoSync } from '../hooks/useAutoSync';
|
||||||
import { safDirectory, StoredFolder, SyncMode } from '../services/safDirectory';
|
import { safDirectory, StoredFolder, SyncMode, SyncGlobalMode } from '../services/safDirectory';
|
||||||
|
|
||||||
const NUM_COLUMNS = 3;
|
const NUM_COLUMNS = 3;
|
||||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||||
@@ -155,6 +155,8 @@ export function HomeScreen() {
|
|||||||
const { data: foldersData } = useFolders();
|
const { data: foldersData } = useFolders();
|
||||||
const [moveModalVisible, setMoveModalVisible] = useState(false);
|
const [moveModalVisible, setMoveModalVisible] = useState(false);
|
||||||
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
|
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
|
||||||
|
const [globalSyncMode, setGlobalSyncMode] = useState<SyncGlobalMode>(() => safDirectory.getGlobalSyncMode());
|
||||||
|
const [globalSyncCellular, setGlobalSyncCellular] = useState(() => safDirectory.getGlobalSyncCellular());
|
||||||
const { pendingCount, isSyncing } = useSyncQueue();
|
const { pendingCount, isSyncing } = useSyncQueue();
|
||||||
useAutoSync();
|
useAutoSync();
|
||||||
|
|
||||||
@@ -333,6 +335,16 @@ export function HomeScreen() {
|
|||||||
refreshFolders();
|
refreshFolders();
|
||||||
}, [refreshFolders]);
|
}, [refreshFolders]);
|
||||||
|
|
||||||
|
const handleSetGlobalSyncMode = useCallback((mode: SyncGlobalMode) => {
|
||||||
|
safDirectory.setGlobalSyncMode(mode);
|
||||||
|
setGlobalSyncMode(mode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSetGlobalSyncCellular = useCallback((enabled: boolean) => {
|
||||||
|
safDirectory.setGlobalSyncCellular(enabled);
|
||||||
|
setGlobalSyncCellular(enabled);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleItemPress = useCallback((file: UnifiedFileItem) => {
|
const handleItemPress = useCallback((file: UnifiedFileItem) => {
|
||||||
if (selectionMode) {
|
if (selectionMode) {
|
||||||
toggleSelection(file.id);
|
toggleSelection(file.id);
|
||||||
@@ -617,6 +629,10 @@ export function HomeScreen() {
|
|||||||
onAddFolder={handleAddFolder}
|
onAddFolder={handleAddFolder}
|
||||||
onUpdateSyncMode={handleUpdateSyncMode}
|
onUpdateSyncMode={handleUpdateSyncMode}
|
||||||
onUpdateSyncCellular={handleUpdateSyncCellular}
|
onUpdateSyncCellular={handleUpdateSyncCellular}
|
||||||
|
globalSyncMode={globalSyncMode}
|
||||||
|
onSetGlobalSyncMode={handleSetGlobalSyncMode}
|
||||||
|
globalSyncCellular={globalSyncCellular}
|
||||||
|
onSetGlobalSyncCellular={handleSetGlobalSyncCellular}
|
||||||
/>
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Alert,
|
Alert,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { MaterialIcons } from '@expo/vector-icons';
|
import { MaterialIcons } from '@expo/vector-icons';
|
||||||
import { StoredFolder, SyncMode } from '../services/safDirectory';
|
import { StoredFolder, SyncMode, SyncGlobalMode } from '../services/safDirectory';
|
||||||
|
|
||||||
type SettingsView = 'menu' | 'folders' | 'sync';
|
type SettingsView = 'menu' | 'folders' | 'sync';
|
||||||
|
|
||||||
@@ -22,6 +22,10 @@ interface SettingsModalProps {
|
|||||||
onAddFolder: () => void;
|
onAddFolder: () => void;
|
||||||
onUpdateSyncMode: (folderId: string, mode: SyncMode) => void;
|
onUpdateSyncMode: (folderId: string, mode: SyncMode) => void;
|
||||||
onUpdateSyncCellular: (folderId: string, enabled: boolean) => void;
|
onUpdateSyncCellular: (folderId: string, enabled: boolean) => void;
|
||||||
|
globalSyncMode: SyncGlobalMode;
|
||||||
|
onSetGlobalSyncMode: (mode: SyncGlobalMode) => void;
|
||||||
|
globalSyncCellular: boolean;
|
||||||
|
onSetGlobalSyncCellular: (enabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SYNC_MODES: { value: SyncMode; label: string; icon: string; color: string }[] = [
|
const SYNC_MODES: { value: SyncMode; label: string; icon: string; color: string }[] = [
|
||||||
@@ -30,6 +34,12 @@ const SYNC_MODES: { value: SyncMode; label: string; icon: string; color: string
|
|||||||
{ value: 'auto', label: 'Auto', icon: 'sync-problem', color: '#43A047' },
|
{ value: 'auto', label: 'Auto', icon: 'sync-problem', color: '#43A047' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const GLOBAL_MODES: { value: SyncGlobalMode; label: string; description: string; icon: string; color: string }[] = [
|
||||||
|
{ value: 'off', label: 'Désactivé', description: 'Aucun upload automatique', icon: 'sync-disabled', color: '#999' },
|
||||||
|
{ value: 'auto', label: 'Automatique', description: 'Tout synchroniser automatiquement', icon: 'sync-problem', color: '#43A047' },
|
||||||
|
{ value: 'manual', label: 'Par dossier', description: 'Choisir dossier par dossier', icon: 'tune', color: '#1976D2' },
|
||||||
|
];
|
||||||
|
|
||||||
export function SettingsModal({
|
export function SettingsModal({
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -39,6 +49,10 @@ export function SettingsModal({
|
|||||||
onAddFolder,
|
onAddFolder,
|
||||||
onUpdateSyncMode,
|
onUpdateSyncMode,
|
||||||
onUpdateSyncCellular,
|
onUpdateSyncCellular,
|
||||||
|
globalSyncMode,
|
||||||
|
onSetGlobalSyncMode,
|
||||||
|
globalSyncCellular,
|
||||||
|
onSetGlobalSyncCellular,
|
||||||
}: SettingsModalProps) {
|
}: SettingsModalProps) {
|
||||||
const [view, setView] = useState<SettingsView>('menu');
|
const [view, setView] = useState<SettingsView>('menu');
|
||||||
|
|
||||||
@@ -103,6 +117,10 @@ export function SettingsModal({
|
|||||||
onBack={() => setView('menu')}
|
onBack={() => setView('menu')}
|
||||||
onUpdateSyncMode={onUpdateSyncMode}
|
onUpdateSyncMode={onUpdateSyncMode}
|
||||||
onUpdateSyncCellular={onUpdateSyncCellular}
|
onUpdateSyncCellular={onUpdateSyncCellular}
|
||||||
|
globalSyncMode={globalSyncMode}
|
||||||
|
onSetGlobalSyncMode={onSetGlobalSyncMode}
|
||||||
|
globalSyncCellular={globalSyncCellular}
|
||||||
|
onSetGlobalSyncCellular={onSetGlobalSyncCellular}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -209,11 +227,19 @@ function SyncView({
|
|||||||
onBack,
|
onBack,
|
||||||
onUpdateSyncMode,
|
onUpdateSyncMode,
|
||||||
onUpdateSyncCellular,
|
onUpdateSyncCellular,
|
||||||
|
globalSyncMode,
|
||||||
|
onSetGlobalSyncMode,
|
||||||
|
globalSyncCellular,
|
||||||
|
onSetGlobalSyncCellular,
|
||||||
}: {
|
}: {
|
||||||
folders: StoredFolder[];
|
folders: StoredFolder[];
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onUpdateSyncMode: (id: string, mode: SyncMode) => void;
|
onUpdateSyncMode: (id: string, mode: SyncMode) => void;
|
||||||
onUpdateSyncCellular: (id: string, enabled: boolean) => void;
|
onUpdateSyncCellular: (id: string, enabled: boolean) => void;
|
||||||
|
globalSyncMode: SyncGlobalMode;
|
||||||
|
onSetGlobalSyncMode: (mode: SyncGlobalMode) => void;
|
||||||
|
globalSyncCellular: boolean;
|
||||||
|
onSetGlobalSyncCellular: (enabled: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.viewContainer} showsVerticalScrollIndicator={false}>
|
<ScrollView style={styles.viewContainer} showsVerticalScrollIndicator={false}>
|
||||||
@@ -225,74 +251,143 @@ function SyncView({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.syncInfo}>
|
<Text style={styles.syncInfo}>
|
||||||
Configurez comment les fichiers de chaque dossier sont envoyés au serveur.
|
Choisissez comment vos fichiers sont envoyés au serveur.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{folders.length === 0 ? (
|
<View style={styles.globalModeCard}>
|
||||||
<Text style={styles.emptyText}>Aucun dossier configuré</Text>
|
<Text style={styles.sectionLabel}>Mode de synchronisation</Text>
|
||||||
) : (
|
{GLOBAL_MODES.map((mode) => (
|
||||||
folders.map((folder) => (
|
<TouchableOpacity
|
||||||
<View key={folder.id} style={styles.syncFolderCard}>
|
key={mode.value}
|
||||||
<View style={styles.syncFolderHeader}>
|
style={[
|
||||||
<MaterialIcons name="folder" size={18} color="#F57C00" />
|
styles.globalModeRow,
|
||||||
<Text style={styles.syncFolderName} numberOfLines={1}>
|
globalSyncMode === mode.value && styles.globalModeRowActive,
|
||||||
{folder.name}
|
]}
|
||||||
|
onPress={() => onSetGlobalSyncMode(mode.value)}
|
||||||
|
>
|
||||||
|
<MaterialIcons
|
||||||
|
name={globalSyncMode === mode.value ? 'radio-button-checked' : 'radio-button-unchecked'}
|
||||||
|
size={20}
|
||||||
|
color={globalSyncMode === mode.value ? mode.color : '#999'}
|
||||||
|
/>
|
||||||
|
<View style={styles.globalModeTextContainer}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.globalModeLabel,
|
||||||
|
globalSyncMode === mode.value && { color: mode.color },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{mode.label}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text style={styles.globalModeDescription}>{mode.description}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<MaterialIcons
|
||||||
|
name={mode.icon as any}
|
||||||
|
size={20}
|
||||||
|
color={globalSyncMode === mode.value ? mode.color : '#ccc'}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.radioGroup}>
|
{globalSyncMode === 'auto' && (
|
||||||
{SYNC_MODES.map((mode) => (
|
<View style={styles.cellularCard}>
|
||||||
<TouchableOpacity
|
<View style={styles.cellularRow}>
|
||||||
key={mode.value}
|
<MaterialIcons name="cell-tower" size={20} color="#666" />
|
||||||
style={[
|
<Text style={styles.cellularLabel}>Autoriser le réseau cellulaire</Text>
|
||||||
styles.radioBtn,
|
<TouchableOpacity
|
||||||
folder.syncMode === mode.value && styles.radioBtnActive,
|
style={[
|
||||||
]}
|
styles.toggleBtn,
|
||||||
onPress={() => onUpdateSyncMode(folder.id, mode.value)}
|
globalSyncCellular && styles.toggleBtnActive,
|
||||||
>
|
]}
|
||||||
<MaterialIcons
|
onPress={() => onSetGlobalSyncCellular(!globalSyncCellular)}
|
||||||
name={
|
>
|
||||||
folder.syncMode === mode.value
|
<View
|
||||||
? 'radio-button-checked'
|
style={[
|
||||||
: 'radio-button-unchecked'
|
styles.toggleDot,
|
||||||
}
|
globalSyncCellular && styles.toggleDotActive,
|
||||||
size={18}
|
]}
|
||||||
color={folder.syncMode === mode.value ? mode.color : '#999'}
|
/>
|
||||||
/>
|
</TouchableOpacity>
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.radioLabel,
|
|
||||||
folder.syncMode === mode.value && { color: mode.color },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{mode.label}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{folder.syncMode !== 'none' && (
|
|
||||||
<View style={styles.cellularRow}>
|
|
||||||
<MaterialIcons name="cell-tower" size={18} color="#666" />
|
|
||||||
<Text style={styles.cellularLabel}>Réseau cellulaire</Text>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[
|
|
||||||
styles.toggleBtn,
|
|
||||||
folder.syncCellular && styles.toggleBtnActive,
|
|
||||||
]}
|
|
||||||
onPress={() => onUpdateSyncCellular(folder.id, !folder.syncCellular)}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.toggleDot,
|
|
||||||
folder.syncCellular && styles.toggleDotActive,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
))
|
<Text style={styles.cellularHint}>
|
||||||
|
{globalSyncCellular
|
||||||
|
? 'Upload via WiFi et données mobiles'
|
||||||
|
: 'Upload uniquement en WiFi'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{globalSyncMode === 'manual' && (
|
||||||
|
<View style={styles.perFolderSection}>
|
||||||
|
<Text style={styles.sectionLabel}>Configuration par dossier</Text>
|
||||||
|
{folders.length === 0 ? (
|
||||||
|
<Text style={styles.emptyText}>Aucun dossier configuré</Text>
|
||||||
|
) : (
|
||||||
|
folders.map((folder) => (
|
||||||
|
<View key={folder.id} style={styles.syncFolderCard}>
|
||||||
|
<View style={styles.syncFolderHeader}>
|
||||||
|
<MaterialIcons name="folder" size={18} color="#F57C00" />
|
||||||
|
<Text style={styles.syncFolderName} numberOfLines={1}>
|
||||||
|
{folder.name}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.radioGroup}>
|
||||||
|
{SYNC_MODES.map((mode) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={mode.value}
|
||||||
|
style={[
|
||||||
|
styles.radioBtn,
|
||||||
|
folder.syncMode === mode.value && styles.radioBtnActive,
|
||||||
|
]}
|
||||||
|
onPress={() => onUpdateSyncMode(folder.id, mode.value)}
|
||||||
|
>
|
||||||
|
<MaterialIcons
|
||||||
|
name={
|
||||||
|
folder.syncMode === mode.value
|
||||||
|
? 'radio-button-checked'
|
||||||
|
: 'radio-button-unchecked'
|
||||||
|
}
|
||||||
|
size={18}
|
||||||
|
color={folder.syncMode === mode.value ? mode.color : '#999'}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.radioLabel,
|
||||||
|
folder.syncMode === mode.value && { color: mode.color },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{mode.label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{folder.syncMode !== 'none' && (
|
||||||
|
<View style={styles.cellularRow}>
|
||||||
|
<MaterialIcons name="cell-tower" size={18} color="#666" />
|
||||||
|
<Text style={styles.cellularLabel}>Réseau cellulaire</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.toggleBtn,
|
||||||
|
folder.syncCellular && styles.toggleBtnActive,
|
||||||
|
]}
|
||||||
|
onPress={() => onUpdateSyncCellular(folder.id, !folder.syncCellular)}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.toggleDot,
|
||||||
|
folder.syncCellular && styles.toggleDotActive,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
@@ -420,6 +515,72 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
lineHeight: 18,
|
lineHeight: 18,
|
||||||
},
|
},
|
||||||
|
globalModeCard: {
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
sectionLabel: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#666',
|
||||||
|
marginBottom: 10,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
globalModeRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
gap: 10,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
globalModeRowActive: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#e0e0e0',
|
||||||
|
},
|
||||||
|
globalModeTextContainer: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
globalModeLabel: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
globalModeDescription: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#999',
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
cellularCard: {
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
cellularRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
cellularLabel: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
cellularHint: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#999',
|
||||||
|
marginTop: 6,
|
||||||
|
marginLeft: 28,
|
||||||
|
},
|
||||||
|
perFolderSection: {
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
syncFolderCard: {
|
syncFolderCard: {
|
||||||
backgroundColor: '#fafafa',
|
backgroundColor: '#fafafa',
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
@@ -464,19 +625,6 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#999',
|
color: '#999',
|
||||||
},
|
},
|
||||||
cellularRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
paddingTop: 6,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopColor: '#e8e8e8',
|
|
||||||
},
|
|
||||||
cellularLabel: {
|
|
||||||
flex: 1,
|
|
||||||
fontSize: 13,
|
|
||||||
color: '#666',
|
|
||||||
},
|
|
||||||
toggleBtn: {
|
toggleBtn: {
|
||||||
width: 44,
|
width: 44,
|
||||||
height: 24,
|
height: 24,
|
||||||
|
|||||||
+30
-23
@@ -9,13 +9,6 @@ import { API_BASE_URL, ENDPOINTS } from '../constants/api';
|
|||||||
import { ApiError } from '../types';
|
import { ApiError } from '../types';
|
||||||
import { setIsSyncing } from './useSyncQueue';
|
import { setIsSyncing } from './useSyncQueue';
|
||||||
|
|
||||||
function canSyncFolder(folder: StoredFolder, netInfo: NetInfoState): boolean {
|
|
||||||
if (folder.syncMode !== 'auto') return false;
|
|
||||||
if (!netInfo.isConnected) return false;
|
|
||||||
if (!folder.syncCellular && netInfo.type !== 'wifi') return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UploadResult {
|
interface UploadResult {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -53,38 +46,52 @@ async function uploadFile(file: { uri: string; type: string; name: string }): Pr
|
|||||||
return item as UploadResult;
|
return item as UploadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canSyncBasedOnNetwork(netInfo: NetInfoState, cellularAllowed: boolean): boolean {
|
||||||
|
if (!netInfo.isConnected) return false;
|
||||||
|
if (!cellularAllowed && netInfo.type !== 'wifi') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function useAutoSync() {
|
export function useAutoSync() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const isRunning = useRef(false);
|
const isRunning = useRef(false);
|
||||||
|
|
||||||
const checkAndSync = useCallback(async () => {
|
const checkAndSync = useCallback(async () => {
|
||||||
if (isRunning.current) return;
|
if (isRunning.current) return;
|
||||||
|
|
||||||
|
const globalMode = safDirectory.getGlobalSyncMode();
|
||||||
|
if (globalMode === 'off') return;
|
||||||
|
|
||||||
isRunning.current = true;
|
isRunning.current = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const folders = safDirectory.getAll();
|
const globalCellular = safDirectory.getGlobalSyncCellular();
|
||||||
const autoFolders = folders.filter((f) => f.syncMode === 'auto');
|
|
||||||
if (autoFolders.length === 0) return;
|
|
||||||
|
|
||||||
const netInfo = await NetInfo.fetch();
|
const netInfo = await NetInfo.fetch();
|
||||||
const eligible = autoFolders.filter((f) => canSyncFolder(f, netInfo));
|
|
||||||
if (eligible.length === 0) return;
|
|
||||||
|
|
||||||
const eligibleIds = new Set(eligible.map((f) => f.id));
|
if (!canSyncBasedOnNetwork(netInfo, globalCellular)) return;
|
||||||
|
|
||||||
const registry = localFileRegistry.getAll();
|
const registry = localFileRegistry.getAll();
|
||||||
|
let pendingFiles = registry.filter(
|
||||||
const pendingFiles = registry.filter(
|
(entry) => !entry.backendFileId && entry.syncStatus === 'local' && entry.localUri
|
||||||
(entry) =>
|
|
||||||
!entry.backendFileId &&
|
|
||||||
entry.syncStatus === 'local' &&
|
|
||||||
entry.folderId &&
|
|
||||||
eligibleIds.has(entry.folderId) &&
|
|
||||||
entry.localUri
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (globalMode === 'auto') {
|
||||||
|
// Mode auto global : tous les fichiers locaux sans backendFileId
|
||||||
|
// (pas de filtre par dossier)
|
||||||
|
} else {
|
||||||
|
// Mode manuel : uniquement les fichiers des dossiers en mode auto
|
||||||
|
const allFolders = safDirectory.getAll();
|
||||||
|
const autoFolderIds = new Set(
|
||||||
|
allFolders.filter((f) => f.syncMode === 'auto').map((f) => f.id)
|
||||||
|
);
|
||||||
|
pendingFiles = pendingFiles.filter(
|
||||||
|
(entry) => entry.folderId && autoFolderIds.has(entry.folderId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingFiles.length === 0) return;
|
if (pendingFiles.length === 0) return;
|
||||||
|
|
||||||
console.log(`[useAutoSync] upload de ${pendingFiles.length} fichier(s)`);
|
console.log(`[useAutoSync] mode=${globalMode}, upload de ${pendingFiles.length} fichier(s)`);
|
||||||
setIsSyncing(true);
|
setIsSyncing(true);
|
||||||
|
|
||||||
for (const entry of pendingFiles) {
|
for (const entry of pendingFiles) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect, useRef } from 'react';
|
||||||
import { useDeviceFiles } from './useDeviceFiles';
|
import { useDeviceFiles } from './useDeviceFiles';
|
||||||
import { localFileRegistry } from '../services/localFileRegistry';
|
import { localFileRegistry } from '../services/localFileRegistry';
|
||||||
import { LocalFileEntry } from '../types';
|
import { LocalFileEntry } from '../types';
|
||||||
@@ -8,6 +8,23 @@ export function useLocalFiles() {
|
|||||||
|
|
||||||
const registryEntries = useMemo(() => localFileRegistry.getAll(), []);
|
const registryEntries = useMemo(() => localFileRegistry.getAll(), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
for (const df of deviceFiles) {
|
||||||
|
if (localFileRegistry.get(df.id)) continue;
|
||||||
|
const entry: LocalFileEntry = {
|
||||||
|
id: df.id,
|
||||||
|
localUri: df.uri,
|
||||||
|
name: df.name,
|
||||||
|
mimeType: df.mimeType,
|
||||||
|
size: df.size,
|
||||||
|
syncStatus: 'local',
|
||||||
|
createdAt: df.createdAt,
|
||||||
|
folderId: df.folderId,
|
||||||
|
};
|
||||||
|
localFileRegistry.register(entry);
|
||||||
|
}
|
||||||
|
}, [deviceFiles]);
|
||||||
|
|
||||||
const localFiles = useMemo(() => {
|
const localFiles = useMemo(() => {
|
||||||
const merged = new Map<string, LocalFileEntry>();
|
const merged = new Map<string, LocalFileEntry>();
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,9 @@ export function useSyncQueue() {
|
|||||||
const { folders } = useDeviceFiles();
|
const { folders } = useDeviceFiles();
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
const allFolders = safDirectory.getAll();
|
const globalMode = safDirectory.getGlobalSyncMode();
|
||||||
const syncFolderIds = new Set(
|
|
||||||
allFolders.filter((f) => f.syncMode !== 'none').map((f) => f.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (syncFolderIds.size === 0) {
|
if (globalMode === 'off') {
|
||||||
setPendingCount(0);
|
setPendingCount(0);
|
||||||
setIsSyncingState(getIsSyncing());
|
setIsSyncingState(getIsSyncing());
|
||||||
return;
|
return;
|
||||||
@@ -33,11 +30,21 @@ export function useSyncQueue() {
|
|||||||
|
|
||||||
const registry = localFileRegistry.getAll();
|
const registry = localFileRegistry.getAll();
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
for (const entry of registry) {
|
for (const entry of registry) {
|
||||||
if (entry.backendFileId) continue;
|
if (entry.backendFileId) continue;
|
||||||
if (entry.syncStatus !== 'local') continue;
|
if (entry.syncStatus !== 'local') continue;
|
||||||
if (entry.folderId && !syncFolderIds.has(entry.folderId)) continue;
|
|
||||||
count++;
|
if (globalMode === 'auto') {
|
||||||
|
count++;
|
||||||
|
} else {
|
||||||
|
// mode manuel : uniquement les fichiers des dossiers en mode auto
|
||||||
|
if (!entry.folderId) continue;
|
||||||
|
const folder = safDirectory.getAll().find((f) => f.id === entry.folderId);
|
||||||
|
if (folder && folder.syncMode === 'auto') {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPendingCount(count);
|
setPendingCount(count);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createMMKV } from 'react-native-mmkv';
|
import { createMMKV } from 'react-native-mmkv';
|
||||||
|
|
||||||
export type SyncMode = 'none' | 'manual' | 'auto';
|
export type SyncMode = 'none' | 'manual' | 'auto';
|
||||||
|
export type SyncGlobalMode = 'off' | 'auto' | 'manual';
|
||||||
|
|
||||||
export type StoredFolder = {
|
export type StoredFolder = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -14,6 +15,8 @@ export type StoredFolder = {
|
|||||||
const storage = createMMKV({ id: 'vaultdrop-saf' });
|
const storage = createMMKV({ id: 'vaultdrop-saf' });
|
||||||
|
|
||||||
const FOLDERS_KEY = 'saf_folders';
|
const FOLDERS_KEY = 'saf_folders';
|
||||||
|
const SYNC_GLOBAL_MODE_KEY = 'sync_global_mode';
|
||||||
|
const SYNC_GLOBAL_CELLULAR_KEY = 'sync_global_cellular';
|
||||||
|
|
||||||
function generateId(): string {
|
function generateId(): string {
|
||||||
return `folder_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
return `folder_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
@@ -88,6 +91,24 @@ export const safDirectory = {
|
|||||||
saveAll(folders);
|
saveAll(folders);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getGlobalSyncMode(): SyncGlobalMode {
|
||||||
|
const raw = storage.getString(SYNC_GLOBAL_MODE_KEY);
|
||||||
|
if (raw === 'auto' || raw === 'manual') return raw;
|
||||||
|
return 'off';
|
||||||
|
},
|
||||||
|
|
||||||
|
setGlobalSyncMode(mode: SyncGlobalMode) {
|
||||||
|
storage.set(SYNC_GLOBAL_MODE_KEY, mode);
|
||||||
|
},
|
||||||
|
|
||||||
|
getGlobalSyncCellular(): boolean {
|
||||||
|
return storage.getString(SYNC_GLOBAL_CELLULAR_KEY) === 'true';
|
||||||
|
},
|
||||||
|
|
||||||
|
setGlobalSyncCellular(enabled: boolean) {
|
||||||
|
storage.set(SYNC_GLOBAL_CELLULAR_KEY, enabled ? 'true' : 'false');
|
||||||
|
},
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
storage.remove(FOLDERS_KEY);
|
storage.remove(FOLDERS_KEY);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user