remove picture read access

This commit is contained in:
m
2026-08-28 23:08:52 +02:00
parent 523454fe91
commit bb78d6ac95
8 changed files with 40 additions and 269 deletions
+1 -12
View File
@@ -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",
+1 -37
View File
@@ -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 (
<View style={styles.center}>
{!hasPermission ? (
<View style={styles.permissionBanner}>
<MaterialIcons name="photo-library" size={28} color="#1976D2" />
<Text style={styles.permissionText}>
Autorisez l'accès à vos photos pour les afficher ici
</Text>
<TouchableOpacity style={styles.permissionBtn} onPress={requestPermission}>
<Text style={styles.permissionBtnText}>Autoriser</Text>
</TouchableOpacity>
</View>
) : (
<Text style={styles.infoText}>Chargement...</Text>
)}
</View>
);
}
@@ -426,17 +414,6 @@ export function HomeScreen() {
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? 96 : 0}
>
{!hasPermission && (
<View style={styles.permissionBanner}>
<MaterialIcons name="photo-library" size={28} color="#1976D2" />
<Text style={styles.permissionText}>
Autorisez l'accès à vos photos pour les afficher ici
</Text>
<TouchableOpacity style={styles.permissionBtn} onPress={requestPermission}>
<Text style={styles.permissionBtnText}>Autoriser</Text>
</TouchableOpacity>
</View>
)}
{files.length === 0 && !searchQuery && (
<View style={styles.folderScanBanner}>
{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,
-48
View File
@@ -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) {
</TouchableOpacity>
</View>
<TouchableOpacity
style={styles.photoButton}
onPress={pickImages}
>
<MaterialIcons name="photo-library" size={20} color="#fff" />
<Text style={styles.buttonText}>Sélectionner des photos</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.docButton}
onPress={pickDocuments}
@@ -145,16 +107,6 @@ const styles = StyleSheet.create({
closeBtn: {
padding: 4,
},
photoButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#1976D2',
padding: 16,
borderRadius: 8,
marginBottom: 12,
gap: 8,
},
docButton: {
flexDirection: 'row',
alignItems: 'center',
+12 -122
View File
@@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import * as MediaLibrary from 'expo-media-library/legacy';
import * as FileSystem from 'expo-file-system/legacy';
import { safDirectory, type StoredFolder } from '../services/safDirectory';
import { downloadRegistry } from '../services/downloadRegistry';
@@ -90,30 +89,6 @@ async function scanSafFolder(folder: StoredFolder): Promise<DeviceFile[]> {
}
}
async function scanMediaAlbum(folder: StoredFolder): Promise<DeviceFile[]> {
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<DeviceFile[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasPermission, setHasPermission] = useState(false);
const [folders, setFolders] = useState<StoredFolder[]>(() => 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<number> => {
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();
}, [requestPermission, loadAssets, scanVisibleFolders, loadRegistryFiles]);
} finally {
setIsLoading(false);
}
}, [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,
};
}
+1 -4
View File
@@ -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<Set<string>>(new Set());
@@ -101,13 +101,10 @@ export function useLocalFiles() {
return {
localFiles,
isLoading: deviceLoading,
hasPermission,
requestPermission,
rescan,
pickAndScanRecursive,
folders,
refreshFolders,
discovered,
discoverMediaAlbums,
};
}
-24
View File
@@ -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",
-2
View File
@@ -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",
+18 -13
View File
@@ -22,32 +22,37 @@ export function initDB() {
);
if (!existingVersion) {
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) {
dropAllTables(_sqliteDb);
createSchema(_sqliteDb);
_sqliteDb.execSync(`INSERT INTO schema_version (version) VALUES (${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) {