remove picture read access
This commit is contained in:
+1
-12
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Generated
-24
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user