fix(android): use system photo picker

Block broad image and video read permissions in Expo config and the checked-in Android manifest. Keep picker access scoped to user-selected media and gallery saves write-only. Add regression coverage and document the release-manifest invariant.
This commit is contained in:
jjbrunton committed 2026-08-30 17:15:57 +01:00
1 parent 5e581efe51
commit 8658fc38fe
9 files changed
+156 -23

No files matched your search

@@ -0,0 +1,46 @@
import { act, renderHook } from '@testing-library/react-native';
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system/legacy';
import { useCanvasCapture } from './useCanvasCapture';
jest.mock('expo-media-library', () => ({
requestPermissionsAsync: jest.fn(),
saveToLibraryAsync: jest.fn(),
}));
jest.mock('expo-file-system/legacy', () => ({
cacheDirectory: 'file:///cache/',
writeAsStringAsync: jest.fn(),
EncodingType: { Base64: 'base64' },
}));
describe('useCanvasCapture', () => {
beforeEach(() => {
jest.clearAllMocks();
(MediaLibrary.requestPermissionsAsync as jest.Mock).mockResolvedValue({ status: 'granted' });
(MediaLibrary.saveToLibraryAsync as jest.Mock).mockResolvedValue(undefined);
});
it('requests add-only permission before saving the captured image', async () => {
const { result } = renderHook(() => useCanvasCapture());
result.current.makeSnapshot.current = () => ({
encodeToBase64: () => 'encoded-image',
}) as never;
let saved = false;
await act(async () => {
saved = await result.current.saveToGallery();
});
expect(saved).toBe(true);
expect(MediaLibrary.requestPermissionsAsync).toHaveBeenCalledWith(true);
expect(MediaLibrary.saveToLibraryAsync).toHaveBeenCalledWith(
expect.stringMatching(/^file:\/\/\/cache\/livedraw_\d+\.png$/)
);
expect(FileSystem.writeAsStringAsync).toHaveBeenCalledWith(
expect.stringMatching(/^file:\/\/\/cache\/livedraw_\d+\.png$/),
'encoded-image',
{ encoding: 'base64' }
);
});
});