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) {