diff --git a/mobile/app.json b/mobile/app.json index d57b684..defb730 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -9,8 +9,7 @@ "ios": { "supportsTablet": true, "infoPlist": { - "NSCameraUsageDescription": "VaultDrop a besoin d'accéder à votre caméra pour scanner des documents.", - "NSPhotoLibraryUsageDescription": "VaultDrop a besoin d'accéder à votre galerie pour sélectionner des photos." + "NSCameraUsageDescription": "VaultDrop a besoin d'accéder à votre caméra pour scanner des documents." }, "bundleIdentifier": "com.anonymous.webui" }, @@ -19,9 +18,6 @@ "CAMERA", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE", - "READ_MEDIA_IMAGES", - "READ_MEDIA_VIDEO", - "READ_MEDIA_AUDIO", "RECEIVE_BOOT_COMPLETED" ], "adaptiveIcon": { @@ -38,13 +34,6 @@ }, "plugins": [ "expo-secure-store", - [ - "expo-media-library", - { - "photosPermission": "VaultDrop a besoin d'accéder à vos photos pour les afficher et les organiser.", - "videosPermission": "VaultDrop a besoin d'accéder à vos vidéos pour les afficher et les organiser." - } - ], "expo-image", "expo-sqlite", "expo-background-task", diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index 8b18027..0b8fc66 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -79,7 +79,7 @@ export function HomeScreen() { const insets = useSafeAreaInsets(); const [page, setPage] = useState(1); const { data, isLoading, error, isFetching, refetch } = useFiles(null, page, PAGE_SIZE); - const { hasPermission, requestPermission, pickAndScanRecursive, folders, refreshFolders, discovered } = useLocalFiles(); + const { pickAndScanRecursive, folders, refreshFolders, discovered } = useLocalFiles(); const freeLocalSpace = useFreeLocalSpace(); const queryClient = useQueryClient(); const [searchQuery, setSearchQuery] = useState(''); @@ -395,19 +395,7 @@ export function HomeScreen() { if (isLoading) { return ( - {!hasPermission ? ( - - - - Autorisez l'accès à vos photos pour les afficher ici - - - Autoriser - - - ) : ( - Chargement... - )} + Chargement... ); } @@ -426,17 +414,6 @@ export function HomeScreen() { behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.OS === 'ios' ? 96 : 0} > - {!hasPermission && ( - - - - Autorisez l'accès à vos photos pour les afficher ici - - - Autoriser - - - )} {files.length === 0 && !searchQuery && ( {folders.length > 0 ? ( @@ -657,19 +634,6 @@ const styles = StyleSheet.create({ flex: 1, backgroundColor: '#f5f5f5', }, - permissionBanner: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#E3F2FD', - paddingHorizontal: 16, - paddingVertical: 14, - gap: 10, - }, - permissionText: { - flex: 1, - fontSize: 14, - color: '#333', - }, permissionBtn: { backgroundColor: '#1976D2', borderRadius: 8, diff --git a/mobile/components/UploadModal.tsx b/mobile/components/UploadModal.tsx index cc3ca18..ffbfbe7 100644 --- a/mobile/components/UploadModal.tsx +++ b/mobile/components/UploadModal.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Modal, Alert } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; -import * as ImagePicker from 'expo-image-picker'; import * as DocumentPicker from 'expo-document-picker'; import { UploadFile } from '../services/uploadQueue'; import { useUploadQueue } from '../hooks/useUploadQueue'; @@ -14,35 +13,6 @@ interface UploadModalProps { export function UploadModal({ visible, onClose }: UploadModalProps) { const { enqueue } = useUploadQueue(); - const pickImages = async () => { - try { - const { status: perm } = await ImagePicker.requestMediaLibraryPermissionsAsync(); - if (perm !== 'granted') { - Alert.alert('Permission requise', "L'accès à la galerie est nécessaire pour sélectionner des photos."); - return; - } - - const result = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ['images'], - quality: 1, - allowsMultipleSelection: true, - }); - - if (result.canceled || result.assets.length === 0) return; - - const files = result.assets.map((asset) => ({ - uri: asset.uri, - type: asset.mimeType || 'image/jpeg', - name: asset.fileName || 'photo.jpg', - })); - - enqueue(files); - onClose(); - } catch { - Alert.alert('Erreur', "Impossible d'accéder à la galerie"); - } - }; - const pickDocuments = async () => { try { const result = await DocumentPicker.getDocumentAsync({ @@ -87,14 +57,6 @@ export function UploadModal({ visible, onClose }: UploadModalProps) { - - - Sélectionner des photos - - { } } -async function scanMediaAlbum(folder: StoredFolder): Promise { - try { - if (!folder.albumId) return []; - const result = await MediaLibrary.getAssetsAsync({ - album: folder.albumId, - first: 500, - sortBy: 'creationTime', - }); - return result.assets.map((asset) => ({ - id: `media_${folder.id}_${asset.id}`, - uri: asset.uri, - name: asset.filename, - mimeType: asset.mediaType === 'video' ? 'video/mp4' : 'image/jpeg', - size: 0, - createdAt: asset.creationTime - ? new Date(asset.creationTime).toISOString() - : new Date().toISOString(), - folderId: folder.id, - })); - } catch (err) { - return []; - } -} - export async function scanSubdirectories( baseUri: string, depth: number = 0, @@ -143,80 +118,15 @@ export async function scanSubdirectories( export function useDeviceFiles() { const [files, setFiles] = useState([]); const [isLoading, setIsLoading] = useState(false); - const [hasPermission, setHasPermission] = useState(false); const [folders, setFolders] = useState(() => safDirectory.getAll()); const [discovered, setDiscovered] = useState(() => safDirectory.getDiscovered()); const { newFiles, clearNewFiles } = useFileWatcher(); - const requestPermission = useCallback(async () => { - const req = await MediaLibrary.requestPermissionsAsync(); - if (req.granted) { - setHasPermission(true); - return true; - } - const check = await MediaLibrary.getPermissionsAsync(); - const granted = check.granted; - setHasPermission(granted); - return granted; - }, []); - - const loadAssets = useCallback(async () => { - setIsLoading(true); - try { - const result = await MediaLibrary.getAssetsAsync({ - first: 500, - mediaType: ['photo', 'video'], - sortBy: 'creationTime', - }); - - const deviceFiles: DeviceFile[] = result.assets.map((asset) => ({ - id: asset.id, - uri: asset.uri, - name: asset.filename, - mimeType: asset.mediaType === 'video' ? 'video/mp4' : 'image/jpeg', - size: 0, - createdAt: asset.creationTime - ? new Date(asset.creationTime).toISOString() - : new Date().toISOString(), - })); - - setFiles(deviceFiles); - } catch (err) { - setFiles([]); - } finally { - setIsLoading(false); - } - }, []); - - const discoverMediaAlbums = useCallback(async (): Promise => { - try { - const albums = await MediaLibrary.getAlbumsAsync(); - let added = 0; - for (const album of albums) { - if (album.title && album.assetCount > 0) { - safDirectory.addMediaFolder(album.id, album.title, ''); - added++; - } - } - if (added > 0) { - setFolders(safDirectory.getAll()); - } - return added; - } catch (err) { - return 0; - } - }, []); - const scanVisibleFolders = useCallback(async () => { - const visibleFolders = safDirectory.getVisibleFolders(); + const visibleFolders = safDirectory.getVisibleFolders().filter((f) => f.source !== 'media-library'); if (visibleFolders.length === 0) return; - const results = await Promise.all( - visibleFolders.map((folder) => { - if (folder.source === 'media-library') return scanMediaAlbum(folder); - return scanSafFolder(folder); - }) - ); + const results = await Promise.all(visibleFolders.map((folder) => scanSafFolder(folder))); const safFiles = results.flat(); setFiles((prev) => { @@ -284,33 +194,19 @@ export function useDeviceFiles() { } }, [discovered, scanVisibleFolders]); - const runInitialDiscovery = useCallback(async () => { - if (safDirectory.getDiscovered()) return; - - const granted = hasPermission || await requestPermission(); - if (granted) { - const mediaCount = await discoverMediaAlbums(); - if (mediaCount > 0) { - await scanVisibleFolders(); - } - } - - safDirectory.setDiscovered(); - setDiscovered(true); - }, [hasPermission, requestPermission, discoverMediaAlbums, scanVisibleFolders]); - const refreshFolders = useCallback(() => { setFolders(safDirectory.getAll()); }, []); const rescan = useCallback(async () => { - const granted = await requestPermission(); - if (granted) { - await loadAssets(); + setIsLoading(true); + try { + await scanVisibleFolders(); + loadRegistryFiles(); + } finally { + setIsLoading(false); } - await scanVisibleFolders(); - loadRegistryFiles(); - }, [requestPermission, loadAssets, scanVisibleFolders, loadRegistryFiles]); + }, [scanVisibleFolders, loadRegistryFiles]); // Handle new files detected by native module useEffect(() => { @@ -333,22 +229,16 @@ export function useDeviceFiles() { // Initial load useEffect(() => { - requestPermission().then((granted) => { - if (granted) { - loadAssets(); - } - }); scanVisibleFolders(); loadRegistryFiles(); - runInitialDiscovery(); - }, []); + }, [scanVisibleFolders, loadRegistryFiles]); return { - files, isLoading, hasPermission, - requestPermission, rescan, + files, isLoading, + rescan, pickAndScanRecursive, folders, refreshFolders, - discovered, discoverMediaAlbums, + discovered, }; } diff --git a/mobile/hooks/useLocalFiles.ts b/mobile/hooks/useLocalFiles.ts index 67581b1..bc594fa 100644 --- a/mobile/hooks/useLocalFiles.ts +++ b/mobile/hooks/useLocalFiles.ts @@ -6,7 +6,7 @@ import { generateLocalThumbnail } from '../services/thumbnail'; import { UnifiedFileItem } from '../types'; export function useLocalFiles() { - const { files: deviceFiles, isLoading: deviceLoading, hasPermission, requestPermission, rescan, pickAndScanRecursive, folders, refreshFolders, discovered, discoverMediaAlbums } = useDeviceFiles(); + const { files: deviceFiles, isLoading: deviceLoading, rescan, pickAndScanRecursive, folders, refreshFolders, discovered } = useDeviceFiles(); const queryClient = useQueryClient(); const lastDeviceCount = useRef(0); const thumbnailQueue = useRef>(new Set()); @@ -101,13 +101,10 @@ export function useLocalFiles() { return { localFiles, isLoading: deviceLoading, - hasPermission, - requestPermission, rescan, pickAndScanRecursive, folders, refreshFolders, discovered, - discoverMediaAlbums, }; } diff --git a/mobile/package-lock.json b/mobile/package-lock.json index b5e21c9..2d84fc4 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -23,8 +23,6 @@ "expo-file-system": "~57.0.1", "expo-image": "~57.0.1", "expo-image-manipulator": "~57.0.14", - "expo-image-picker": "~57.0.6", - "expo-media-library": "~57.0.3", "expo-print": "~57.0.1", "expo-secure-store": "~57.0.1", "expo-sqlite": "~57.0.1", @@ -4398,28 +4396,6 @@ "expo": "*" } }, - "node_modules/expo-image-picker": { - "version": "57.0.6", - "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-57.0.6.tgz", - "integrity": "sha512-6Of7SzyFVC+WFuFxhD4+nRTQ9joqldPhbiVWRsumg4ybttKmY8GxrnXAUSIDfa9DSk9PhgIAy2C1c5y1+CnU3g==", - "license": "MIT", - "dependencies": { - "expo-image-loader": "~57.0.1" - }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-media-library": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-media-library/-/expo-media-library-57.0.3.tgz", - "integrity": "sha512-VI2NEDaMWIfz9Gxgibzu8BxxxVvgwHRAeZDjV9gci9YH+JkXn5vdvNAnRdmojaPxpp3tbWMfMDB1ympFcrak9A==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react-native": "*" - } - }, "node_modules/expo-modules-autolinking": { "version": "57.0.9", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.9.tgz", diff --git a/mobile/package.json b/mobile/package.json index 97ace66..4218385 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -18,8 +18,6 @@ "expo-file-system": "~57.0.1", "expo-image": "~57.0.1", "expo-image-manipulator": "~57.0.14", - "expo-image-picker": "~57.0.6", - "expo-media-library": "~57.0.3", "expo-print": "~57.0.1", "expo-secure-store": "~57.0.1", "expo-sqlite": "~57.0.1", diff --git a/mobile/services/fileStore/index.ts b/mobile/services/fileStore/index.ts index ae43be5..a7fd0b7 100644 --- a/mobile/services/fileStore/index.ts +++ b/mobile/services/fileStore/index.ts @@ -22,32 +22,37 @@ export function initDB() { ); if (!existingVersion) { - _sqliteDb.execSync(`CREATE TABLE schema_version (version INTEGER PRIMARY KEY);`); - } - - const versionRow = _sqliteDb.getFirstSync<{ version: number }>( - `SELECT version FROM schema_version ORDER BY version DESC LIMIT 1` - ); - const currentVersion = versionRow?.version ?? 0; - - if (currentVersion < SCHEMA_VERSION) { - dropAllTables(_sqliteDb); createSchema(_sqliteDb); + _sqliteDb.execSync(`CREATE TABLE schema_version (version INTEGER PRIMARY KEY);`); _sqliteDb.execSync(`INSERT INTO schema_version (version) VALUES (${SCHEMA_VERSION});`); + } else { + const versionRow = _sqliteDb.getFirstSync<{ version: number }>( + `SELECT version FROM schema_version ORDER BY version DESC LIMIT 1` + ); + const currentVersion = versionRow?.version ?? 0; + + if (currentVersion < SCHEMA_VERSION) { + migrate(_sqliteDb, currentVersion, SCHEMA_VERSION); + _sqliteDb.execSync(`UPDATE schema_version SET version = ${SCHEMA_VERSION};`); + } } _db = drizzle(_sqliteDb); return _db; } -function dropAllTables(db: SQLite.SQLiteDatabase) { - db.execSync(`DROP TABLE IF EXISTS file_tags;`); - db.execSync(`DROP TABLE IF EXISTS files;`); - db.execSync(`DROP TABLE IF EXISTS deleted_files;`); - db.execSync(`DROP TABLE IF EXISTS device_info;`); - db.execSync(`DROP TABLE IF EXISTS resources_fts;`); - db.execSync(`DROP TABLE IF EXISTS pending_actions;`); - db.execSync(`DROP TABLE IF EXISTS schema_version;`); +const MIGRATIONS: Array<(db: SQLite.SQLiteDatabase) => void> = [ + // v5: add thumbnail_local column + (db) => { + db.execSync(`ALTER TABLE files ADD COLUMN thumbnail_local TEXT;`); + }, +]; + +function migrate(db: SQLite.SQLiteDatabase, fromVersion: number, toVersion: number) { + for (let v = fromVersion; v < toVersion; v++) { + const migration = MIGRATIONS[v - 1]; + if (migration) migration(db); + } } function createSchema(db: SQLite.SQLiteDatabase) {