mirror of
https://github.com/status-im/react-native-camera-roll.git
synced 2026-08-29 04:11:10 +00:00
feat(all): Add useCameraRoll() hook (#354)
* Add `useCameraRoll()` hook * Bump React Native patch ver. to fix Android build
This commit is contained in:
@@ -143,6 +143,8 @@ On Android 13 the `READ_EXTERNAL_STORAGE` has been [replace](https://developer.a
|
||||
* [`getAlbums`](#getalbums)
|
||||
* [`getPhotos`](#getphotos)
|
||||
* [`deletePhotos`](#deletephotos)
|
||||
* [`iosGetImageDataById`](#iosgetimagedatabyid)
|
||||
* [`useCameraRoll`](#usecameraroll)
|
||||
|
||||
---
|
||||
|
||||
@@ -472,6 +474,29 @@ CameraRoll.iosGetImageDataById(internalID, true);
|
||||
| internalID | string | Yes | Ios internal ID 'PH://xxxx'. |
|
||||
| convertHeic | boolean | False | Whether to convert or not to JPEG image. |
|
||||
|
||||
|
||||
|
||||
### `useCameraRoll()`
|
||||
|
||||
`useCameraRoll` is a utility hooks for the CameraRoll module. data contains the content stored in the clipboard.
|
||||
|
||||
```javascript
|
||||
import React, {useEffect} from 'react';
|
||||
import {Button} from 'react-native';
|
||||
import {useCameraRoll} from "@react-native-camera-roll/camera-roll";
|
||||
|
||||
function Example() {
|
||||
const [photos, getPhotos, save] = useCameraRoll();
|
||||
|
||||
return <>
|
||||
<Button title='Get Photos' onPress={() => getPhotos()}>Get Photos</Button>
|
||||
{
|
||||
photos.map((photo, index) => /* render photos */)
|
||||
}
|
||||
</>;
|
||||
};
|
||||
```
|
||||
|
||||
### Known issues
|
||||
|
||||
#### IOS
|
||||
|
||||
+7
-6
@@ -54,29 +54,30 @@
|
||||
"react-native": ">=0.59"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-native-builder-bob": "0.18.3",
|
||||
"@babel/core": "7.18.10",
|
||||
"@babel/runtime": "7.18.9",
|
||||
"@react-native-community/eslint-config": "3.1.0",
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "28.1.7",
|
||||
"@types/react-native": "0.62.10",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-plugin-module-resolver": "4.1.0",
|
||||
"babel-jest": "28.1.3",
|
||||
"babel-plugin-module-resolver": "4.1.0",
|
||||
"detox": "19.9.3",
|
||||
"eslint": "8.22.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"husky": "8.0.1",
|
||||
"jest": "28.1.3",
|
||||
"@types/jest": "28.1.7",
|
||||
"prettier": "2.7.1",
|
||||
"metro-react-native-babel-preset": "0.72.0",
|
||||
"prettier": "2.7.1",
|
||||
"pretty-quick": "3.1.3",
|
||||
"react": "17.0.2",
|
||||
"react-native": "0.66.0",
|
||||
"react-native": "0.66.5",
|
||||
"react-native-builder-bob": "0.18.3",
|
||||
"react-test-renderer": "17.0.2",
|
||||
"semantic-release": "19.0.3",
|
||||
"rimraf": "3.0.2",
|
||||
"semantic-release": "19.0.3",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"react-native-builder-bob": {
|
||||
|
||||
@@ -193,6 +193,7 @@ export class CameraRoll {
|
||||
);
|
||||
return CameraRoll.save(tag, {type});
|
||||
}
|
||||
|
||||
static getAlbums(
|
||||
params: GetAlbumsParams = {assetType: 'All'},
|
||||
): Promise<Album[]> {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import {renderHook} from '@testing-library/react-hooks';
|
||||
import {useCameraRoll} from '../useCameraRoll';
|
||||
import RNCCameraRoll from '../nativeInterface';
|
||||
|
||||
jest.mock('../nativeInterface', () => ({
|
||||
getPhotos: jest.fn(),
|
||||
saveToCameraRoll: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('useCameraRoll()', () => {
|
||||
it('should return initial photos by default', () => {
|
||||
const {result} = renderHook(() => useCameraRoll());
|
||||
|
||||
expect(result.current).toEqual([
|
||||
{
|
||||
edges: [],
|
||||
page_info: {end_cursor: '', has_next_page: false, start_cursor: ''},
|
||||
},
|
||||
expect.any(Function),
|
||||
expect.any(Function),
|
||||
]);
|
||||
});
|
||||
|
||||
describe('saveToCameraRoll()', () => {
|
||||
it('should invoke save with passed params', () => {
|
||||
const tag = 'mock-tag';
|
||||
const type = 'video';
|
||||
const album = 'test-album';
|
||||
const {result} = renderHook(() => useCameraRoll());
|
||||
const [, , saveToCameraRoll] = result.current;
|
||||
|
||||
RNCCameraRoll.saveToCameraRoll.mockResolvedValueOnce('');
|
||||
saveToCameraRoll(tag, {type, album});
|
||||
|
||||
expect(RNCCameraRoll.saveToCameraRoll).toBeCalledWith(tag, {album, type});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPhotos()', () => {
|
||||
const createPhotosMock = ({
|
||||
edges = [] as Array<{node: {type: string}}>,
|
||||
has_next_page = false,
|
||||
start_cursor = '',
|
||||
end_cursor = '',
|
||||
limited = false,
|
||||
} = {}) => ({
|
||||
edges,
|
||||
limited,
|
||||
page_info: {has_next_page, start_cursor, end_cursor},
|
||||
});
|
||||
|
||||
it('should invoke getPhotos with default params', async () => {
|
||||
const {result, waitForNextUpdate} = renderHook(() => useCameraRoll());
|
||||
const [, getPhotos] = result.current;
|
||||
|
||||
RNCCameraRoll.getPhotos.mockResolvedValueOnce(createPhotosMock());
|
||||
getPhotos();
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(RNCCameraRoll.getPhotos).toHaveBeenCalledWith({
|
||||
assetType: 'All',
|
||||
first: 20,
|
||||
groupTypes: 'All',
|
||||
});
|
||||
});
|
||||
|
||||
it('should invoke getPhotos with custom params', async () => {
|
||||
const customParams = {
|
||||
first: 1,
|
||||
assetType: 'Photos' as const,
|
||||
include: ['filename' as const],
|
||||
};
|
||||
const {result, waitForNextUpdate} = renderHook(() => useCameraRoll());
|
||||
const [, getPhotos] = result.current;
|
||||
|
||||
RNCCameraRoll.getPhotos.mockResolvedValueOnce(createPhotosMock());
|
||||
getPhotos(customParams);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(RNCCameraRoll.getPhotos).toHaveBeenCalledWith({
|
||||
assetType: 'Photos',
|
||||
first: 1,
|
||||
groupTypes: 'All',
|
||||
include: ['filename'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return result of getPhotos', async () => {
|
||||
const mockPhotos = createPhotosMock({
|
||||
edges: [{node: {type: 'mock-type'}}],
|
||||
});
|
||||
const {result, waitForNextUpdate} = renderHook(() => useCameraRoll());
|
||||
const [, getPhotos] = result.current;
|
||||
|
||||
RNCCameraRoll.getPhotos.mockResolvedValueOnce(mockPhotos);
|
||||
getPhotos();
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
const [photos] = result.current;
|
||||
|
||||
expect(photos).toEqual(mockPhotos);
|
||||
});
|
||||
|
||||
it('should handle an error when invoke getPhotos', async () => {
|
||||
const error = new Error('Ops...');
|
||||
const {result} = renderHook(() => useCameraRoll());
|
||||
const [initialPhotos, getPhotos] = result.current;
|
||||
|
||||
RNCCameraRoll.getPhotos.mockRejectedValueOnce(error);
|
||||
getPhotos();
|
||||
|
||||
const [afterError] = result.current;
|
||||
|
||||
expect(initialPhotos).toBe(afterError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useCameraRoll';
|
||||
export * from './CameraRoll';
|
||||
export * from './CameraRollIOSPermission';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import {useState} from 'react';
|
||||
import type {
|
||||
GetPhotosParams,
|
||||
PhotoIdentifiersPage,
|
||||
SaveToCameraRollOptions,
|
||||
} from './CameraRoll';
|
||||
import {CameraRoll} from './CameraRoll';
|
||||
|
||||
const initialState: PhotoIdentifiersPage = {
|
||||
edges: [],
|
||||
page_info: {
|
||||
end_cursor: '',
|
||||
has_next_page: false,
|
||||
start_cursor: '',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultConfig: GetPhotosParams = {
|
||||
first: 20,
|
||||
groupTypes: 'All',
|
||||
};
|
||||
|
||||
type UseCameraRollResult = [
|
||||
PhotoIdentifiersPage,
|
||||
(config?: GetPhotosParams) => Promise<void>,
|
||||
(tag: string, options?: SaveToCameraRollOptions) => Promise<void>,
|
||||
];
|
||||
|
||||
export function useCameraRoll(): UseCameraRollResult {
|
||||
const [photos, setPhotos] = useState<PhotoIdentifiersPage>(initialState);
|
||||
|
||||
async function getPhotos(
|
||||
config: GetPhotosParams = defaultConfig,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const result = await CameraRoll.getPhotos(config);
|
||||
setPhotos(result);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.log('[useCameraRoll] Error getting photos: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function save(
|
||||
...args: Parameters<typeof CameraRoll.save>
|
||||
): Promise<void> {
|
||||
try {
|
||||
await CameraRoll.save(...args);
|
||||
} catch (error) {
|
||||
if (__DEV__)
|
||||
console.log('[useCameraRoll] Error saving to camera roll: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
return [photos, getPhotos, save];
|
||||
}
|
||||
@@ -1761,6 +1761,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.12.5":
|
||||
version "7.20.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9"
|
||||
integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.10"
|
||||
|
||||
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.2.2":
|
||||
version "7.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
|
||||
@@ -2856,6 +2863,14 @@
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@testing-library/react-hooks@8.0.1":
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12"
|
||||
integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
react-error-boundary "^3.1.0"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
@@ -9338,6 +9353,13 @@ react-devtools-core@^4.13.0:
|
||||
shell-quote "^1.6.1"
|
||||
ws "^7"
|
||||
|
||||
react-error-boundary@^3.1.0:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0"
|
||||
integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
@@ -9394,10 +9416,10 @@ react-native-codegen@^0.0.7:
|
||||
jscodeshift "^0.11.0"
|
||||
nullthrows "^1.1.1"
|
||||
|
||||
react-native@0.66.0:
|
||||
version "0.66.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.66.0.tgz#99bdd83a9a612a71b94242767989d666d445b007"
|
||||
integrity sha512-m26TKwzsfHVdZ1hDG+7mZ4M4ftxFFZrhtiT0OXuwfBzmNtB3xhsJtYszPeizw33c9YNp8rvehKT3c4ldDCW6kA==
|
||||
react-native@0.66.5:
|
||||
version "0.66.5"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.66.5.tgz#9056a2f7ad04d5e75b3a00dab847b3366d69f26a"
|
||||
integrity sha512-dC5xmE1anT+m8eGU0N/gv2XUWZygii6TTqbwZPsN+uMhVvjxt4FsTqpZOFFvA5sxLPR/NDEz8uybTvItNBMClw==
|
||||
dependencies:
|
||||
"@jest/create-cache-key-function" "^27.0.1"
|
||||
"@react-native-community/cli" "^6.0.0"
|
||||
@@ -9607,6 +9629,11 @@ regenerate@^1.4.2:
|
||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
||||
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
|
||||
|
||||
regenerator-runtime@^0.13.10:
|
||||
version "0.13.10"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee"
|
||||
integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==
|
||||
|
||||
regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4:
|
||||
version "0.13.5"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
|
||||
|
||||
Reference in New Issue
Block a user