import { getPermissionsAsync, requestPermissionsAsync, createAssetAsync, getAlbumAsync, createAlbumAsync, addAssetsToAlbumAsync } from 'expo-media-library'; import * as FileSystem from 'expo-file-system'; /** * Request necessary media library permissions. * @returns Promise True if permissions are granted. */ export async function requestStoragePermission(): Promise { const { status, canAskAgain } = await getPermissionsAsync(true); if (status === 'granted') { return true; } if (canAskAgain) { const { status: newStatus } = await requestPermissionsAsync(true); return newStatus === 'granted'; } // On Android 13+, writeOnly permissions don't block MediaStore inserts return true; } function getFormattedDateTime(): string { const d = new Date(); const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; const time = `${String(d.getHours()).padStart(2, '0')}-${String(d.getMinutes()).padStart(2, '0')}-${String(d.getSeconds()).padStart(2, '0')}`; return `${date}_${time}`; } /** * Saves a local image file directly to the public DCIM/Pictures gallery inside a structured album. * * @param localUri The local file URI of the image (temp cached file). * @param type 'Raw' for original photos, 'Collage' for final prints. * @param eventName The name of the event to use for folder and file naming. * @returns Promise The permanent URI of the saved asset in the gallery. */ export async function saveToGallery(localUri: string, type: 'Raw' | 'Collage', eventName: string): Promise { const hasPermission = await requestStoragePermission(); if (!hasPermission) { throw new Error('Storage permission not granted. Cannot save photo to gallery.'); } // Clean event name for file system usage const safeEventName = eventName.replace(/[^a-zA-Z0-9_-]/g, '').trim() || 'Event'; const timestamp = getFormattedDateTime(); const fileExtension = localUri.split('.').pop() || 'jpg'; // Format: EventName_YYYY-MM-DD_HH-mm-ss_Type.jpg const newFileName = `${safeEventName}_${timestamp}_${type}.${fileExtension}`; const newLocalUri = `${FileSystem.cacheDirectory}${newFileName}`; // Copy the file to the new location to force the filename await FileSystem.copyAsync({ from: localUri, to: newLocalUri, }); // 1. Create a media asset from the renamed file const asset = await createAssetAsync(newLocalUri); // Define album name const subFolder = type === 'Raw' ? 'Originale' : 'Collagen'; const ALBUM_NAME = `Schnappix - ${safeEventName} - ${subFolder}`; try { // 2. Check if the album already exists const album = await getAlbumAsync(ALBUM_NAME); if (!album) { // 3. If it doesn't exist, create it with our asset await createAlbumAsync(ALBUM_NAME, asset, false); console.log(`Created new album "${ALBUM_NAME}" and copied photo.`); } else { // 4. If it exists, add our asset to it await addAssetsToAlbumAsync([asset], album, false); console.log(`Copied photo to existing album "${ALBUM_NAME}".`); } return asset.uri; } catch (e: any) { console.error(`Error saving asset to album ${ALBUM_NAME}:`, e); return asset.uri; } finally { // Cleanup our temp renamed file try { await FileSystem.deleteAsync(newLocalUri, { idempotent: true }); } catch (e) { // ignore } } }